UnmanagedLibrary.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Copyright 2015 gRPC authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #define GUICS
  15. using System.Diagnostics.CodeAnalysis;
  16. using System.Runtime.InteropServices;
  17. namespace Unix.Terminal;
  18. /// <summary>
  19. /// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner. First, the
  20. /// native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows). dlsym or GetProcAddress
  21. /// are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c> transforms the addresses
  22. /// into delegates to native methods. See
  23. /// http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
  24. /// </summary>
  25. internal class UnmanagedLibrary
  26. {
  27. private const string UnityEngineApplicationClassName = "UnityEngine.Application, UnityEngine";
  28. private const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android";
  29. private const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS";
  30. private static readonly bool IsWindows;
  31. private static readonly bool IsLinux;
  32. private static readonly bool Is64Bit;
  33. #if GUICS
  34. private static readonly bool IsMono;
  35. #else
  36. static bool IsMono, IsUnity, IsXamarinIOS, IsXamarinAndroid, IsXamarin;
  37. #endif
  38. private static bool IsNetCore;
  39. public static bool IsMacOSPlatform { get; }
  40. [DllImport ("libc")]
  41. private static extern int uname (nint buf);
  42. private static string GetUname ()
  43. {
  44. nint buffer = Marshal.AllocHGlobal (8192);
  45. try
  46. {
  47. if (uname (buffer) == 0)
  48. {
  49. return Marshal.PtrToStringAnsi (buffer);
  50. }
  51. return string.Empty;
  52. }
  53. catch
  54. {
  55. return string.Empty;
  56. }
  57. finally
  58. {
  59. if (buffer != nint.Zero)
  60. {
  61. Marshal.FreeHGlobal (buffer);
  62. }
  63. }
  64. }
  65. [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
  66. static UnmanagedLibrary ()
  67. {
  68. PlatformID platform = Environment.OSVersion.Platform;
  69. IsMacOSPlatform = platform == PlatformID.Unix && GetUname () == "Darwin";
  70. IsLinux = platform == PlatformID.Unix && !IsMacOSPlatform;
  71. IsWindows = platform == PlatformID.Win32NT
  72. || platform == PlatformID.Win32S
  73. || platform == PlatformID.Win32Windows;
  74. Is64Bit = Marshal.SizeOf (typeof (nint)) == 8;
  75. IsMono = Type.GetType ("Mono.Runtime") != null;
  76. if (!IsMono)
  77. {
  78. IsNetCore = Type.GetType ("System.MathF") != null;
  79. }
  80. #if GUICS
  81. //IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false;
  82. #else
  83. IsUnity = Type.GetType (UnityEngineApplicationClassName) != null;
  84. IsXamarinIOS = Type.GetType (XamarinIOSObjectClassName) != null;
  85. IsXamarinAndroid = Type.GetType (XamarinAndroidObjectClassName) != null;
  86. IsXamarin = IsXamarinIOS || IsXamarinAndroid;
  87. #endif
  88. }
  89. // flags for dlopen
  90. private const int RTLD_LAZY = 1;
  91. private const int RTLD_GLOBAL = 8;
  92. public readonly string LibraryPath;
  93. public nint NativeLibraryHandle { get; }
  94. //
  95. // if isFullPath is set to true, the provided array of libraries are full paths
  96. // and are tested for the file existing, otherwise the file is merely the name
  97. // of the shared library that we pass to dlopen
  98. //
  99. public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
  100. {
  101. if (isFullPath)
  102. {
  103. LibraryPath = FirstValidLibraryPath (libraryPathAlternatives);
  104. NativeLibraryHandle = PlatformSpecificLoadLibrary (LibraryPath);
  105. }
  106. else
  107. {
  108. foreach (string lib in libraryPathAlternatives)
  109. {
  110. NativeLibraryHandle = PlatformSpecificLoadLibrary (lib);
  111. if (NativeLibraryHandle != nint.Zero)
  112. {
  113. LibraryPath = lib;
  114. break;
  115. }
  116. }
  117. }
  118. if (NativeLibraryHandle == nint.Zero)
  119. {
  120. throw new IOException ($"Error loading native library \"{string.Join (", ", libraryPathAlternatives)}\"");
  121. }
  122. }
  123. /// <summary>Loads symbol in a platform specific way.</summary>
  124. /// <param name="symbolName"></param>
  125. /// <returns></returns>
  126. public nint LoadSymbol (string symbolName)
  127. {
  128. if (IsWindows)
  129. {
  130. // See http://stackoverflow.com/questions/10473310 for background on this.
  131. if (Is64Bit)
  132. {
  133. return Windows.GetProcAddress (NativeLibraryHandle, symbolName);
  134. }
  135. // Yes, we could potentially predict the size... but it's a lot simpler to just try
  136. // all the candidates. Most functions have a suffix of @0, @4 or @8 so we won't be trying
  137. // many options - and if it takes a little bit longer to fail if we've really got the wrong
  138. // library, that's not a big problem. This is only called once per function in the native library.
  139. symbolName = "_" + symbolName + "@";
  140. for (var stackSize = 0; stackSize < 128; stackSize += 4)
  141. {
  142. nint candidate = Windows.GetProcAddress (NativeLibraryHandle, symbolName + stackSize);
  143. if (candidate != nint.Zero)
  144. {
  145. return candidate;
  146. }
  147. }
  148. // Fail.
  149. return nint.Zero;
  150. }
  151. if (IsLinux)
  152. {
  153. if (IsMono)
  154. {
  155. return Mono.dlsym (NativeLibraryHandle, symbolName);
  156. }
  157. if (IsNetCore)
  158. {
  159. return CoreCLR.dlsym (NativeLibraryHandle, symbolName);
  160. }
  161. return Linux.dlsym (NativeLibraryHandle, symbolName);
  162. }
  163. if (IsMacOSPlatform)
  164. {
  165. return MacOSX.dlsym (NativeLibraryHandle, symbolName);
  166. }
  167. throw new InvalidOperationException ("Unsupported platform.");
  168. }
  169. public T GetNativeMethodDelegate<T> (string methodName)
  170. where T : class
  171. {
  172. nint ptr = LoadSymbol (methodName);
  173. if (ptr == nint.Zero)
  174. {
  175. throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
  176. }
  177. return Marshal.GetDelegateForFunctionPointer<T> (ptr); // non-generic version is obsolete
  178. }
  179. /// <summary>Loads library in a platform specific way.</summary>
  180. private static nint PlatformSpecificLoadLibrary (string libraryPath)
  181. {
  182. if (IsWindows)
  183. {
  184. return Windows.LoadLibrary (libraryPath);
  185. }
  186. if (IsLinux)
  187. {
  188. if (IsMono)
  189. {
  190. return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
  191. }
  192. if (IsNetCore)
  193. {
  194. try
  195. {
  196. return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
  197. }
  198. catch (Exception)
  199. {
  200. IsNetCore = false;
  201. }
  202. }
  203. return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
  204. }
  205. if (IsMacOSPlatform)
  206. {
  207. return MacOSX.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
  208. }
  209. throw new InvalidOperationException ("Unsupported platform.");
  210. }
  211. private static string FirstValidLibraryPath (string [] libraryPathAlternatives)
  212. {
  213. foreach (string path in libraryPathAlternatives)
  214. {
  215. if (File.Exists (path))
  216. {
  217. return path;
  218. }
  219. }
  220. throw new FileNotFoundException (
  221. string.Format (
  222. "Error loading native library. Not found in any of the possible locations: {0}",
  223. string.Join (",", libraryPathAlternatives)
  224. )
  225. );
  226. }
  227. private static class Windows
  228. {
  229. [DllImport ("kernel32.dll")]
  230. internal static extern nint GetProcAddress (nint hModule, string procName);
  231. [DllImport ("kernel32.dll")]
  232. internal static extern nint LoadLibrary (string filename);
  233. }
  234. private static class Linux
  235. {
  236. [DllImport ("libdl.so")]
  237. internal static extern nint dlopen (string filename, int flags);
  238. [DllImport ("libdl.so")]
  239. internal static extern nint dlsym (nint handle, string symbol);
  240. }
  241. private static class MacOSX
  242. {
  243. [DllImport ("libSystem.dylib")]
  244. internal static extern nint dlopen (string filename, int flags);
  245. [DllImport ("libSystem.dylib")]
  246. internal static extern nint dlsym (nint handle, string symbol);
  247. }
  248. /// <summary>
  249. /// On Linux systems, using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if
  250. /// libc6-dev is not installed. As a workaround, we load symbols for dlopen and dlsym from the current process as on
  251. /// Linux Mono sure is linked against these symbols.
  252. /// </summary>
  253. private static class Mono
  254. {
  255. [DllImport ("__Internal")]
  256. internal static extern nint dlopen (string filename, int flags);
  257. [DllImport ("__Internal")]
  258. internal static extern nint dlsym (nint handle, string symbol);
  259. }
  260. /// <summary>
  261. /// Similarly as for Mono on Linux, we load symbols for dlopen and dlsym from the "libcoreclr.so", to avoid the
  262. /// dependency on libc-dev Linux.
  263. /// </summary>
  264. private static class CoreCLR
  265. {
  266. // Custom resolver to support true single-file apps
  267. // (those which run directly from bundle; in-memory).
  268. // -1 on Unix means self-referencing binary (libcoreclr.so)
  269. // 0 means fallback to CoreCLR's internal resolution
  270. // Note: meaning of -1 stay the same even for non-single-file form factors.
  271. static CoreCLR ()
  272. {
  273. NativeLibrary.SetDllImportResolver (
  274. typeof (CoreCLR).Assembly,
  275. (libraryName, assembly, searchPath) =>
  276. libraryName == "libcoreclr.so" ? -1 : nint.Zero
  277. );
  278. }
  279. [DllImport ("libcoreclr.so")]
  280. internal static extern nint dlopen (string filename, int flags);
  281. [DllImport ("libcoreclr.so")]
  282. internal static extern nint dlsym (nint handle, string symbol);
  283. }
  284. }