Skip to content

Commit 0ac097f

Browse files
authored
Implement load_assembly_bytes delegate for hosting API (#84650)
1 parent 6875ba0 commit 0ac097f

File tree

14 files changed

+365
-41
lines changed

14 files changed

+365
-41
lines changed

src/installer/tests/Assets/TestProjects/ComponentWithNoDependencies/Component.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ static Component()
1919
{
2020
Assembly asm = Assembly.GetExecutingAssembly();
2121
Console.WriteLine($"{asm.GetName().Name}: AssemblyLoadContext = {AssemblyLoadContext.GetLoadContext(asm)}");
22+
Console.WriteLine($"{asm.GetName().Name}: Location = '{asm.Location}'");
2223
}
2324

2425
private static void PrintComponentCallLog(string name, IntPtr arg, int size)

src/installer/tests/HostActivation.Tests/NativeHosting/FunctionPointerResultExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,10 @@ public static FluentAssertions.AndConstraint<CommandResultAssertions> ExecuteInD
4242
{
4343
return assertion.HaveStdOutContaining($"{assemblyName}: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
4444
}
45+
46+
public static FluentAssertions.AndConstraint<CommandResultAssertions> ExecuteWithLocation(this CommandResultAssertions assertion, string assemblyName, string location)
47+
{
48+
return assertion.HaveStdOutContaining($"{assemblyName}: Location = '{location}'");
49+
}
4550
}
4651
}

src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssembly.cs

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,89 +11,128 @@
1111

1212
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
1313
{
14-
public partial class LoadAssembly : IClassFixture<LoadAssembly.SharedTestState>
14+
public class LoadAssembly : IClassFixture<LoadAssembly.SharedTestState>
1515
{
1616
private const string AppLoadAssemblyArg = "app_load_assembly";
1717
private const string ComponentLoadAssemblyArg = "component_load_assembly";
1818

19+
private const string AppLoadAssemblyBytesArg = "app_load_assembly_bytes";
20+
private const string ComponentLoadAssemblyBytesArg = "component_load_assembly_bytes";
21+
1922
private readonly SharedTestState sharedState;
2023

2124
public LoadAssembly(SharedTestState sharedTestState)
2225
{
2326
sharedState = sharedTestState;
2427
}
2528

26-
[Fact]
27-
public void ApplicationContext()
29+
private void ApplicationContext(bool loadAssemblyBytes, bool loadSymbolBytes)
2830
{
29-
var appProject = sharedState.Application;
30-
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
31-
string[] args =
31+
var app = sharedState.Application;
32+
var component = sharedState.Component;
33+
IEnumerable<string> args = new[]
3234
{
33-
AppLoadAssemblyArg,
35+
loadAssemblyBytes ? AppLoadAssemblyBytesArg : AppLoadAssemblyArg,
3436
sharedState.HostFxrPath,
35-
appProject.AppDll,
36-
componentProject.AppDll,
37-
sharedState.ComponentTypeName,
38-
sharedState.ComponentEntryPoint1,
39-
};
37+
app.AppDll
38+
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));
39+
4040
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
4141
.Execute();
4242

4343
result.Should().Pass()
44-
.And.InitializeContextForApp(appProject.AppDll)
44+
.And.InitializeContextForApp(app.AppDll)
4545
.And.ExecuteSelfContained(selfContained: false)
46-
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
46+
.And.ExecuteInDefaultContext(component.AssemblyName)
47+
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
4748
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
4849
}
4950

5051
[Fact]
51-
public void ComponentContext()
52+
public void ApplicationContext_FilePath()
53+
{
54+
ApplicationContext(loadAssemblyBytes: false, loadSymbolBytes: false);
55+
}
56+
57+
[Theory]
58+
[InlineData(true)]
59+
[InlineData(false)]
60+
public void ApplicationContext_Bytes(bool loadSymbolBytes)
61+
{
62+
ApplicationContext(loadAssemblyBytes: true, loadSymbolBytes);
63+
}
64+
65+
private void ComponentContext(bool loadAssemblyBytes, bool loadSymbolBytes)
5266
{
53-
var appProject = sharedState.Application;
54-
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
55-
string[] args =
67+
var app = sharedState.Application;
68+
var component = sharedState.Component;
69+
IEnumerable<string> args = new[]
5670
{
57-
ComponentLoadAssemblyArg,
71+
loadAssemblyBytes ? ComponentLoadAssemblyBytesArg : ComponentLoadAssemblyArg,
5872
sharedState.HostFxrPath,
59-
componentProject.RuntimeConfigJson,
60-
componentProject.AppDll,
61-
sharedState.ComponentTypeName,
62-
sharedState.ComponentEntryPoint1,
63-
};
73+
component.RuntimeConfigJson
74+
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));
75+
6476
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
6577
.Execute();
6678

