using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security.Permissions; using Microsoft.Win32.SafeHandles; // Source: https://docs.microsoft.com/en-us/archive/blogs/jmstall/type-safe-managed-wrappers-for-kernel32getprocaddress namespace SimConnect.Lib { /// /// Utility class to wrap an unmanaged DLL and be responsible for freeing it. /// /// /// This is a managed wrapper over the native LoadLibrary, GetProcAddress, and FreeLibrary calls. /// public sealed class UnmanagedLibrary : IDisposable { /// /// See http://msdn.microsoft.com/msdnmag/issues/05/10/Reliability/ for more about safe handles. /// [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeLibraryHandle() : base(true) { } /// protected override bool ReleaseHandle() { return NativeMethods.FreeLibrary(handle); } } private static class NativeMethods { private const string SKernel = "kernel32"; [DllImport(SKernel, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] public static extern SafeLibraryHandle LoadLibrary(string fileName); [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [DllImport(SKernel, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool FreeLibrary(IntPtr hModule); [DllImport(SKernel)] public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, String procname); } /// /// Constructor to load a dll and be responible for freeing it. /// /// full path name of dll to load /// if fileName can't be found /// Throws exceptions on failure. Most common failure would be file-not-found, or /// that the file is not a loadable image. public UnmanagedLibrary(string fileName) { libraryHandle = NativeMethods.LoadLibrary(fileName); if (!libraryHandle.IsInvalid) return; var hr = Marshal.GetHRForLastWin32Error(); Marshal.ThrowExceptionForHR(hr); } /// /// Dynamically lookup a function in the dll via kernel32!GetProcAddress. /// /// raw name of the function in the export table. /// null if function is not found. Else a delegate to the unmanaged function. /// /// GetProcAddress results are valid as long as the dll is not yet unloaded. This /// is very very dangerous to use since you need to ensure that the dll is not unloaded /// until after you're done with any objects implemented by the dll. For example, if you /// get a delegate that then gets an IUnknown implemented by this dll, /// you can not dispose this library until that IUnknown is collected. Else, you may free /// the library and then the CLR may call release on that IUnknown and it will crash. public TDelegate GetUnmanagedFunction(string functionName) where TDelegate : class { var p = NativeMethods.GetProcAddress(libraryHandle, functionName); // Failure is a common case, especially for adaptive code. if (p == IntPtr.Zero) return null; var function = Marshal.GetDelegateForFunctionPointer(p, typeof(TDelegate)); // Ideally, we'd just make the constraint on TDelegate be // System.Delegate, but compiler error CS0702 (constrained can't be System.Delegate) // prevents that. So we make the constraint system.object and do the cast from object-->TDelegate. object o = function; return (TDelegate)o; } /// /// Call FreeLibrary on the unmanaged dll. All function pointers /// handed out from this class become invalid after this. /// /// This is very dangerous because it suddenly invalidate /// everything retrieved from this dll. This includes any functions /// handed out via GetProcAddress, and potentially any objects returned /// from those functions (which may have an implemention in the /// dll). /// public void Dispose() { if (!libraryHandle.IsClosed) { libraryHandle.Close(); } } // Unmanaged resource. CLR will ensure SafeHandles get freed, without requiring a finalizer on this class. private readonly SafeLibraryHandle libraryHandle; } }