C
C#8mo ago
BattleFrogue

✅ Debugging exceptions not caught by try-catch

Hi, I have a small piece of code I am having trouble debugging. I have this class:
public static partial class Library
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void PfnVkCreateInstance(IntPtr pCreateInfo, IntPtr pAllocator, out IntPtr pInstance);
private static PfnVkCreateInstance CreateInstanceInternal;

public static void LoadLibrary()
{
IntPtr createInstanceFp = GetInstanceProcAddr(IntPtr.Zero, "vkCreateInstance");

try
{
CreateInstanceInternal = Marshal.GetDelegateForFunctionPointer<PfnVkCreateInstance>(createInstanceFp);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}

[LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)]
private static partial int GetInstanceProcAddr(IntPtr module, string name);
}
public static partial class Library
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void PfnVkCreateInstance(IntPtr pCreateInfo, IntPtr pAllocator, out IntPtr pInstance);
private static PfnVkCreateInstance CreateInstanceInternal;

public static void LoadLibrary()
{
IntPtr createInstanceFp = GetInstanceProcAddr(IntPtr.Zero, "vkCreateInstance");

try
{
CreateInstanceInternal = Marshal.GetDelegateForFunctionPointer<PfnVkCreateInstance>(createInstanceFp);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}

[LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)]
private static partial int GetInstanceProcAddr(IntPtr module, string name);
}
And the line I am getting the exception on is CreateInstanceInternal = Marshal.GetDelegateForFunctionPointer<PfnVkCreateInstance>(createInstanceFp);. The problem I am having is that the exception that is thrown is both not caught by the catch block and doesn't provide any information as to why it throws the exception. I've even stepped through the IL generated for Marshal.GetDelegateForFunctionPointer<PfnVkCreateInstance>(createInstanceFp); and it passes through all the exception checks that it has. So I would have thought that the function would have succeeded. I'm at a loss as to what to try next. Any ideas?
29 Replies
JakenVeina
JakenVeina8mo ago
there's no such thing as an exception that cannot be caught by a try/catch the exception is not being thrown from Marshal.GetDelegateForFunctionPointer() what are you actually observing?
BattleFrogue
BattleFrogue8mo ago
The behaviour I am observing is that execution reaches CreateInstanceInternal = Marshal.GetDelegateForFunctionPointer<PfnVkCreateInstance>(createInstanceFp); I step into the function just fine and manage to step over all the lines until the very last line in the IL code: return (TDelegate)(object)GetDelegateForFunctionPointerInternal(ptr, t);. When I try to step over that line I get an EngineExecutionError that isn't caught by try-catch block.
No description
BattleFrogue
BattleFrogue8mo ago
I have tried catching both Exceptions and EngineExecutionExceptions. In fact when I tried catching an EngineExecutionException specifically Rider warned me that those exceptions aren't even raised by the runtime anymore.
matkoch
matkoch8mo ago
And if you continue, will it get to the catch block?
BattleFrogue
BattleFrogue8mo ago
If I try to continue the app stops entirely and reports a fatal internal CLR error:
Fatal error. Internal CLR error. (0x80131506)
at System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointerInternal(IntPtr, System.Type)
at System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](IntPtr)
at VulkanSDK.VulkanNative.Library.LoadLibrary()
at Program.<Main>$(System.String[])
Fatal error. Internal CLR error. (0x80131506)
at System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointerInternal(IntPtr, System.Type)
at System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](IntPtr)
at VulkanSDK.VulkanNative.Library.LoadLibrary()
at Program.<Main>$(System.String[])
matkoch
matkoch8mo ago
since I see Vulkan... maybe @ref<'perksey> has some idea
Perksey
Perksey8mo ago
ExecutionEngineException, AccessViolationException, and on non-Windows any exception that isn’t caught until it crosses a native barrier all can’t be caught. Is the delegate type you’re using marked with the UnmanagedFunctionPointer attribute? By the way I recommend using Silk.NET (takes care of everything for you), C# 9 function pointers, or basically anything other than the really slow and clunky delegate-based interop functions.
BattleFrogue
BattleFrogue8mo ago
I do, I have [UnmanagedFunctionPointer(CallingConvention.StdCall)] and I have also tried [UnmanagedFunctionPointer(CallingConvention.Cdecl]. I have seen Silk.NET, and as nice as it looks part of this exercise is me wanting to learn how to do performant native interops. Even as wide spread as the .NET community is I'm not always going to be lucky enough that there is a ready made library that does it for me. It's interesting that you mention that delegates are slow and clunky because I actually asked here in a different thread what the most performance way to do native function calls are and the LibraryLoad attribute was what was suggested to me. Does Silk use C#9 function pointers?
Perksey
Perksey8mo ago
yeah silk uses C# 9 function pointers, you have to use them for Vulkan (or the delegate tricks which are awful these days) it’s basically ((delegate* unmanaged[Cdecl]<VkInstanceCreateInfo*, VkAllocationCallbacks*, VkInstanceT*>)createInstanceFp)(…); This is the most efficient way to call a native function that is not a simple static export today LibraryImport is just DllImport but with marshaling done by a source generator instead of the runtime, so it can only be used with simple static exports
BattleFrogue
BattleFrogue8mo ago
I see, thanks for the advice. And just so I'm understanding this correctly. I would be able to use a C# 9 function pointer for a native function pointer returned to me by using vkGetInstanceProcAddr? I would like to follow Khronos' guidelines for reducing driver overhead
Perksey
Perksey8mo ago
yeah that’s the idea, you could (and probably should) DllImport vkGetInstanceProcAddr as that’s always guaranteed to be exported by the loader and will ensure that you remain AOT compatible, but for the rest of th use function pointers Silk.NET generates a bunch of instance device pair specific function pointer tables and store them in an object on which you call the functions
BattleFrogue
BattleFrogue8mo ago
Oh cool, so I was part way there with my LibraryLoad of vkGetInstanceProcAddr it was just the latter part that was slow and clunky (and broken) That sounds very similar to how I wanted to set mine up
Perksey
Perksey8mo ago
ah cool, I’m glad someone else gets it. Most people actually really dislike the API-as-objects thing but we have to do this for correctness and overhead.
BattleFrogue
BattleFrogue8mo ago
I will admit I am more familiar with the raw C api and prefer it to say the C++ api. But I think in the context of C/C++ it makes sense to use the raw API. C# is a managed lanugage and I feel like having a more managed approach to it, even when AOT'd, makes sense I do also plan to have a Raw namepsace which will be just the C api with a C# front cover. So in the event that I want to use the raw API I can, and internall the managed API will use the Raw function calls
Perksey
Perksey8mo ago
yeah that’s the thing, like yeah the C api is all static but that’s because it’s doing a bunch of state-based trampolines under-the-hood within the loader oh we still expose the raw API exclusively, we just don’t expose them as static functions given that the function pointers are instance and device dependent
BattleFrogue
BattleFrogue8mo ago
Right, because vkGetInstanceProcAddr/vkGetDeviceProcAddr require a valid Instance or Device. C get's away with it because it does the trampolining, but if I wanted to do that in C# every function would have to be LibraryLoaded which would be a performance nightmare. Okay maybe I don't do that then 😂
Perksey
Perksey8mo ago
any other questions just shout, I’ve spent three years of my life bikeshedding stuff like this 😅
BattleFrogue
BattleFrogue8mo ago
Thanks, I'll keep that in mind. Any particular location to shout you from? Sorry for the ping, but I just wanted to check my use of C# function pointers. Would this be how you expect them to be used?
using System.Runtime.InteropServices;

namespace VulkanSDK.VulkanNative
{
public class ExtensionProperties
{
// TODO: Populate struct
}

public static unsafe partial class Library
{

[LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr GetInstanceProcAddr(IntPtr module, string name);

private static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, List<ExtensionProperties>> EnumerateInstanceLayerPropertiesDelegate;

public static void LoadLibrary()
{
EnumerateInstanceLayerPropertiesDelegate = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, List<ExtensionProperties>>)GetInstanceProcAddr(IntPtr.Zero, "vkEnumerateInstanceLayerProperties");
List<ExtensionProperties> extensionPropertiesList = EnumerateInstanceLayerPropertiesDelegate(IntPtr.Zero, IntPtr.Zero);
}
}
}
using System.Runtime.InteropServices;

namespace VulkanSDK.VulkanNative
{
public class ExtensionProperties
{
// TODO: Populate struct
}

public static unsafe partial class Library
{

[LibraryImport("vulkan-1.dll", EntryPoint = "vkGetInstanceProcAddr", StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr GetInstanceProcAddr(IntPtr module, string name);

private static delegate* unmanaged[Cdecl]<IntPtr, IntPtr, List<ExtensionProperties>> EnumerateInstanceLayerPropertiesDelegate;

public static void LoadLibrary()
{
EnumerateInstanceLayerPropertiesDelegate = (delegate* unmanaged[Cdecl]<IntPtr, IntPtr, List<ExtensionProperties>>)GetInstanceProcAddr(IntPtr.Zero, "vkEnumerateInstanceLayerProperties");
List<ExtensionProperties> extensionPropertiesList = EnumerateInstanceLayerPropertiesDelegate(IntPtr.Zero, IntPtr.Zero);
}
}
}
Assuming that that's correct, I would also typedef the function pointer for better readability
Perksey
Perksey8mo ago
oh why'd you have to use IntPtr 😭 but no that's not correct, and this is possibly why your delegate wasn't working either. the signatures need to be fully blittable
BattleFrogue
BattleFrogue8mo ago
Is there something wrong with IntPtr? I thought that was how C# represented pointers and IntPtr.Zero is the equivalent of null?
Perksey
Perksey8mo ago
C# represents pointers using pointers uint* ExtensionProperties* this particular function would be
(delegate* unmanaged[Cdecl]<uint*, LayerProperties*, Result>)
(delegate* unmanaged[Cdecl]<uint*, LayerProperties*, Result>)
BattleFrogue
BattleFrogue8mo ago
I see, I have been lied to by the internet. Colour me shocked 🤣
Perksey
Perksey8mo ago
the internet is full of a ridiculous heap of misinformation about C# and its native interoperability and that's because it's had so many forms over the years e.g. there was a time where making a C++/CLI shim was standard practice and a time when CLS compliance mattered, that's where the IntPtr to represent pointers came from
BattleFrogue
BattleFrogue8mo ago
I see, that does complicate things. In my own research I find that there's either a lot of research on a topic, usually pre-2012. Or 1 page on MS's docs site that doesn't really give me enough information for me to understand how it works
Perksey
Perksey8mo ago
more reason to rely on a library that has already spent years figuring this all out haha
BattleFrogue
BattleFrogue8mo ago
True, true. But like I said, I'd rather learn this myself so I can apply it to other libraries. My main ulterior motive right now are some internal libraries at work. We're forced to use C/C++ because they are C libraries. But I personally have fallen out of favour with C++ and want to move to .NET now it has AOT support. I picked Vulkan as a practice library because it's a library I have native familiarity with and because it had the added complexity of needing to load function pointers manually to skip trampolining. I figured if I can get some semi-complex demo running using Vulkan I can map our internal libraries to .NET and use a more palletable language (in my opinion) Got that function working. At least for evalutating how many layers there are. Now to make it return the actual data. Thanks again
Perksey
Perksey8mo ago
np. here's some old generated code from silk in case you get stuck oh wait that's the crappy .NET Standard version, one sec
Perksey
Perksey8mo ago
this should be the good one
BattleFrogue
BattleFrogue8mo ago
Awesome, thanks.