6779
result.Should().Pass()
68-
.And.InitializeContextForConfig(componentProject.RuntimeConfigJson)
69-
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
80+
.And.InitializeContextForConfig(component.RuntimeConfigJson)
81+
.And.ExecuteInDefaultContext(component.AssemblyName)
82+
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
7083
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
7184
}
7285

7386
[Fact]
74-
public void SelfContainedApplicationContext()
87+
public void ComponentContext_FilePath()
88+
{
89+
ComponentContext(loadAssemblyBytes: false, loadSymbolBytes: false);
90+
}
91+
92+
[Theory]
93+
[InlineData(true)]
94+
[InlineData(false)]
95+
public void ComponentContext_Bytes(bool loadSymbolBytes)
96+
{
97+
ComponentContext(loadAssemblyBytes: true, loadSymbolBytes);
98+
}
99+
100+
private void SelfContainedApplicationContext(bool loadAssemblyBytes, bool loadSymbolBytes)
75101
{
76-
var appProject = sharedState.SelfContainedApplication;
77-
var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject;
78-
string[] args =
102+
var app = sharedState.SelfContainedApplication;
103+
var component = sharedState.Component;
104+
IEnumerable<string> args = new[]
79105
{
80-
AppLoadAssemblyArg,
81-
appProject.HostFxrDll,
82-
appProject.AppDll,
83-
componentProject.AppDll,
84-
sharedState.ComponentTypeName,
85-
sharedState.ComponentEntryPoint1
86-
};
106+
loadAssemblyBytes ? AppLoadAssemblyBytesArg : AppLoadAssemblyArg,
107+
app.HostFxrDll,
108+
app.AppDll
109+
}.Concat(sharedState.GetComponentLoadArgs(loadAssemblyBytes, loadSymbolBytes));
110+
87111
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot)
88112
.Execute();
89113

90114
result.Should().Pass()
91-
.And.InitializeContextForApp(appProject.AppDll)
115+
.And.InitializeContextForApp(app.AppDll)
92116
.And.ExecuteSelfContained(selfContained: true)
93-
.And.ExecuteInDefaultContext(componentProject.AssemblyName)
117+
.And.ExecuteInDefaultContext(component.AssemblyName)
118+
.And.ExecuteWithLocation(component.AssemblyName, loadAssemblyBytes ? string.Empty : component.AppDll)
94119
.And.ExecuteFunctionPointer(sharedState.ComponentEntryPoint1, 1, 1);
95120
}
96121

122+
[Fact]
123+
public void SelfContainedApplicationContext_FilePath()
124+
{
125+
SelfContainedApplicationContext(loadAssemblyBytes: false, loadSymbolBytes: false);
126+
}
127+
128+
[Theory]
129+
[InlineData(true)]
130+
[InlineData(false)]
131+
public void SelfContainedApplicationContext_Bytes(bool loadSymbolBytes)
132+
{
133+
SelfContainedApplicationContext(loadAssemblyBytes: true, loadSymbolBytes);
134+
}
135+
97136
public class SharedTestState : SharedTestStateBase
98137
{
99138
public string HostFxrPath { get; }
@@ -103,10 +142,10 @@ public class SharedTestState : SharedTestStateBase
103142
public TestApp SelfContainedApplication { get; }
104143

105144
public TestProjectFixture ComponentWithNoDependenciesFixture { get; }
145+
public TestApp Component => ComponentWithNoDependenciesFixture.TestProject.BuiltApp;
106146

107147
public string ComponentTypeName { get; }
108148
public string ComponentEntryPoint1 => "ComponentEntryPoint1";
109-
public string UnmanagedFunctionPointerEntryPoint1 => "UnmanagedFunctionPointerEntryPoint1";
110149

111150
public SharedTestState()
112151
{
@@ -127,6 +166,17 @@ public SharedTestState()
127166
ComponentTypeName = $"Component.Component, {ComponentWithNoDependenciesFixture.TestProject.AssemblyName}";
128167
}
129168

169+
internal IEnumerable<string> GetComponentLoadArgs(bool loadAssemblyBytes, bool loadSymbolBytes)
170+
{
171+
List<string> args = new List<string>() { Component.AppDll };
172+
if (loadAssemblyBytes)
173+
args.Add(loadSymbolBytes ? $"{Path.GetFileNameWithoutExtension(Component.AppDll)}.pdb" : "nullptr");
174+
175+
args.Add(ComponentTypeName);
176+
args.Add(ComponentEntryPoint1);
177+
return args;
178+
}
179+
130180
protected override void Dispose(bool disposing)
131181
{
132182
if (Application != null)

src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.LibraryBuild.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
3232
<!-- Used by hostpolicy.cpp -->
3333
<method name="LoadAssembly" />
34+
<method name="LoadAssemblyBytes" />
3435
<method name="LoadAssemblyAndGetFunctionPointer" />
3536
<method name="GetFunctionPointer" />
3637
</type>

src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
-->
1717
<type fullname="Internal.Runtime.InteropServices.ComponentActivator">
1818
<method name="LoadAssembly" />
19+
<method name="LoadAssemblyBytes" />
1920
<method name="LoadAssemblyAndGetFunctionPointer" />
2021
</type>
2122
</assembly>

src/libraries/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,56 @@ private static void LoadAssemblyImpl(string assemblyPath)
160160
}
161161
}
162162

