• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 
3 // Copyright 2015 gRPC authors.
4 //
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
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 
17 #endregion
18 
19 using System;
20 using System.IO;
21 using System.Reflection;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 
25 using Grpc.Core.Logging;
26 using Grpc.Core.Utils;
27 
28 namespace Grpc.Core.Internal
29 {
30     /// <summary>
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.
36     /// </summary>
37     internal class UnmanagedLibrary
38     {
39         static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<UnmanagedLibrary>();
40 
41         // flags for dlopen
42         const int RTLD_LAZY = 1;
43         const int RTLD_GLOBAL = 8;
44 
45         readonly string libraryPath;
46         readonly IntPtr handle;
47 
UnmanagedLibrary(string[] libraryPathAlternatives)48         public UnmanagedLibrary(string[] libraryPathAlternatives)
49         {
50             this.libraryPath = FirstValidLibraryPath(libraryPathAlternatives);
51 
52             Logger.Debug("Attempting to load native library \"{0}\"", this.libraryPath);
53 
54             this.handle = PlatformSpecificLoadLibrary(this.libraryPath, out string loadLibraryErrorDetail);
55 
56             if (this.handle == IntPtr.Zero)
57             {
58                 throw new IOException(string.Format("Error loading native library \"{0}\". {1}",
59                                                     this.libraryPath, loadLibraryErrorDetail));
60             }
61         }
62 
63         /// <summary>
64         /// Loads symbol in a platform specific way.
65         /// </summary>
66         /// <param name="symbolName"></param>
67         /// <returns></returns>
LoadSymbol(string symbolName)68         private IntPtr LoadSymbol(string symbolName)
69         {
70             if (PlatformApis.IsWindows)
71             {
72                 // See http://stackoverflow.com/questions/10473310 for background on this.
73                 if (PlatformApis.Is64Bit)
74                 {
75                     return Windows.GetProcAddress(this.handle, symbolName);
76                 }
77                 else
78                 {
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)
85                     {
86                         IntPtr candidate = Windows.GetProcAddress(this.handle, symbolName + stackSize);
87                         if (candidate != IntPtr.Zero)
88                         {
89                             return candidate;
90                         }
91                     }
92                     // Fail.
93                     return IntPtr.Zero;
94                 }
95             }
96             if (PlatformApis.IsLinux)
97             {
98                 if (PlatformApis.IsMono)
99                 {
100                     return Mono.dlsym(this.handle, symbolName);
101                 }
102                 if (PlatformApis.IsNetCore)
103                 {
104                     return CoreCLR.dlsym(this.handle, symbolName);
105                 }
106                 return Linux.dlsym(this.handle, symbolName);
107             }
108             if (PlatformApis.IsMacOSX)
109             {
110                 return MacOSX.dlsym(this.handle, symbolName);
111             }
112             throw new InvalidOperationException("Unsupported platform.");
113         }
114 
115         public T GetNativeMethodDelegate<T>(string methodName)
116             where T : class
117         {
118             var ptr = LoadSymbol(methodName);
119             if (ptr == IntPtr.Zero)
120             {
121                 throw new MissingMethodException(string.Format("The native method \"{0}\" does not exist", methodName));
122             }
123 #if NETSTANDARD
124             return Marshal.GetDelegateForFunctionPointer<T>(ptr);  // non-generic version is obsolete
125 #else
126             return Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)) as T;  // generic version not available in .NET45
127 #endif
128         }
129 
130         /// <summary>
131         /// Loads library in a platform specific way.
132         /// </summary>
PlatformSpecificLoadLibrary(string libraryPath, out string errorMsg)133         private static IntPtr PlatformSpecificLoadLibrary(string libraryPath, out string errorMsg)
134         {
135             if (PlatformApis.IsWindows)
136             {
137                 // TODO(jtattermusch): populate the error on Windows
138                 errorMsg = null;
139                 return Windows.LoadLibrary(libraryPath);
140             }
141             if (PlatformApis.IsLinux)
142             {
143                 if (PlatformApis.IsMono)
144                 {
145                     return LoadLibraryPosix(Mono.dlopen, Mono.dlerror, libraryPath, out errorMsg);
146                 }
147                 if (PlatformApis.IsNetCore)
148                 {
149                     return LoadLibraryPosix(CoreCLR.dlopen, CoreCLR.dlerror, libraryPath, out errorMsg);
150                 }
151                 return LoadLibraryPosix(Linux.dlopen, Linux.dlerror, libraryPath, out errorMsg);
152             }
153             if (PlatformApis.IsMacOSX)
154             {
155                 return LoadLibraryPosix(MacOSX.dlopen, MacOSX.dlerror, libraryPath, out errorMsg);
156             }
157             throw new InvalidOperationException("Unsupported platform.");
158         }
159 
LoadLibraryPosix(Func<string, int, IntPtr> dlopenFunc, Func<IntPtr> dlerrorFunc, string libraryPath, out string errorMsg)160         private static IntPtr LoadLibraryPosix(Func<string, int, IntPtr> dlopenFunc, Func<IntPtr> dlerrorFunc, string libraryPath, out string errorMsg)
161         {
162             errorMsg = null;
163             IntPtr ret = dlopenFunc(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
164             if (ret == IntPtr.Zero)
165             {
166                 errorMsg = Marshal.PtrToStringAnsi(dlerrorFunc());
167             }
168             return ret;
169         }
170 
FirstValidLibraryPath(string[] libraryPathAlternatives)171         private static string FirstValidLibraryPath(string[] libraryPathAlternatives)
172         {
173             GrpcPreconditions.CheckArgument(libraryPathAlternatives.Length > 0, "libraryPathAlternatives cannot be empty.");
174             foreach (var path in libraryPathAlternatives)
175             {
176                 if (File.Exists(path))
177                 {
178                     return path;
179                 }
180             }
181             throw new FileNotFoundException(
182                 String.Format("Error loading native library. Not found in any of the possible locations: {0}",
183                     string.Join(",", libraryPathAlternatives)));
184         }
185 
186         private static class Windows
187         {
188             [DllImport("kernel32.dll")]
LoadLibrary(string filename)189             internal static extern IntPtr LoadLibrary(string filename);
190 
191             [DllImport("kernel32.dll")]
GetProcAddress(IntPtr hModule, string procName)192             internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
193         }
194 
195         private static class Linux
196         {
197             [DllImport("libdl.so")]
dlopen(string filename, int flags)198             internal static extern IntPtr dlopen(string filename, int flags);
199 
200             [DllImport("libdl.so")]
dlerror()201             internal static extern IntPtr dlerror();
202 
203             [DllImport("libdl.so")]
dlsym(IntPtr handle, string symbol)204             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
205         }
206 
207         private static class MacOSX
208         {
209             [DllImport("libSystem.dylib")]
dlopen(string filename, int flags)210             internal static extern IntPtr dlopen(string filename, int flags);
211 
212             [DllImport("libSystem.dylib")]
dlerror()213             internal static extern IntPtr dlerror();
214 
215             [DllImport("libSystem.dylib")]
dlsym(IntPtr handle, string symbol)216             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
217         }
218 
219         /// <summary>
220         /// On Linux systems, using dlopen and dlsym results in
221         /// DllNotFoundException("libdl.so not found") if libc6-dev
222         /// is not installed. As a workaround, we load symbols for
223         /// dlopen and dlsym from the current process as on Linux
224         /// Mono sure is linked against these symbols.
225         /// </summary>
226         private static class Mono
227         {
228             [DllImport("__Internal")]
dlopen(string filename, int flags)229             internal static extern IntPtr dlopen(string filename, int flags);
230 
231             [DllImport("__Internal")]
dlerror()232             internal static extern IntPtr dlerror();
233 
234             [DllImport("__Internal")]
dlsym(IntPtr handle, string symbol)235             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
236         }
237 
238         /// <summary>
239         /// Similarly as for Mono on Linux, we load symbols for
240         /// dlopen and dlsym from the "libcoreclr.so",
241         /// to avoid the dependency on libc-dev Linux.
242         /// </summary>
243         private static class CoreCLR
244         {
245             [DllImport("libcoreclr.so")]
dlopen(string filename, int flags)246             internal static extern IntPtr dlopen(string filename, int flags);
247 
248             [DllImport("libcoreclr.so")]
dlerror()249             internal static extern IntPtr dlerror();
250 
251             [DllImport("libcoreclr.so")]
dlsym(IntPtr handle, string symbol)252             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
253         }
254     }
255 }
256