#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.");
}
}
}