Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 62 additions & 19 deletions src/csharp/Grpc.Core/Internal/NativeExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,25 @@ public NativeMethods NativeMethods
/// <summary>
/// Detects which configuration of native extension to load and load it.
/// </summary>
private static UnmanagedLibrary LoadUnmanagedLibrary()
private static NativeMethods LoadNativeMethodsLegacyNetFramework()
{
// 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());
var assemblyDirectory = GetAssemblyDirectory();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this method should not be needed with your new fix.


// With "classic" 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 netcoreappX.Y, 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", GetRuntimeIdString());
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);
string[] paths = new[] { classicPath };

// TODO(jtattermusch): the UnmanagedLibrary mechanism for loading the native extension while avoiding
// direct use of DllImport is quite complicated and is currently only needed to cover some niche scenarios
// (such legacy .NET Framework projects that use assembly shadowing) - everything else can be covered
// by using the [DllImport]. We should investigate the possibility of eliminating UnmanagedLibrary completely
// in the future.
return new NativeMethods(new UnmanagedLibrary(paths));
}

/// <summary>
Expand All @@ -117,7 +114,43 @@ private static NativeMethods LoadNativeMethods()
{
return LoadNativeMethodsXamarin();
}
return new NativeMethods(LoadUnmanagedLibrary());
if (PlatformApis.IsNetCore)
{
// On .NET Core, native libraries are a supported feature and the SDK makes
// sure that the native library is made available in the right location and that
// they will be discoverable by the [DllImport] default loading mechanism,
// even in some of the more exotic situations such as single file apps.
//
// While in theory, we could just [DllImport("grpc_csharp_ext")] for all the platforms
// and operating systems, the native libraries in the nuget package
// need to be laid out in a way that still allows things to work well under
// the legacy .NET Framework (where native libraries are a concept unknown to the runtime).
// Therefore, we use several flavors of the DllImport attribute
// (e.g. the ".x86" vs ".x64" suffix) and we choose the one we want at runtime.
// The classes with the list of DllImport'd methods are code generated,
// so having more than just one doesn't really bother us.

// on Windows, the DllImport("grpc_csharp_ext.x64") doesn't work for some reason,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was bug dotnet/coreclr#17505 . It is fixed in .NET Core 3.1, but I assume you still want this to work on .NET Core 2.1.

// but DllImport("grpc_csharp_ext.x64.dll") does, so we need a special case for that.
bool useDllSuffix = PlatformApis.IsWindows;
if (PlatformApis.Is64Bit)
{
if (useDllSuffix)
{
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib_x64_dll());
}
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib_x64());
}
else
{
if (useDllSuffix)
{
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib_x86_dll());
}
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib_x86());
}
}
return LoadNativeMethodsLegacyNetFramework();
}

/// <summary>
Expand All @@ -139,6 +172,10 @@ private static NativeMethods LoadNativeMethodsUnity()

/// <summary>
/// Return native method delegates when running on the Xamarin platform.
/// On Xamarin, the standard <c>[DllImport]</c> loading logic just works
/// as the native library metadata is provided by the <c>AndroidNativeLibrary</c> or
/// <c>NativeReference</c> items in the Xamarin projects (injected automatically
/// by the Grpc.Core.Xamarin nuget).
/// WARNING: Xamarin support is experimental and work-in-progress. Don't expect it to work.
/// </summary>
private static NativeMethods LoadNativeMethodsXamarin()
Expand All @@ -147,17 +184,23 @@ private static NativeMethods LoadNativeMethodsXamarin()
{
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib());
}
// not tested yet
return new NativeMethods(new NativeMethods.DllImportsFromStaticLib());
}

private static string GetAssemblyPath()
private static string GetAssemblyDirectory()
{
var assembly = typeof(NativeExtension).GetTypeInfo().Assembly;
#if NETSTANDARD
// 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;
var assemblyLocation = assembly.Location;
if (!string.IsNullOrEmpty(assemblyLocation))
{
return Path.GetDirectoryName(assemblyLocation);
}
// In .NET5 single-file deployments, assembly.Location won't be available
// Also see https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file#other-considerations
return AppContext.BaseDirectory;
#else
// If assembly is shadowed (e.g. in a webapp), EscapedCodeBase is pointing
// to the original location of the assembly, and Location is pointing
Expand All @@ -167,9 +210,9 @@ private static string GetAssemblyPath()
var escapedCodeBase = assembly.EscapedCodeBase;
if (IsFileUri(escapedCodeBase))
{
return new Uri(escapedCodeBase).LocalPath;
return Path.GetDirectoryName(new Uri(escapedCodeBase).LocalPath);
}
return assembly.Location;
return Path.GetDirectoryName(assembly.Location);
#endif
}

Expand Down
Loading