UnmanagedLibrary.cs 11 KB

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