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 23 using Grpc.Core.Logging; 24 25 namespace Grpc.Core.Internal 26 { 27 /// <summary> 28 /// Takes care of loading C# native extension and provides access to PInvoke calls the library exports. 29 /// </summary> 30 internal sealed class NativeExtension 31 { 32 static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<NativeExtension>(); 33 static readonly object staticLock = new object(); 34 static volatile NativeExtension instance; 35 36 readonly NativeMethods nativeMethods; 37 NativeExtension()38 private NativeExtension() 39 { 40 this.nativeMethods = LoadNativeMethods(); 41 42 // Redirect the native logs as the very first thing after loading the native extension 43 // to make sure we don't lose any logs. 44 NativeLogRedirector.Redirect(this.nativeMethods); 45 46 // Initialize 47 NativeCallbackDispatcher.Init(this.nativeMethods); 48 49 DefaultSslRootsOverride.Override(this.nativeMethods); 50 51 Logger.Debug("gRPC native library loaded successfully."); 52 } 53 54 /// <summary> 55 /// Gets singleton instance of this class. 56 /// The native extension is loaded when called for the first time. 57 /// </summary> Get()58 public static NativeExtension Get() 59 { 60 if (instance == null) 61 { 62 lock (staticLock) 63 { 64 if (instance == null) { 65 instance = new NativeExtension(); 66 } 67 } 68 } 69 return instance; 70 } 71 72 /// <summary> 73 /// Provides access to the exported native methods. 74 /// </summary> 75 public NativeMethods NativeMethods 76 { 77 get { return this.nativeMethods; } 78 } 79 80 /// <summary> 81 /// Detects which configuration of native extension to load and load it. 82 /// </summary> LoadUnmanagedLibrary()83 private static UnmanagedLibrary LoadUnmanagedLibrary() 84 { 85 // TODO: allow customizing path to native extension (possibly through exposing a GrpcEnvironment property). 86 // See https://github.com/grpc/grpc/pull/7303 for one option. 87 var assemblyDirectory = Path.GetDirectoryName(GetAssemblyPath()); 88 89 // With "classic" VS projects, the native libraries get copied using a .targets rule to the build output folder 90 // alongside the compiled assembly. 91 // With dotnet cli projects targeting net45 framework, the native libraries (just the required ones) 92 // are similarly copied to the built output folder, through the magic of Microsoft.NETCore.Platforms. 93 var classicPath = Path.Combine(assemblyDirectory, GetNativeLibraryFilename()); 94 95 // With dotnet cli project targeting netcoreappX.Y, projects will use Grpc.Core assembly directly in the location where it got restored 96 // by nuget. We locate the native libraries based on known structure of Grpc.Core nuget package. 97 // When "dotnet publish" is used, the runtimes directory is copied next to the published assemblies. 98 string runtimesDirectory = string.Format("runtimes/{0}/native", GetPlatformString()); 99 var netCorePublishedAppStylePath = Path.Combine(assemblyDirectory, runtimesDirectory, GetNativeLibraryFilename()); 100 var netCoreAppStylePath = Path.Combine(assemblyDirectory, "../..", runtimesDirectory, GetNativeLibraryFilename()); 101 102 // Look for the native library in all possible locations in given order. 103 string[] paths = new[] { classicPath, netCorePublishedAppStylePath, netCoreAppStylePath}; 104 return new UnmanagedLibrary(paths); 105 } 106 107 /// <summary> 108 /// Loads native extension and return native methods delegates. 109 /// </summary> LoadNativeMethods()110 private static NativeMethods LoadNativeMethods() 111 { 112 if (PlatformApis.IsUnity) 113 { 114 return LoadNativeMethodsUnity(); 115 } 116 if (PlatformApis.IsXamarin) 117 { 118 return LoadNativeMethodsXamarin(); 119 } 120 return new NativeMethods(LoadUnmanagedLibrary()); 121 } 122 123 /// <summary> 124 /// Return native method delegates when running on Unity platform. 125 /// Unity does not use standard NuGet packages and the native library is treated 126 /// there as a "native plugin" which is (provided it has the right metadata) 127 /// automatically made available to <c>[DllImport]</c> loading logic. 128 /// WARNING: Unity support is experimental and work-in-progress. Don't expect it to work. 129 /// </summary> LoadNativeMethodsUnity()130 private static NativeMethods LoadNativeMethodsUnity() 131 { 132 switch (PlatformApis.GetUnityRuntimePlatform()) 133 { 134 case "IPhonePlayer": 135 return new NativeMethods(new NativeMethods.DllImportsFromStaticLib()); 136 default: 137 // most other platforms load unity plugins as a shared library 138 return new NativeMethods(new NativeMethods.DllImportsFromSharedLib()); 139 } 140 } 141 142 /// <summary> 143 /// Return native method delegates when running on the Xamarin platform. 144 /// WARNING: Xamarin support is experimental and work-in-progress. Don't expect it to work. 145 /// </summary> LoadNativeMethodsXamarin()146 private static NativeMethods LoadNativeMethodsXamarin() 147 { 148 if (PlatformApis.IsXamarinAndroid) 149 { 150 return new NativeMethods(new NativeMethods.DllImportsFromSharedLib()); 151 } 152 // not tested yet 153 return new NativeMethods(new NativeMethods.DllImportsFromStaticLib()); 154 } 155 GetAssemblyPath()156 private static string GetAssemblyPath() 157 { 158 var assembly = typeof(NativeExtension).GetTypeInfo().Assembly; 159 #if NETSTANDARD1_5 || NETSTANDARD2_0 160 // Assembly.EscapedCodeBase does not exist under CoreCLR, but assemblies imported from a nuget package 161 // don't seem to be shadowed by DNX-based projects at all. 162 return assembly.Location; 163 #else 164 // If assembly is shadowed (e.g. in a webapp), EscapedCodeBase is pointing 165 // to the original location of the assembly, and Location is pointing 166 // to the shadow copy. We care about the original location because 167 // the native dlls don't get shadowed. 168 169 var escapedCodeBase = assembly.EscapedCodeBase; 170 if (IsFileUri(escapedCodeBase)) 171 { 172 return new Uri(escapedCodeBase).LocalPath; 173 } 174 return assembly.Location; 175 #endif 176 } 177 178 #if !NETSTANDARD1_5 && !NETSTANDARD2_0 IsFileUri(string uri)179 private static bool IsFileUri(string uri) 180 { 181 return uri.ToLowerInvariant().StartsWith(Uri.UriSchemeFile); 182 } 183 #endif 184 GetPlatformString()185 private static string GetPlatformString() 186 { 187 if (PlatformApis.IsWindows) 188 { 189 return "win"; 190 } 191 if (PlatformApis.IsLinux) 192 { 193 return "linux"; 194 } 195 if (PlatformApis.IsMacOSX) 196 { 197 return "osx"; 198 } 199 throw new InvalidOperationException("Unsupported platform."); 200 } 201 202 // Currently, only Intel platform is supported. GetArchitectureString()203 private static string GetArchitectureString() 204 { 205 if (PlatformApis.Is64Bit) 206 { 207 return "x64"; 208 } 209 else 210 { 211 return "x86"; 212 } 213 } 214 215 // platform specific file name of the extension library GetNativeLibraryFilename()216 private static string GetNativeLibraryFilename() 217 { 218 string architecture = GetArchitectureString(); 219 if (PlatformApis.IsWindows) 220 { 221 return string.Format("grpc_csharp_ext.{0}.dll", architecture); 222 } 223 if (PlatformApis.IsLinux) 224 { 225 return string.Format("libgrpc_csharp_ext.{0}.so", architecture); 226 } 227 if (PlatformApis.IsMacOSX) 228 { 229 return string.Format("libgrpc_csharp_ext.{0}.dylib", architecture); 230 } 231 throw new InvalidOperationException("Unsupported platform."); 232 } 233 } 234 } 235