DynDllImport.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. // Dummy file from https://github.com/MonoMod/MonoMod/pull/65 until it gets merged
  2. #pragma warning disable IDE1006 // Naming Styles
  3. using System.Reflection;
  4. using System.Runtime.InteropServices;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.ComponentModel;
  9. using System.Linq;
  10. namespace MonoMod.Utils.Dummy
  11. {
  12. internal static class DynDll
  13. {
  14. /// <summary>
  15. /// Allows you to remap library paths / names and specify loading flags. Useful for cross-platform compatibility. Applies only to DynDll.
  16. /// </summary>
  17. public static Dictionary<string, List<DynDllMapping>> Mappings = new Dictionary<string, List<DynDllMapping>>();
  18. #region kernel32 imports
  19. [DllImport("kernel32", SetLastError = true)]
  20. private static extern IntPtr GetModuleHandle(string lpModuleName);
  21. [DllImport("kernel32", SetLastError = true)]
  22. private static extern IntPtr LoadLibrary(string lpFileName);
  23. [DllImport("kernel32", SetLastError = true)]
  24. private static extern bool FreeLibrary(IntPtr hLibModule);
  25. [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
  26. private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
  27. #endregion
  28. #region dl imports
  29. [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  30. private static extern IntPtr dlopen(string filename, int flags);
  31. [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  32. private static extern bool dlclose(IntPtr handle);
  33. [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  34. private static extern IntPtr dlsym(IntPtr handle, string symbol);
  35. [DllImport("dl", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
  36. private static extern IntPtr dlerror();
  37. #endregion
  38. private static bool CheckError(out Exception exception)
  39. {
  40. if (PlatformHelper.Is(Platform.Windows))
  41. {
  42. int errorCode = Marshal.GetLastWin32Error();
  43. if (errorCode != 0)
  44. {
  45. exception = new Win32Exception(errorCode);
  46. return false;
  47. }
  48. }
  49. else
  50. {
  51. IntPtr errorCode = dlerror();
  52. if (errorCode != IntPtr.Zero)
  53. {
  54. exception = new Win32Exception(Marshal.PtrToStringAnsi(errorCode));
  55. return false;
  56. }
  57. }
  58. exception = null;
  59. return true;
  60. }
  61. /// <summary>
  62. /// Open a given library and get its handle.
  63. /// </summary>
  64. /// <param name="name">The library name.</param>
  65. /// <param name="skipMapping">Whether to skip using the mapping or not.</param>
  66. /// <param name="flags">Any optional platform-specific flags.</param>
  67. /// <returns>The library handle.</returns>
  68. public static IntPtr OpenLibrary(string name, bool skipMapping = false, int? flags = null)
  69. {
  70. if (!InternalTryOpenLibrary(name, out var libraryPtr, skipMapping, flags))
  71. throw new DllNotFoundException($"Unable to load library '{name}'");
  72. if (!CheckError(out var exception))
  73. throw exception;
  74. return libraryPtr;
  75. }
  76. /// <summary>
  77. /// Try to open a given library and get its handle.
  78. /// </summary>
  79. /// <param name="name">The library name.</param>
  80. /// <param name="libraryPtr">The library handle, or null if it failed loading.</param>
  81. /// <param name="skipMapping">Whether to skip using the mapping or not.</param>
  82. /// <param name="flags">Any optional platform-specific flags.</param>
  83. /// <returns>True if the handle was obtained, false otherwise.</returns>
  84. public static bool TryOpenLibrary(string name, out IntPtr libraryPtr, bool skipMapping = false, int? flags = null)
  85. {
  86. if (!InternalTryOpenLibrary(name, out libraryPtr, skipMapping, flags))
  87. return false;
  88. if (!CheckError(out _))
  89. return false;
  90. return true;
  91. }
  92. private static bool InternalTryOpenLibrary(string name, out IntPtr libraryPtr, bool skipMapping, int? flags)
  93. {
  94. if (name != null && !skipMapping && Mappings.TryGetValue(name, out List<DynDllMapping> mappingList))
  95. {
  96. foreach (var mapping in mappingList)
  97. {
  98. if (InternalTryOpenLibrary(mapping.LibraryName, out libraryPtr, true, mapping.Flags))
  99. return true;
  100. }
  101. libraryPtr = IntPtr.Zero;
  102. return true;
  103. }
  104. if (PlatformHelper.Is(Platform.Windows))
  105. {
  106. libraryPtr = name == null
  107. ? GetModuleHandle(name)
  108. : LoadLibrary(name);
  109. }
  110. else
  111. {
  112. int _flags = flags ?? (DlopenFlags.RTLD_NOW | DlopenFlags.RTLD_GLOBAL); // Default should match LoadLibrary.
  113. libraryPtr = dlopen(name, _flags);
  114. if (libraryPtr == IntPtr.Zero && File.Exists(name))
  115. libraryPtr = dlopen(Path.GetFullPath(name), _flags);
  116. }
  117. return libraryPtr != IntPtr.Zero;
  118. }
  119. /// <summary>
  120. /// Release a library handle obtained via OpenLibrary. Don't release the result of OpenLibrary(null)!
  121. /// </summary>
  122. /// <param name="lib">The library handle.</param>
  123. public static bool CloseLibrary(IntPtr lib)
  124. {
  125. if (PlatformHelper.Is(Platform.Windows))
  126. CloseLibrary(lib);
  127. else
  128. dlclose(lib);
  129. return CheckError(out _);
  130. }
  131. /// <summary>
  132. /// Get a function pointer for a function in the given library.
  133. /// </summary>
  134. /// <param name="libraryPtr">The library handle.</param>
  135. /// <param name="name">The function name.</param>
  136. /// <returns>The function pointer.</returns>
  137. public static IntPtr GetFunction(this IntPtr libraryPtr, string name)
  138. {
  139. if (!InternalTryGetFunction(libraryPtr, name, out var functionPtr))
  140. throw new MissingMethodException($"Unable to load function '{name}'");
  141. if (!CheckError(out var exception))
  142. throw exception;
  143. return functionPtr;
  144. }
  145. /// <summary>
  146. /// Get a function pointer for a function in the given library.
  147. /// </summary>
  148. /// <param name="libraryPtr">The library handle.</param>
  149. /// <param name="name">The function name.</param>
  150. /// <param name="functionPtr">The function pointer, or null if it wasn't found.</param>
  151. /// <returns>True if the function pointer was obtained, false otherwise.</returns>
  152. public static bool TryGetFunction(this IntPtr libraryPtr, string name, out IntPtr functionPtr)
  153. {
  154. if (!InternalTryGetFunction(libraryPtr, name, out functionPtr))
  155. return false;
  156. if (!CheckError(out _))
  157. return false;
  158. return true;
  159. }
  160. private static bool InternalTryGetFunction(IntPtr libraryPtr, string name, out IntPtr functionPtr)
  161. {
  162. if (libraryPtr == IntPtr.Zero)
  163. throw new ArgumentNullException(nameof(libraryPtr));
  164. functionPtr = PlatformHelper.Is(Platform.Windows)
  165. ? GetProcAddress(libraryPtr, name)
  166. : dlsym(libraryPtr, name);
  167. return functionPtr != IntPtr.Zero;
  168. }
  169. /// <summary>
  170. /// Extension method wrapping Marshal.GetDelegateForFunctionPointer
  171. /// </summary>
  172. public static T AsDelegate<T>(this IntPtr s) where T : class
  173. {
  174. #pragma warning disable CS0618 // Type or member is obsolete
  175. return Marshal.GetDelegateForFunctionPointer(s, typeof(T)) as T;
  176. #pragma warning restore CS0618 // Type or member is obsolete
  177. }
  178. /// <summary>
  179. /// Fill all static delegate fields with the DynDllImport attribute.
  180. /// Call this early on in the static constructor.
  181. /// </summary>
  182. /// <param name="type">The type containing the DynDllImport delegate fields.</param>
  183. /// <param name="mappings">Any optional mappings similar to the static mappings.</param>
  184. public static void ResolveDynDllImports(this Type type, Dictionary<string, List<DynDllMapping>> mappings = null)
  185. => InternalResolveDynDllImports(type, null, mappings);
  186. /// <summary>
  187. /// Fill all instance delegate fields with the DynDllImport attribute.
  188. /// Call this early on in the constructor.
  189. /// </summary>
  190. /// <param name="instance">An instance of a type containing the DynDllImport delegate fields.</param>
  191. /// <param name="mappings">Any optional mappings similar to the static mappings.</param>
  192. public static void ResolveDynDllImports(object instance, Dictionary<string, List<DynDllMapping>> mappings = null)
  193. => InternalResolveDynDllImports(instance.GetType(), instance, mappings);
  194. private static void InternalResolveDynDllImports(Type type, object instance, Dictionary<string, List<DynDllMapping>> mappings)
  195. {
  196. BindingFlags fieldFlags = BindingFlags.Public | BindingFlags.NonPublic;
  197. if (instance == null)
  198. fieldFlags |= BindingFlags.Static;
  199. else
  200. fieldFlags |= BindingFlags.Instance;
  201. foreach (FieldInfo field in type.GetFields(fieldFlags))
  202. {
  203. bool found = true;
  204. foreach (DynDllImportAttribute attrib in field.GetCustomAttributes(typeof(DynDllImportAttribute), true))
  205. {
  206. found = false;
  207. IntPtr libraryPtr = IntPtr.Zero;
  208. if (mappings != null && mappings.TryGetValue(attrib.LibraryName, out List<DynDllMapping> mappingList))
  209. {
  210. bool mappingFound = false;
  211. foreach (var mapping in mappingList)
  212. {
  213. if (TryOpenLibrary(mapping.LibraryName, out libraryPtr, true, mapping.Flags))
  214. {
  215. mappingFound = true;
  216. break;
  217. }
  218. }
  219. if (!mappingFound)
  220. continue;
  221. }
  222. else
  223. {
  224. if (!TryOpenLibrary(attrib.LibraryName, out libraryPtr))
  225. continue;
  226. }
  227. foreach (string entryPoint in attrib.EntryPoints.Concat(new[] { field.Name, field.FieldType.Name }))
  228. {
  229. if (!libraryPtr.TryGetFunction(entryPoint, out IntPtr functionPtr))
  230. continue;
  231. #pragma warning disable CS0618 // Type or member is obsolete
  232. field.SetValue(instance, Marshal.GetDelegateForFunctionPointer(functionPtr, field.FieldType));
  233. #pragma warning restore CS0618 // Type or member is obsolete
  234. found = true;
  235. break;
  236. }
  237. if (found)
  238. break;
  239. }
  240. if (!found)
  241. throw new EntryPointNotFoundException($"No matching entry point found for {field.Name} in {field.DeclaringType.FullName}");
  242. }
  243. }
  244. public static class DlopenFlags
  245. {
  246. public const int RTLD_LAZY = 0x0001;
  247. public const int RTLD_NOW = 0x0002;
  248. public const int RTLD_LOCAL = 0x0000;
  249. public const int RTLD_GLOBAL = 0x0100;
  250. }
  251. }
  252. /// <summary>
  253. /// Similar to DllImport, but requires you to run typeof(DeclaringType).ResolveDynDllImports();
  254. /// </summary>
  255. [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
  256. public class DynDllImportAttribute : Attribute
  257. {
  258. /// <summary>
  259. /// The library or library alias to use.
  260. /// </summary>
  261. public string LibraryName { get; set; }
  262. /// <summary>
  263. /// A list of possible entrypoints that the function can be resolved to. Implicitly includes the field name and delegate name.
  264. /// </summary>
  265. public string[] EntryPoints { get; set; }
  266. /// <param name="libraryName">The library or library alias to use.</param>
  267. /// <param name="entryPoints">A list of possible entrypoints that the function can be resolved to. Implicitly includes the field name and delegate name.</param>
  268. public DynDllImportAttribute(string libraryName, params string[] entryPoints)
  269. {
  270. LibraryName = libraryName;
  271. EntryPoints = entryPoints;
  272. }
  273. }
  274. /// <summary>
  275. /// A mapping entry, to be used by <see cref="DynDllImportAttribute"/>.
  276. /// </summary>
  277. public sealed class DynDllMapping
  278. {
  279. /// <summary>
  280. /// The name as which the library will be resolved as. Useful to remap libraries or to provide full paths.
  281. /// </summary>
  282. public string LibraryName { get; set; }
  283. /// <summary>
  284. /// Platform-dependent loading flags.
  285. /// </summary>
  286. public int? Flags { get; set; }
  287. /// <param name="libraryName">The name as which the library will be resolved as. Useful to remap libraries or to provide full paths.</param>
  288. /// <param name="flags">Platform-dependent loading flags.</param>
  289. public DynDllMapping(string libraryName, int? flags = null)
  290. {
  291. LibraryName = libraryName ?? throw new ArgumentNullException(nameof(libraryName));
  292. Flags = flags;
  293. }
  294. public static implicit operator DynDllMapping(string libraryName)
  295. {
  296. return new DynDllMapping(libraryName);
  297. }
  298. }
  299. }