Add native binary resolver (#11032)

This commit is contained in:
Ilya
2019-12-10 15:55:40 -08:00
committed by Travis Plunk
parent bc99fbf02a
commit 5df5ac8fab
2 changed files with 156 additions and 11 deletions
@@ -1,19 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#if CORECLR
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.Loader;
using System.Text;
using System.Linq;
namespace System.Management.Automation
{
@@ -46,7 +41,9 @@ namespace System.Management.Automation
lock (s_syncObj)
{
if (Instance != null)
{
throw new InvalidOperationException(SingletonAlreadyInitialized);
}
Instance = new PowerShellAssemblyLoadContext(basePaths);
return Instance;
@@ -93,8 +90,15 @@ namespace System.Management.Automation
_availableDotNetAssemblyNames = new Lazy<HashSet<string>>(
() => new HashSet<string>(_coreClrTypeCatalog.Values, StringComparer.Ordinal));
// LAST: Register 'Resolving' handler on the default load context.
// LAST: Register the 'Resolving' handler and 'ResolvingUnmanagedDll' handler on the default load context.
AssemblyLoadContext.Default.Resolving += Resolve;
// Add last resort native dll resolver.
// Default order:
// 1. System.Runtime.InteropServices.DllImportResolver callbacks
// 2. AssemblyLoadContext.LoadUnmanagedDll()
// 3. AssemblyLoadContext.Default.ResolvingUnmanagedDll handlers
AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeDllHandler;
}
#endregion Constructor
@@ -194,6 +198,50 @@ namespace System.Management.Automation
return null;
}
/// <summary>
/// If a managed dll has native dependencies the handler will try to find these native dlls.
/// 1. Gets the managed.dll location (folder)
/// 2. Based on OS name and architecture name builds subfolder name where it is expected the native dll resides:
/// 3. Loads the native dll
///
/// managed.dll folder
/// |
/// |--- 'win-x64' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-x86' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-arm' subfolder
/// | |--- native.dll
/// |
/// |--- 'win-arm64' subfolder
/// | |--- native.dll
/// |
/// |--- 'linux-x64' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-x86' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-arm' subfolder
/// | |--- native.so
/// |
/// |--- 'linux-arm64' subfolder
/// | |--- native.so
/// |
/// |--- 'osx-x64' subfolder
/// | |--- native.dylib
/// </summary>
internal static IntPtr NativeDllHandler(Assembly assembly, string libraryName)
{
var folder = Path.GetDirectoryName(assembly.Location);
s_nativeDllSubFolder ??= GetNativeDllSubFolderName(out s_nativeDllExtension);
var fullName = Path.Combine(folder, s_nativeDllSubFolder, libraryName) + s_nativeDllExtension;
return NativeLibrary.Load(fullName);
}
#endregion Internal_Methods
#region Private_Methods
@@ -483,6 +531,34 @@ namespace System.Management.Automation
throw new FileNotFoundException(message);
}
private static string s_nativeDllSubFolder;
private static string s_nativeDllExtension;
private static string GetNativeDllSubFolderName(out string ext)
{
string folderName = string.Empty;
ext = string.Empty;
var processArch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
folderName = "win-" + processArch;
ext = ".dll";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
folderName = "linux-" + processArch;
ext = ".so";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
folderName = "osx-x64";
ext = ".dylib";
}
return folderName;
}
#endregion Private_Methods
}
@@ -512,6 +588,3 @@ namespace System.Management.Automation
}
}
}
#endif
@@ -0,0 +1,72 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe "Can load a native assembly" -Tags "CI" {
BeforeAll {
## The assembly files cannot be removed once they are loaded, unless the current PowerShell session exits.
## If we use $TestDrive here, then Pester will try to remove them afterward and result in errors.
$TempPath = [System.IO.Path]::GetTempFileName()
if (Test-Path $TempPath) { Remove-Item -Path $TempPath -Force -Recurse }
New-Item -Path $TempPath -ItemType Directory -Force > $null
$root = Join-Path $TempPath "testDllNativeFolder"
New-Item -Path $root -ItemType Directory -Force > $null
$processArch = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLower()
if ($IsWindows) {
$arch = "win-" + $processArch
$nativeDllName = "nativedll.dll"
$sourceDllName = "hostpolicy.dll"
} elseif ($IsLinux) {
$arch = "linux-" + $processArch
$nativeDllName = "nativedll.so"
$sourceDllName = "libhostpolicy.so"
} elseif ($IsMacOs) {
$arch = "osx-" + $processArch
$nativeDllName = "nativedll.dylib"
$sourceDllName = "libhostpolicy.dylib"
} else {
throw "Unsupported OS"
}
$archFolder = Join-Path $root $arch
New-Item -Path $archFolder -ItemType Directory -Force > $null
#New-Item -Path $archFolder\$nativeDllName -ItemType File -Force > $null
Copy-Item -Path $PSHOME\$sourceDllName -Destination $archFolder\$nativeDllName
$managedDllPath = Join-Path $root managed.dll
$source = @"
using System;
using System.Runtime.InteropServices;
public class TestNativeClass2
{
public static int Add(int a, int b)
{
return (a + b);
}
public static void LoadNative()
{
TestEntry();
}
[DllImport ("nativedll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void TestEntry();
}
"@
Add-Type -OutputAssembly $managedDllPath -TypeDefinition $source
Add-Type -Assembly $managedDllPath
}
It "Can load native dll" {
# Managed dll is loaded
[TestNativeClass2]::Add(1,2) | Should -Be 3
# Native dll is loaded from the same managed dll
{ [TestNativeClass2]::LoadNative() } | Should -Throw -ErrorId "EntryPointNotFoundException"
}
}