-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add LoadAssemblyFromNativeMemory function to load assemblies in memory from a native PowerShell host
#14652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add LoadAssemblyFromNativeMemory function to load assemblies in memory from a native PowerShell host
#14652
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ffac56c
Add NativeHost.LoadAssemblyData helper function to allow hosting Powe…
mamoreau-devolutions 0458635
Fix code style issues reported in CI
mamoreau-devolutions 8ddec49
make sure MemoryStream is disposed of in all cases
mamoreau-devolutions 9100cc6
rename and move native host assembly loader helper function to SMA
mamoreau-devolutions 3577f10
use UnmanagedMemoryStream in LoadAssemblyFromMemory + mark PowerShell…
mamoreau-devolutions 39b1c78
avoid temporary variable for IntPtr to Byte* cast in LoadAssemblyFrom…
mamoreau-devolutions 2a2efcf
Add a basic test for the new 'LoadAssemblyFromNativeMemory' API
daxian-dbw b441aa2
Address feedback
daxian-dbw 404d8c5
Address a CodeFactor issue
daxian-dbw 97a30b1
Define the experimental feature name
daxian-dbw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
| using System.Runtime.InteropServices; | ||
| using System.Runtime.Loader; | ||
| using System.Management.Automation; | ||
| using Xunit; | ||
|
|
||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.Emit; | ||
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace PSTests.Sequential | ||
| { | ||
| public static class NativeInterop | ||
| { | ||
| [Fact] | ||
| public static void TestLoadNativeInMemoryAssembly() | ||
| { | ||
| string tempDir = Path.Combine(Path.GetTempPath(), "TestLoadNativeInMemoryAssembly"); | ||
| string testDll = Path.Combine(tempDir, "test.dll"); | ||
|
|
||
| if (!File.Exists(testDll)) | ||
| { | ||
| Directory.CreateDirectory(tempDir); | ||
| bool result = CreateTestDll(testDll); | ||
| Assert.True(result, "The call to 'CreateTestDll' should be successful and return true."); | ||
| Assert.True(File.Exists(testDll), "The test assembly should be created."); | ||
| } | ||
|
|
||
| var asmName = AssemblyName.GetAssemblyName(testDll); | ||
| string asmFullName = SearchAssembly(asmName.Name); | ||
| Assert.Null(asmFullName); | ||
|
|
||
| unsafe | ||
| { | ||
| int ret = LoadAssemblyTest(testDll); | ||
| Assert.Equal(0, ret); | ||
| } | ||
|
|
||
| asmFullName = SearchAssembly(asmName.Name); | ||
| Assert.Equal(asmName.FullName, asmFullName); | ||
| } | ||
|
|
||
| private static unsafe int LoadAssemblyTest(string assemblyPath) | ||
| { | ||
| // The 'LoadAssemblyFromNativeMemory' method is annotated with 'UnmanagedCallersOnly' attribute, | ||
| // so we have to use the 'unmanaged' function pointer to invoke it. | ||
| delegate* unmanaged<IntPtr, int, int> funcPtr = &PowerShellUnsafeAssemblyLoad.LoadAssemblyFromNativeMemory; | ||
|
|
||
| int length = 0; | ||
| IntPtr nativeMem = IntPtr.Zero; | ||
|
|
||
| try | ||
| { | ||
| using (var fileStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read)) | ||
| { | ||
| length = (int)fileStream.Length; | ||
| nativeMem = Marshal.AllocHGlobal(length); | ||
|
|
||
| using var unmanagedStream = new UnmanagedMemoryStream((byte*)nativeMem, length, length, FileAccess.Write); | ||
| fileStream.CopyTo(unmanagedStream); | ||
| } | ||
|
|
||
| // Call the function pointer. | ||
| return funcPtr(nativeMem, length); | ||
| } | ||
| finally | ||
| { | ||
| // Free the native memory | ||
| Marshal.FreeHGlobal(nativeMem); | ||
| } | ||
| } | ||
|
|
||
| private static string SearchAssembly(string assemblyName) | ||
| { | ||
| Assembly asm = AssemblyLoadContext.Default.Assemblies.FirstOrDefault( | ||
| assembly => assembly.FullName.StartsWith(assemblyName, StringComparison.OrdinalIgnoreCase)); | ||
|
|
||
| return asm?.FullName; | ||
| } | ||
|
|
||
| private static bool CreateTestDll(string dllPath) | ||
| { | ||
| var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); | ||
| var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); | ||
|
|
||
| List<SyntaxTree> syntaxTrees = new(); | ||
| SourceText sourceText = SourceText.From("public class Utt { }"); | ||
| syntaxTrees.Add(CSharpSyntaxTree.ParseText(sourceText, parseOptions)); | ||
|
|
||
| var refs = new List<PortableExecutableReference> { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }; | ||
| Compilation compilation = CSharpCompilation.Create( | ||
| Path.GetRandomFileName(), | ||
| syntaxTrees: syntaxTrees, | ||
| references: refs, | ||
| options: compilationOptions); | ||
|
|
||
| using var fs = new FileStream(dllPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); | ||
| EmitResult emitResult = compilation.Emit(peStream: fs, options: null); | ||
| return emitResult.Success; | ||
| } | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.