Imported Upstream version 1.41.0
[platform/upstream/grpc.git] / src / csharp / Grpc.Core / Internal / UnmanagedLibrary.cs
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
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>
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>
133         private static IntPtr PlatformSpecificLoadLibrary(string libraryPath, out string errorMsg)
134         {
135             if (PlatformApis.IsWindows)
136             {
137                 errorMsg = null;
138                 var handle = Windows.LoadLibrary(libraryPath);
139                 if (handle == IntPtr.Zero)
140                 {
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)
145                     {
146                         errorMsg += ": The specified module could not be found.";
147                     }
148                 }
149                 return handle;
150             }
151             if (PlatformApis.IsLinux)
152             {
153                 if (PlatformApis.IsMono)
154                 {
155                     return LoadLibraryPosix(Mono.dlopen, Mono.dlerror, libraryPath, out errorMsg);
156                 }
157                 if (PlatformApis.IsNetCore)
158                 {
159                     return LoadLibraryPosix(CoreCLR.dlopen, CoreCLR.dlerror, libraryPath, out errorMsg);
160                 }
161                 return LoadLibraryPosix(Linux.dlopen, Linux.dlerror, libraryPath, out errorMsg);
162             }
163             if (PlatformApis.IsMacOSX)
164             {
165                 return LoadLibraryPosix(MacOSX.dlopen, MacOSX.dlerror, libraryPath, out errorMsg);
166             }
167             throw new InvalidOperationException("Unsupported platform.");
168         }
169
170         private static IntPtr LoadLibraryPosix(Func<string, int, IntPtr> dlopenFunc, Func<IntPtr> dlerrorFunc, string libraryPath, out string errorMsg)
171         {
172             errorMsg = null;
173             IntPtr ret = dlopenFunc(libraryPath, RTLD_GLOBAL + RTLD_LAZY);
174             if (ret == IntPtr.Zero)
175             {
176                 errorMsg = Marshal.PtrToStringAnsi(dlerrorFunc());
177             }
178             return ret;
179         }
180
181         private static string FirstValidLibraryPath(string[] libraryPathAlternatives)
182         {
183             GrpcPreconditions.CheckArgument(libraryPathAlternatives.Length > 0, "libraryPathAlternatives cannot be empty.");
184             foreach (var path in libraryPathAlternatives)
185             {
186                 if (File.Exists(path))
187                 {
188                     return path;
189                 }
190             }
191             throw new FileNotFoundException(
192                 String.Format("Error loading native library. Not found in any of the possible locations: {0}",
193                     string.Join(",", libraryPathAlternatives)));
194         }
195
196         private static class Windows
197         {
198             [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
199             internal static extern IntPtr LoadLibrary(string filename);
200
201             [DllImport("kernel32.dll")]
202             internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
203         }
204
205         private static class Linux
206         {
207             [DllImport("libdl.so")]
208             internal static extern IntPtr dlopen(string filename, int flags);
209
210             [DllImport("libdl.so")]
211             internal static extern IntPtr dlerror();
212
213             [DllImport("libdl.so")]
214             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
215         }
216
217         private static class MacOSX
218         {
219             [DllImport("libSystem.dylib")]
220             internal static extern IntPtr dlopen(string filename, int flags);
221
222             [DllImport("libSystem.dylib")]
223             internal static extern IntPtr dlerror();
224
225             [DllImport("libSystem.dylib")]
226             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
227         }
228
229         /// <summary>
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.
235         /// </summary>
236         private static class Mono
237         {
238             [DllImport("__Internal")]
239             internal static extern IntPtr dlopen(string filename, int flags);
240
241             [DllImport("__Internal")]
242             internal static extern IntPtr dlerror();
243
244             [DllImport("__Internal")]
245             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
246         }
247
248         /// <summary>
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.
252         /// </summary>
253         private static class CoreCLR
254         {
255             [DllImport("libcoreclr.so")]
256             internal static extern IntPtr dlopen(string filename, int flags);
257
258             [DllImport("libcoreclr.so")]
259             internal static extern IntPtr dlerror();
260
261             [DllImport("libcoreclr.so")]
262             internal static extern IntPtr dlsym(IntPtr handle, string symbol);
263         }
264     }
265 }