#region Copyright notice and license // Copyright 2015 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #endregion using System; using System.IO; using System.Reflection; using Grpc.Core.Logging; namespace Grpc.Core.Internal { /// /// Takes care of loading C# native extension and provides access to PInvoke calls the library exports. /// internal sealed class NativeExtension { static readonly ILogger Logger = GrpcEnvironment.Logger.ForType(); static readonly object staticLock = new object(); static volatile NativeExtension instance; readonly NativeMethods nativeMethods; private NativeExtension() { this.nativeMethods = LoadNativeMethods(); // Redirect the native logs as the very first thing after loading the native extension // to make sure we don't lose any logs. NativeLogRedirector.Redirect(this.nativeMethods); DefaultSslRootsOverride.Override(this.nativeMethods); Logger.Debug("gRPC native library loaded successfully."); } /// /// Gets singleton instance of this class. /// The native extension is loaded when called for the first time. /// public static NativeExtension Get() { if (instance == null) { lock (staticLock) { if (instance == null) { instance = new NativeExtension(); } } } return instance; } /// /// Provides access to the exported native methods. /// public NativeMethods NativeMethods { get { return this.nativeMethods; } } /// /// Detects which configuration of native extension to load and load it. /// private static UnmanagedLibrary LoadUnmanagedLibrary() { // TODO: allow customizing path to native extension (possibly through exposing a GrpcEnvironment property). // See https://github.com/grpc/grpc/pull/7303 for one option. var assemblyDirectory = Path.GetDirectoryName(GetAssemblyPath()); // With old-style VS projects, the native libraries get copied using a .targets rule to the build output folder // alongside the compiled assembly. // With dotnet cli projects targeting net45 framework, the native libraries (just the required ones) // are similarly copied to the built output folder, through the magic of Microsoft.NETCore.Platforms. var classicPath = Path.Combine(assemblyDirectory, GetNativeLibraryFilename()); // With dotnet cli project targeting netcoreapp1.0, projects will use Grpc.Core assembly directly in the location where it got restored // by nuget. We locate the native libraries based on known structure of Grpc.Core nuget package. // When "dotnet publish" is used, the runtimes directory is copied next to the published assemblies. string runtimesDirectory = string.Format("runtimes/{0}/native", GetPlatformString()); var netCorePublishedAppStylePath = Path.Combine(assemblyDirectory, runtimesDirectory, GetNativeLibraryFilename()); var netCoreAppStylePath = Path.Combine(assemblyDirectory, "../..", runtimesDirectory, GetNativeLibraryFilename()); // Look for the native library in all possible locations in given order. string[] paths = new[] { classicPath, netCorePublishedAppStylePath, netCoreAppStylePath}; return new UnmanagedLibrary(paths); } /// /// Loads native extension and return native methods delegates. /// private static NativeMethods LoadNativeMethods() { if (PlatformApis.IsUnity) { return LoadNativeMethodsUnity(); } if (PlatformApis.IsXamarin) { return LoadNativeMethodsXamarin(); } return new NativeMethods(LoadUnmanagedLibrary()); } /// /// Return native method delegates when running on Unity platform. /// Unity does not use standard NuGet packages and the native library is treated /// there as a "native plugin" which is (provided it has the right metadata) /// automatically made available to [DllImport] loading logic. /// WARNING: Unity support is experimental and work-in-progress. Don't expect it to work. /// private static NativeMethods LoadNativeMethodsUnity() { switch (PlatformApis.GetUnityRuntimePlatform()) { case "IPhonePlayer": return new NativeMethods(new NativeMethods.DllImportsFromStaticLib()); default: // most other platforms load unity plugins as a shared library return new NativeMethods(new NativeMethods.DllImportsFromSharedLib()); } } /// /// Return native method delegates when running on the Xamarin platform. /// WARNING: Xamarin support is experimental and work-in-progress. Don't expect it to work. /// private static NativeMethods LoadNativeMethodsXamarin() { if (PlatformApis.IsXamarinAndroid) { return new NativeMethods(new NativeMethods.DllImportsFromSharedLib()); } // not tested yet return new NativeMethods(new NativeMethods.DllImportsFromStaticLib()); } private static string GetAssemblyPath() { var assembly = typeof(NativeExtension).GetTypeInfo().Assembly; #if NETSTANDARD1_5 // Assembly.EscapedCodeBase does not exist under CoreCLR, but assemblies imported from a nuget package // don't seem to be shadowed by DNX-based projects at all. return assembly.Location; #else // If assembly is shadowed (e.g. in a webapp), EscapedCodeBase is pointing // to the original location of the assembly, and Location is pointing // to the shadow copy. We care about the original location because // the native dlls don't get shadowed. var escapedCodeBase = assembly.EscapedCodeBase; if (IsFileUri(escapedCodeBase)) { return new Uri(escapedCodeBase).LocalPath; } return assembly.Location; #endif } #if !NETSTANDARD1_5 private static bool IsFileUri(string uri) { return uri.ToLowerInvariant().StartsWith(Uri.UriSchemeFile); } #endif private static string GetPlatformString() { if (PlatformApis.IsWindows) { return "win"; } if (PlatformApis.IsLinux) { return "linux"; } if (PlatformApis.IsMacOSX) { return "osx"; } throw new InvalidOperationException("Unsupported platform."); } // Currently, only Intel platform is supported. private static string GetArchitectureString() { if (PlatformApis.Is64Bit) { return "x64"; } else { return "x86"; } } // platform specific file name of the extension library private static string GetNativeLibraryFilename() { string architecture = GetArchitectureString(); if (PlatformApis.IsWindows) { return string.Format("grpc_csharp_ext.{0}.dll", architecture); } if (PlatformApis.IsLinux) { return string.Format("libgrpc_csharp_ext.{0}.so", architecture); } if (PlatformApis.IsMacOSX) { return string.Format("libgrpc_csharp_ext.{0}.dylib", architecture); } throw new InvalidOperationException("Unsupported platform."); } } }