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

\$\endgroup\$
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;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.