Recently I've been toying around with C# interop and the Vulkan API. Today I discovered the SafeHandle
class and decided I'd try to wrap the Windows API calls that I depend upon; please see the code below and let me know if I've made any errors during the implementation process.
Code:
public static class Library
{
[Flags]
public enum LoadLibraryType : uint
{
NONE = 0x00000000,
}
[Flags]
public enum ModuleHandleType : uint
{
NONE = 0x00000000,
}
public sealed class LoadLibrarySafeHandle : SafeHandleZeroIsInvalid
{
[DllImport("Kernel32.dll", BestFitMapping = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
private static extern bool FreeLibrary(IntPtr libraryHandle);
[DllImport("Kernel32.dll", BestFitMapping = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
private static extern bool GetModuleHandleExW(ModuleHandleType moduleHandleType, [MarshalAs(UnmanagedType.LPWStr)] string libraryName, out IntPtr libraryHandle);
[DllImport("Kernel32.dll", BestFitMapping = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
private static extern IntPtr GetProcAddress(IntPtr libraryHandle, [MarshalAs(UnmanagedType.LPStr)] string procedureName);
[DllImport("Kernel32.dll", BestFitMapping = false, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
private static extern IntPtr LoadLibraryExW([MarshalAs(UnmanagedType.LPWStr)] string libraryName, IntPtr fileHandle, LoadLibraryType loadLibraryType);
public static LoadLibrarySafeHandle New(IntPtr address) => new LoadLibrarySafeHandle(address);
public static LoadLibrarySafeHandle New(string name, IntPtr fileHandle, LoadLibraryType loadLibraryType) {
if (GetModuleHandleExW(ModuleHandleType.NONE, name, out IntPtr libraryHandle)) {
return New(libraryHandle);
}
else {
libraryHandle = LoadLibraryExW(name, fileHandle, loadLibraryType);
if (IntPtr.Zero == libraryHandle) {
throw new DllNotFoundException(message: $"unable to find a library named {name}", inner: Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
}
return New(libraryHandle);
}
}
public static LoadLibrarySafeHandle New(string name) => New(name, IntPtr.Zero, LoadLibraryType.NONE);
private LoadLibrarySafeHandle(IntPtr address) : base(true) {
SetHandle(address);
}
[PrePrepareMethod]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle() => FreeLibrary(handle);
public bool TryGetDelegateForFunctionPointer<TDelegate>(string functionName, out TDelegate functionDelegate) {
var functionHandle = GetProcAddress(handle, functionName);
if (IntPtr.Zero == functionHandle) {
functionDelegate = default;
return false;
}
else {
functionDelegate = Marshal.GetDelegateForFunctionPointer<TDelegate>(functionHandle);
}
return true;
}
}
}
Usage:
class Program
{
private delegate IntPtr vkGetInstanceProcAddr(IntPtr instance, IntPtr pName);
static void Main(string[] args) {
var vkDllHandle = Library.LoadLibrarySafeHandle.New("vulkan-1.dll");
if (vkDllHandle.TryGetDelegateForFunctionPointer(nameof(vkGetInstanceProcAddr), out vkGetInstanceProcAddr vkGetInstanceProcAddrDelegate)) {
// do something with vkGetInstanceProcAddrDelegate
}
}
}
References:
https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About