Skip to content

Commit

Permalink
Implement load_assembly_bytes delegate for hosting API (#84650)
Browse files Browse the repository at this point in the history
  • Loading branch information
elinor-fung committed Apr 13, 2023
1 parent 6875ba0 commit 0ac097f
Show file tree
Hide file tree
Showing 14 changed files with 365 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static Component()
{
Assembly asm = Assembly.GetExecutingAssembly();
Console.WriteLine($"{asm.GetName().Name}: AssemblyLoadContext = {AssemblyLoadContext.GetLoadContext(asm)}");
Console.WriteLine($"{asm.GetName().Name}: Location = '{asm.Location}'");
}

private static void PrintComponentCallLog(string name, IntPtr arg, int size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,10 @@ public static FluentAssertions.AndConstraint<CommandResultAssertions> ExecuteInD
{
return assertion.HaveStdOutContaining($"{assemblyName}: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
}

public static FluentAssertions.AndConstraint<CommandResultAssertions> ExecuteWithLocation(this CommandResultAssertions assertion, string assemblyName, string location)
{
return assertion.HaveStdOutContaining($"{assemblyName}: Location = '{location}'");
}
}
}
130 changes: 90 additions & 40 deletions src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,128 @@

namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
{
public partial class LoadAssembly : IClassFixture<LoadAssembly.SharedTestState>
public class LoadAssembly : IClassFixture<LoadAssembly.SharedTestState>
{
private const string AppLoadAssemblyArg = "app_load_assembly";
private const string ComponentLoadAssemblyArg = "component_load_assembly";

private const string AppLoadAssemblyBytesArg = "app_load_assembly_bytes";
private const string ComponentLoadAssemblyBytesArg = "component_load_assembly_bytes";

private readonly SharedTestState sharedState;

public LoadAssembly(SharedTestState sharedTestState)
{
sharedState = sharedTestState;
}

[Fact]
public void ApplicationContext()
private void ApplicationContext(bool loadAssemblyBytes, bool loadSymbolBytes)
{
var appProject = sharedState.Application;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
var app = sharedState.Application;
var component = sharedState.Component;
IEnumerable<string> args = new[]
{
AppLoadAssemblyArg,
loadAssemblyBytes ? AppLoadAssemblyBytesArg : AppLoadAssemblyArg,
sharedState.HostFxrPath,
appProject.AppDll,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1,
};
app.AppDll
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));

CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForApp(appProject.AppDll)
.And.InitializeContextForApp(app.AppDll)
.And.ExecuteSelfContained(selfContained: false)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.ExecuteInDefaultContext(component.AssemblyName)
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

[Fact]
public void ComponentContext()
public void ApplicationContext_FilePath()
{
ApplicationContext(loadAssemblyBytes: false, loadSymbolBytes: false);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ApplicationContext_Bytes(bool loadSymbolBytes)
{
ApplicationContext(loadAssemblyBytes: true, loadSymbolBytes);
}

private void ComponentContext(bool loadAssemblyBytes, bool loadSymbolBytes)
{
var appProject = sharedState.Application;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
var app = sharedState.Application;
var component = sharedState.Component;
IEnumerable<string> args = new[]
{
ComponentLoadAssemblyArg,
loadAssemblyBytes ? ComponentLoadAssemblyBytesArg : ComponentLoadAssemblyArg,
sharedState.HostFxrPath,
componentProject.RuntimeConfigJson,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1,
};
component.RuntimeConfigJson
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));

CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForConfig(componentProject.RuntimeConfigJson)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.InitializeContextForConfig(component.RuntimeConfigJson)
.And.ExecuteInDefaultContext(component.AssemblyName)
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

[Fact]
public void SelfContainedApplicationContext()
public void ComponentContext_FilePath()
{
ComponentContext(loadAssemblyBytes: false, loadSymbolBytes: false);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ComponentContext_Bytes(bool loadSymbolBytes)
{
ComponentContext(loadAssemblyBytes: true, loadSymbolBytes);
}

private void SelfContainedApplicationContext(bool loadAssemblyBytes, bool loadSymbolBytes)
{
var appProject = sharedState.SelfContainedApplication;
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
string[] args =
var app = sharedState.SelfContainedApplication;
var component = sharedState.Component;
IEnumerable<string> args = new[]
{
AppLoadAssemblyArg,
appProject.HostFxrDll,
appProject.AppDll,
componentProject.AppDll,
sharedState.ComponentTypeName,
sharedState.ComponentEntryPoint1
};
loadAssemblyBytes ? AppLoadAssemblyBytesArg : AppLoadAssemblyArg,
app.HostFxrDll,
app.AppDll
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));

CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
.Execute();

result.Should().Pass()
.And.InitializeContextForApp(appProject.AppDll)
.And.InitializeContextForApp(app.AppDll)
.And.ExecuteSelfContained(selfContained: true)
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
.And.ExecuteInDefaultContext(component.AssemblyName)
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
}

[Fact]
public void SelfContainedApplicationContext_FilePath()
{
SelfContainedApplicationContext(loadAssemblyBytes: false, loadSymbolBytes: false);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void SelfContainedApplicationContext_Bytes(bool loadSymbolBytes)
{
SelfContainedApplicationContext(loadAssemblyBytes: true, loadSymbolBytes);
}

public class SharedTestState : SharedTestStateBase
{
public string HostFxrPath { get; }
Expand All @@ -103,10 +142,10 @@ public class SharedTestState : SharedTestStateBase
public TestApp SelfContainedApplication { get; }

public TestProjectFixture ComponentWithNoDependenciesFixture { get; }
public TestApp Component => ComponentWithNoDependenciesFixture.TestProject.BuiltApp;

public string ComponentTypeName { get; }
public string ComponentEntryPoint1 => "ComponentEntryPoint1";
public string UnmanagedFunctionPointerEntryPoint1 => "UnmanagedFunctionPointerEntryPoint1";

public SharedTestState()
{
Expand All @@ -127,6 +166,17 @@ public SharedTestState()
ComponentTypeName = $"Component.Component, {ComponentWithNoDependenciesFixture.TestProject.AssemblyName}";
}

internal IEnumerable<string> GetComponentLoadArgs(bool loadAssemblyBytes, bool loadSymbolBytes)
{
List<string> args = new List<string>() { Component.AppDll };
if (loadAssemblyBytes)
args.Add(loadSymbolBytes ? $"{Path.GetFileNameWithoutExtension(Component.AppDll)}.pdb" : "nullptr");

args.Add(ComponentTypeName);
args.Add(ComponentEntryPoint1);
return args;
}

protected override void Dispose(bool disposing)
{
if (Application != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
<!-- Used by hostpolicy.cpp -->
<method name="LoadAssembly" />
<method name="LoadAssemblyBytes" />
<method name="LoadAssemblyAndGetFunctionPointer" />
<method name="GetFunctionPointer" />
</type>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
-->
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
<method name="LoadAssembly" />
<method name="LoadAssemblyBytes" />
<method name="LoadAssemblyAndGetFunctionPointer" />
</type>
</assembly>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,56 @@ private static void LoadAssemblyImpl(string assemblyPath)
}
}

/// <summary>
/// Native hosting entry point for loading an assembly from a byte array
/// </summary>
/// <param name="assembly">Bytes of the assembly to load</param>
/// <param name="assemblyByteLength">Byte length of the assembly to load</param>
/// <param name="symbols">Optional. Bytes of the symbols for the assembly</param>
/// <param name="symbolsByteLength">Optional. Byte length of the symbols for the assembly</param>
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
/// <param name="reserved">Extensibility parameter (currently unused)</param>
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("maccatalyst")]
[UnsupportedOSPlatform("tvos")]
[UnmanagedCallersOnly]
public static unsafe int LoadAssemblyBytes(byte* assembly, nint assemblyByteLength, byte* symbols, nint symbolsByteLength, IntPtr loadContext, IntPtr reserved)
{
if (!IsSupported)
return HostFeatureDisabled;

try
{
ArgumentNullException.ThrowIfNull(assembly);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(assemblyByteLength);
ArgumentOutOfRangeException.ThrowIfGreaterThan(assemblyByteLength, int.MaxValue);
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);

ReadOnlySpan<byte> assemblySpan = new ReadOnlySpan<byte>(assembly, (int)assemblyByteLength);
ReadOnlySpan<byte> symbolsSpan = default;
if (symbols != null && symbolsByteLength > 0)
{
symbolsSpan = new ReadOnlySpan<byte>(symbols, (int)symbolsByteLength);
}

LoadAssemblyBytesLocal(assemblySpan, symbolsSpan);
}
catch (Exception e)
{
return e.HResult;
}

return 0;

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The same feature switch applies to GetFunctionPointer and this function. We rely on the warning from GetFunctionPointer.")]
static void LoadAssemblyBytesLocal(ReadOnlySpan<byte> assemblyBytes, ReadOnlySpan<byte> symbolsBytes) => AssemblyLoadContext.Default.InternalLoad(assemblyBytes, symbolsBytes);
}

/// <summary>
/// Native hosting entry point for creating a native delegate
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/native/corehost/coreclr_delegates.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,12 @@ typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_fn)(
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
void *reserved /* Extensibility parameter (currently unused and must be 0) */);

typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_bytes_fn)(
const void *assembly_bytes /* Bytes of the assembly to load */,
size_t assembly_bytes_len /* Byte length of the assembly to load */,
const void *symbols_bytes /* Optional. Bytes of the symbols for the assembly */,
size_t symbols_bytes_len /* Optional. Byte length of the symbols for the assembly */,
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
void *reserved /* Extensibility parameter (currently unused and must be 0) */);

#endif // __CORECLR_DELEGATES_H__
1 change: 1 addition & 0 deletions src/native/corehost/corehost_context_contract.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum class coreclr_delegate_type
load_assembly_and_get_function_pointer,
get_function_pointer,
load_assembly,
load_assembly_bytes,

__last, // Sentinel value for determining the last known delegate type
};
Expand Down
2 changes: 2 additions & 0 deletions src/native/corehost/fxr/hostfxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,8 @@ namespace
return coreclr_delegate_type::get_function_pointer;
case hostfxr_delegate_type::hdt_load_assembly:
return coreclr_delegate_type::load_assembly;
case hostfxr_delegate_type::hdt_load_assembly_bytes:
return coreclr_delegate_type::load_assembly_bytes;
}
return coreclr_delegate_type::invalid;
}
Expand Down
1 change: 1 addition & 0 deletions src/native/corehost/hostfxr.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum hostfxr_delegate_type
hdt_load_assembly_and_get_function_pointer,
hdt_get_function_pointer,
hdt_load_assembly,
hdt_load_assembly_bytes,
};

typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv);
Expand Down
6 changes: 6 additions & 0 deletions src/native/corehost/hostpolicy/hostpolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,12 @@ namespace
"Internal.Runtime.InteropServices.ComponentActivator",
"LoadAssembly",
delegate);
case coreclr_delegate_type::load_assembly_bytes:
return coreclr->create_delegate(
"System.Private.CoreLib",
"Internal.Runtime.InteropServices.ComponentActivator",
"LoadAssemblyBytes",
delegate);
default:
return StatusCode::LibHostInvalidArgs;
}
Expand Down
Loading

0 comments on commit 0ac097f

Please sign in to comment.