• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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