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