1 #region Copyright notice and license
3 // Copyright 2015 gRPC authors.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
21 using System.Reflection;
22 using System.Runtime.InteropServices;
23 using System.Threading;
25 using Grpc.Core.Logging;
26 using Grpc.Core.Utils;
28 namespace Grpc.Core.Internal
31 /// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
32 /// First, the native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows).
33 /// dlsym or GetProcAddress are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c>
34 /// transforms the addresses into delegates to native methods.
35 /// See http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
37 internal class UnmanagedLibrary
39 static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<UnmanagedLibrary>();
42 const int RTLD_LAZY = 1;
43 const int RTLD_GLOBAL = 8;
45 readonly string libraryPath;
46 readonly IntPtr handle;
48 public UnmanagedLibrary(string[] libraryPathAlternatives)
50 this.libraryPath = FirstValidLibraryPath(libraryPathAlternatives);
52 Logger.Debug("Attempting to load native library \"{0}\"", this.libraryPath);
54 this.handle = PlatformSpecificLoadLibrary(this.libraryPath, out string loadLibraryErrorDetail);
56 if (this.handle == IntPtr.Zero)
58 throw new IOException(string.Format("Error loading native library \"{0}\". {1}",
59 this.libraryPath, loadLibraryErrorDetail));
64 /// Loads symbol in a platform specific way.
66 /// <param name="symbolName"></param>
67 /// <returns></returns>
68 private IntPtr LoadSymbol(string symbolName)
70 if (PlatformApis.IsWindows)
72 // See http://stackoverflow.com/questions/10473310 for background on this.
73 if (PlatformApis.Is64Bit)
75 return Windows.GetProcAddress(this.handle, symbolName);
79 // Yes, we could potentially predict the size... but it's a lot simpler to just try
80 // all the candidates. Most functions have a suffix of @0, @4 or @8 so we won't be trying
81 // many options - and if it takes a little bit longer to fail if we've really got the wrong
82 // library, that's not a big problem. This is only called once per function in the native library.
83 symbolName = "_" + symbolName + "@";
84 for (int stackSize = 0; stackSize < 128; stackSize += 4)
86 IntPtr candidate = Windows.GetProcAddress(this.handle, symbolName + stackSize);
87 if (candidate != IntPtr.Zero)
96 if (PlatformApis.IsLinux)
98 if (PlatformApis.IsMono)
100 return Mono.dlsym(this.handle, symbolName);
102 if (PlatformApis.IsNetCore)
104 return CoreCLR.dlsym(this.handle, symbolName);
106 return Linux.dlsym(this.handle, symbolName);
108 if (PlatformApis.IsMacOSX)
110 return MacOSX.dlsym(this.handle, symbolName);
112 throw new InvalidOperationException("Unsupported platform.");
115 public T GetNativeMethodDelegate<T>(string methodName)
118 var ptr = LoadSymbol(methodName);
119 if (ptr == IntPtr.Zero)
121 throw new MissingMethodException(string.Format("The native method \"{0}\" does not exist", methodName));
124 return Marshal.GetDelegateForFunctionPointer<T>(ptr); // non-generic version is obsolete
126 return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T; // generic version not available in .NET45
131 /// Loads library in a platform specific way.
133 private static IntPtr PlatformSpecificLoadLibrary(string libraryPath, out string errorMsg)
135 if (PlatformApis.IsWindows)
138 var handle = Windows.LoadLibrary(libraryPath);
139 if (handle == IntPtr.Zero)
141 int win32Error = Marshal.GetLastWin32Error();
142 errorMsg = $"LoadLibrary failed with error {win32Error}";
143 // add extra info for the most common error ERROR_MOD_NOT_FOUND
144 if (win32Error == 126)
146 errorMsg += ": The specified module could not be found.";
151 if (PlatformApis.IsLinux)
153 if (PlatformApis.IsMono)
155 return LoadLibraryPosix(Mono.dlopen, Mono.dlerror, libraryPath, out errorMsg);
157 if (PlatformApis.IsNetCore)
159 return LoadLibraryPosix(CoreCLR.dlopen, CoreCLR.dlerror, libraryPath, out errorMsg);
161 return LoadLibraryPosix(Linux.dlopen, Linux.dlerror, libraryPath, out errorMsg);
163 if (PlatformApis.IsMacOSX)
165 return LoadLibraryPosix(MacOSX.dlopen, MacOSX.dlerror, libraryPath, out errorMsg);
167 throw new InvalidOperationException("Unsupported platform.");
170 private static IntPtr LoadLibraryPosix(Func<string, int, IntPtr> dlopenFunc, Func<IntPtr> dlerrorFunc, string libraryPath, out string errorMsg)
173 IntPtr ret = dlopenFunc(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
174 if (ret == IntPtr.Zero)
176 errorMsg = Marshal.PtrToStringAnsi(dlerrorFunc());
181 private static string FirstValidLibraryPath(string[] libraryPathAlternatives)
183 GrpcPreconditions.CheckArgument(libraryPathAlternatives.Length > 0, "libraryPathAlternatives cannot be empty.");
184 foreach (var path in libraryPathAlternatives)
186 if (File.Exists(path))
191 throw new FileNotFoundException(
192 String.Format("Error loading native library. Not found in any of the possible locations: {0}",
193 string.Join(",", libraryPathAlternatives)));
196 private static class Windows
198 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
199 internal static extern IntPtr LoadLibrary(string filename);
201 [DllImport("kernel32.dll")]
202 internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
205 private static class Linux
207 [DllImport("libdl.so")]
208 internal static extern IntPtr dlopen(string filename, int flags);
210 [DllImport("libdl.so")]
211 internal static extern IntPtr dlerror();
213 [DllImport("libdl.so")]
214 internal static extern IntPtr dlsym(IntPtr handle, string symbol);
217 private static class MacOSX
219 [DllImport("libSystem.dylib")]
220 internal static extern IntPtr dlopen(string filename, int flags);
222 [DllImport("libSystem.dylib")]
223 internal static extern IntPtr dlerror();
225 [DllImport("libSystem.dylib")]
226 internal static extern IntPtr dlsym(IntPtr handle, string symbol);
230 /// On Linux systems, using dlopen and dlsym results in
231 /// DllNotFoundException("libdl.so not found") if libc6-dev
232 /// is not installed. As a workaround, we load symbols for
233 /// dlopen and dlsym from the current process as on Linux
234 /// Mono sure is linked against these symbols.
236 private static class Mono
238 [DllImport("__Internal")]
239 internal static extern IntPtr dlopen(string filename, int flags);
241 [DllImport("__Internal")]
242 internal static extern IntPtr dlerror();
244 [DllImport("__Internal")]
245 internal static extern IntPtr dlsym(IntPtr handle, string symbol);
249 /// Similarly as for Mono on Linux, we load symbols for
250 /// dlopen and dlsym from the "libcoreclr.so",
251 /// to avoid the dependency on libc-dev Linux.
253 private static class CoreCLR
255 [DllImport("libcoreclr.so")]
256 internal static extern IntPtr dlopen(string filename, int flags);
258 [DllImport("libcoreclr.so")]
259 internal static extern IntPtr dlerror();
261 [DllImport("libcoreclr.so")]
262 internal static extern IntPtr dlsym(IntPtr handle, string symbol);