// Dummy file from https://github.com/MonoMod/MonoMod/pull/65 until it gets merged #pragma warning disable IDE1006 // Naming Styles using System.Reflection; using System.Runtime.InteropServices; using System; using System.Collections.Generic; using System.IO; using System.ComponentModel; using System.Linq; namespace MonoMod.Utils.Dummy { internal static class DynDll { /// /// Allows you to remap library paths / names and specify loading flags. Useful for cross-platform compatibility. Applies only to DynDll. /// public static Dictionary> Mappings = new Dictionary>(); #region kernel32 imports [DllImport("kernel32", SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("kernel32", SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32", SetLastError = true)] private static extern bool FreeLibrary(IntPtr hLibModule); [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); #endregion #region dl imports [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern IntPtr dlopen(string filename, int flags); [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern bool dlclose(IntPtr handle); [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern IntPtr dlsym(IntPtr handle, string symbol); [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern IntPtr dlerror(); #endregion private static bool CheckError(out Exception exception) { if (PlatformHelper.Is(Platform.Windows)) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) { exception = new Win32Exception(errorCode); return false; } } else { IntPtr errorCode = dlerror(); if (errorCode != IntPtr.Zero) { exception = new Win32Exception(Marshal.PtrToStringAnsi(errorCode)); return false; } } exception = null; return true; } /// /// Open a given library and get its handle. /// /// The library name. /// Whether to skip using the mapping or not. /// Any optional platform-specific flags. /// The library handle. public static IntPtr OpenLibrary(string name, bool skipMapping = false, int? flags = null) { if (!InternalTryOpenLibrary(name, out var libraryPtr, skipMapping, flags)) throw new DllNotFoundException($"Unable to load library '{name}'"); if (!CheckError(out var exception)) throw exception; return libraryPtr; } /// /// Try to open a given library and get its handle. /// /// The library name. /// The library handle, or null if it failed loading. /// Whether to skip using the mapping or not. /// Any optional platform-specific flags. /// True if the handle was obtained, false otherwise. public static bool TryOpenLibrary(string name, out IntPtr libraryPtr, bool skipMapping = false, int? flags = null) { if (!InternalTryOpenLibrary(name, out libraryPtr, skipMapping, flags)) return false; if (!CheckError(out _)) return false; return true; } private static bool InternalTryOpenLibrary(string name, out IntPtr libraryPtr, bool skipMapping, int? flags) { if (name != null && !skipMapping && Mappings.TryGetValue(name, out List mappingList)) { foreach (var mapping in mappingList) { if (InternalTryOpenLibrary(mapping.LibraryName, out libraryPtr, true, mapping.Flags)) return true; } libraryPtr = IntPtr.Zero; return true; } if (PlatformHelper.Is(Platform.Windows)) { libraryPtr = name == null ? GetModuleHandle(name) : LoadLibrary(name); } else { int _flags = flags ?? (DlopenFlags.RTLD_NOW | DlopenFlags.RTLD_GLOBAL); // Default should match LoadLibrary. libraryPtr = dlopen(name, _flags); if (libraryPtr == IntPtr.Zero && File.Exists(name)) libraryPtr = dlopen(Path.GetFullPath(name), _flags); } return libraryPtr != IntPtr.Zero; } /// /// Release a library handle obtained via OpenLibrary. Don't release the result of OpenLibrary(null)! /// /// The library handle. public static bool CloseLibrary(IntPtr lib) { if (PlatformHelper.Is(Platform.Windows)) CloseLibrary(lib); else dlclose(lib); return CheckError(out _); } /// /// Get a function pointer for a function in the given library. /// /// The library handle. /// The function name. /// The function pointer. public static IntPtr GetFunction(this IntPtr libraryPtr, string name) { if (!InternalTryGetFunction(libraryPtr, name, out var functionPtr)) throw new MissingMethodException($"Unable to load function '{name}'"); if (!CheckError(out var exception)) throw exception; return functionPtr; } /// /// Get a function pointer for a function in the given library. /// /// The library handle. /// The function name. /// The function pointer, or null if it wasn't found. /// True if the function pointer was obtained, false otherwise. public static bool TryGetFunction(this IntPtr libraryPtr, string name, out IntPtr functionPtr) { if (!InternalTryGetFunction(libraryPtr, name, out functionPtr)) return false; if (!CheckError(out _)) return false; return true; } private static bool InternalTryGetFunction(IntPtr libraryPtr, string name, out IntPtr functionPtr) { if (libraryPtr == IntPtr.Zero) throw new ArgumentNullException(nameof(libraryPtr)); functionPtr = PlatformHelper.Is(Platform.Windows) ? GetProcAddress(libraryPtr, name) : dlsym(libraryPtr, name); return functionPtr != IntPtr.Zero; } /// /// Extension method wrapping Marshal.GetDelegateForFunctionPointer /// public static T AsDelegate(this IntPtr s) where T : class { #pragma warning disable CS0618 // Type or member is obsolete return Marshal.GetDelegateForFunctionPointer(s, typeof(T)) as T; #pragma warning restore CS0618 // Type or member is obsolete } /// /// Fill all static delegate fields with the DynDllImport attribute. /// Call this early on in the static constructor. /// /// The type containing the DynDllImport delegate fields. /// Any optional mappings similar to the static mappings. public static void ResolveDynDllImports(this Type type, Dictionary> mappings = null) => InternalResolveDynDllImports(type, null, mappings); /// /// Fill all instance delegate fields with the DynDllImport attribute. /// Call this early on in the constructor. /// /// An instance of a type containing the DynDllImport delegate fields. /// Any optional mappings similar to the static mappings. public static void ResolveDynDllImports(object instance, Dictionary> mappings = null) => InternalResolveDynDllImports(instance.GetType(), instance, mappings); private static void InternalResolveDynDllImports(Type type, object instance, Dictionary> mappings) { BindingFlags fieldFlags = BindingFlags.Public | BindingFlags.NonPublic; if (instance == null) fieldFlags |= BindingFlags.Static; else fieldFlags |= BindingFlags.Instance; foreach (FieldInfo field in type.GetFields(fieldFlags)) { bool found = true; foreach (DynDllImportAttribute attrib in field.GetCustomAttributes(typeof(DynDllImportAttribute), true)) { found = false; IntPtr libraryPtr = IntPtr.Zero; if (mappings != null && mappings.TryGetValue(attrib.LibraryName, out List mappingList)) { bool mappingFound = false; foreach (var mapping in mappingList) { if (TryOpenLibrary(mapping.LibraryName, out libraryPtr, true, mapping.Flags)) { mappingFound = true; break; } } if (!mappingFound) continue; } else { if (!TryOpenLibrary(attrib.LibraryName, out libraryPtr)) continue; } foreach (string entryPoint in attrib.EntryPoints.Concat(new[] { field.Name, field.FieldType.Name })) { if (!libraryPtr.TryGetFunction(entryPoint, out IntPtr functionPtr)) continue; #pragma warning disable CS0618 // Type or member is obsolete field.SetValue(instance, Marshal.GetDelegateForFunctionPointer(functionPtr, field.FieldType)); #pragma warning restore CS0618 // Type or member is obsolete found = true; break; } if (found) break; } if (!found) throw new EntryPointNotFoundException($"No matching entry point found for {field.Name} in {field.DeclaringType.FullName}"); } } public static class DlopenFlags { public const int RTLD_LAZY = 0x0001; public const int RTLD_NOW = 0x0002; public const int RTLD_LOCAL = 0x0000; public const int RTLD_GLOBAL = 0x0100; } } /// /// Similar to DllImport, but requires you to run typeof(DeclaringType).ResolveDynDllImports(); /// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] public class DynDllImportAttribute : Attribute { /// /// The library or library alias to use. /// public string LibraryName { get; set; } /// /// A list of possible entrypoints that the function can be resolved to. Implicitly includes the field name and delegate name. /// public string[] EntryPoints { get; set; } /// The library or library alias to use. /// A list of possible entrypoints that the function can be resolved to. Implicitly includes the field name and delegate name. public DynDllImportAttribute(string libraryName, params string[] entryPoints) { LibraryName = libraryName; EntryPoints = entryPoints; } } /// /// A mapping entry, to be used by . /// public sealed class DynDllMapping { /// /// The name as which the library will be resolved as. Useful to remap libraries or to provide full paths. /// public string LibraryName { get; set; } /// /// Platform-dependent loading flags. /// public int? Flags { get; set; } /// The name as which the library will be resolved as. Useful to remap libraries or to provide full paths. /// Platform-dependent loading flags. public DynDllMapping(string libraryName, int? flags = null) { LibraryName = libraryName ?? throw new ArgumentNullException(nameof(libraryName)); Flags = flags; } public static implicit operator DynDllMapping(string libraryName) { return new DynDllMapping(libraryName); } } }