2
\$\begingroup\$

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://stackoverflow.com/questions/17562295/if-i-allocate-some-memory-with-allochglobal-do-i-have-to-free-it-with-freehglob

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

asked Jul 6, 2019 at 9:22
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Guidelines when dealing with P/Invoke

  • move native methods to nested classes (Recommendation CA1060)
  • define the libraries to import as constants const string Kernel32Lib = "Kernel32.dll"; -> [DllImport(Kernel32Lib)]

General guidelines

Avoid unnecessary code blocks.

if (IntPtr.Zero == functionHandle) {
 functionDelegate = default;
 return false;
}
else {
 functionDelegate = Marshal.GetDelegateForFunctionPointer<TDelegate>(functionHandle);
}
return true;

rewritten:

if (IntPtr.Zero == functionHandle) {
 functionDelegate = default;
 return false;
}
functionDelegate = Marshal.GetDelegateForFunctionPointer<TDelegate>(functionHandle);
return true;
answered Jul 6, 2019 at 9:47
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.