163+
/// <summary>
164+
/// Native hosting entry point for loading an assembly from a byte array
165+
/// </summary>
166+
/// <param name="assembly">Bytes of the assembly to load</param>
167+
/// <param name="assemblyByteLength">Byte length of the assembly to load</param>
168+
/// <param name="symbols">Optional. Bytes of the symbols for the assembly</param>
169+
/// <param name="symbolsByteLength">Optional. Byte length of the symbols for the assembly</param>
170+
/// <param name="loadContext">Extensibility parameter (currently unused)</param>
171+
/// <param name="reserved">Extensibility parameter (currently unused)</param>
172+
[RequiresDynamicCode(NativeAOTIncompatibleWarningMessage)]
173+
[UnsupportedOSPlatform("android")]
174+
[UnsupportedOSPlatform("browser")]
175+
[UnsupportedOSPlatform("ios")]
176+
[UnsupportedOSPlatform("maccatalyst")]
177+
[UnsupportedOSPlatform("tvos")]
178+
[UnmanagedCallersOnly]
179+
public static unsafe int LoadAssemblyBytes(byte* assembly, nint assemblyByteLength, byte* symbols, nint symbolsByteLength, IntPtr loadContext, IntPtr reserved)
180+
{
181+
if (!IsSupported)
182+
return HostFeatureDisabled;
183+
184+
try
185+
{
186+
ArgumentNullException.ThrowIfNull(assembly);
187+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(assemblyByteLength);
188+
ArgumentOutOfRangeException.ThrowIfGreaterThan(assemblyByteLength, int.MaxValue);
189+
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
190+
ArgumentOutOfRangeException.ThrowIfNotEqual(reserved, IntPtr.Zero);
191+
192+
ReadOnlySpan<byte> assemblySpan = new ReadOnlySpan<byte>(assembly, (int)assemblyByteLength);
193+
ReadOnlySpan<byte> symbolsSpan = default;
194+
if (symbols != null && symbolsByteLength > 0)
195+
{
196+
symbolsSpan = new ReadOnlySpan<byte>(symbols, (int)symbolsByteLength);
197+
}
198+
199+
LoadAssemblyBytesLocal(assemblySpan, symbolsSpan);
200+
}
201+
catch (Exception e)
202+
{
203+
return e.HResult;
204+
}
205+
206+
return 0;
207+
208+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
209+
Justification = "The same feature switch applies to GetFunctionPointer and this function. We rely on the warning from GetFunctionPointer.")]
210+
static void LoadAssemblyBytesLocal(ReadOnlySpan<byte> assemblyBytes, ReadOnlySpan<byte> symbolsBytes) => AssemblyLoadContext.Default.InternalLoad(assemblyBytes, symbolsBytes);
211+
}
212+
163213
/// <summary>
164214
/// Native hosting entry point for creating a native delegate
165215
/// </summary>

src/native/corehost/coreclr_delegates.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,12 @@ typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_fn)(
4949
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
5050
void *reserved /* Extensibility parameter (currently unused and must be 0) */);
5151

52+
typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_bytes_fn)(
53+
const void *assembly_bytes /* Bytes of the assembly to load */,
54+
size_t assembly_bytes_len /* Byte length of the assembly to load */,
55+
const void *symbols_bytes /* Optional. Bytes of the symbols for the assembly */,
56+
size_t symbols_bytes_len /* Optional. Byte length of the symbols for the assembly */,
57+
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
58+
void *reserved /* Extensibility parameter (currently unused and must be 0) */);
59+
5260
#endif // __CORECLR_DELEGATES_H__

src/native/corehost/corehost_context_contract.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ enum class coreclr_delegate_type
2929
load_assembly_and_get_function_pointer,
3030
get_function_pointer,
3131
load_assembly,
32+
load_assembly_bytes,
3233

3334
__last, // Sentinel value for determining the last known delegate type
3435
};

src/native/corehost/fxr/hostfxr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,8 @@ namespace
669669
return coreclr_delegate_type::get_function_pointer;
670670
case hostfxr_delegate_type::hdt_load_assembly:
671671
return coreclr_delegate_type::load_assembly;
672+
case hostfxr_delegate_type::hdt_load_assembly_bytes:
673+
return coreclr_delegate_type::load_assembly_bytes;
672674
}
673675
return coreclr_delegate_type::invalid;
674676
}

src/native/corehost/hostfxr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ enum hostfxr_delegate_type
2929
hdt_load_assembly_and_get_function_pointer,
3030
hdt_get_function_pointer,
3131
hdt_load_assembly,
32+
hdt_load_assembly_bytes,
3233
};
3334

3435
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv);

0 commit comments

Comments
 (0)