Skip to content
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

Implement load_assembly_bytes delegate for hosting API #84650

Merged
merged 2 commits into from
Apr 13, 2023
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
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