mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add native binary resolver (#11032)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user