Skip to content

Commit 62c6854

Browse files
mikeoliphantdirecthex
authored andcommitted
Add runtime config parameter to force ijwhost to load assemblies in an isolated context (#105337)
* Add support for isolated load context in LoadInMemoryAssemblyInContext by passing -1 as loadContext * Have ijwhost check a runtime config parameter to determine if it should run in an isolated load context * Added test for ijwhost isolated load context runtime config option
1 parent 8c27dad commit 62c6854

File tree

5 files changed

+62
-8
lines changed

5 files changed

+62
-8
lines changed

src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private static unsafe void LoadInMemoryAssemblyInContextWhenSupported(IntPtr mod
4949
/// </summary>
5050
/// <param name="moduleHandle">The native module handle for the assembly.</param>
5151
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
52-
/// <param name="loadContext">Load context (currently must be IntPtr.Zero)</param>
52+
/// <param name="loadContext">Load context (currently must be either IntPtr.Zero for default ALC or -1 for isolated ALC)</param>
5353
[UnmanagedCallersOnly]
5454
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
5555
Justification = "The same C++/CLI feature switch applies to LoadInMemoryAssembly and this function. We rely on the warning from LoadInMemoryAssembly.")]
@@ -58,9 +58,12 @@ public static unsafe void LoadInMemoryAssemblyInContext(IntPtr moduleHandle, Int
5858
if (!IsSupported)
5959
throw new NotSupportedException(SR.NotSupported_CppCli);
6060

61-
ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
61+
if ((loadContext != IntPtr.Zero) && (loadContext != -1))
62+
{
63+
throw new ArgumentOutOfRangeException(nameof(loadContext));
64+
}
6265

63-
LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, AssemblyLoadContext.Default);
66+
LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, (loadContext == IntPtr.Zero) ? AssemblyLoadContext.Default : null);
6467
}
6568

6669
[RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")]

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public void LoadLibrary(bool no_runtimeconfig)
3131
app.AppDll,
3232
"NativeEntryPoint"
3333
};
34+
3435
if (no_runtimeconfig)
3536
{
3637
File.Delete(app.RuntimeConfigJson);
@@ -53,6 +54,42 @@ public void LoadLibrary(bool no_runtimeconfig)
5354
}
5455
}
5556

57+
[Theory]
58+
[InlineData(true)]
59+
[InlineData(false)]
60+
public void LoadLibrary_ContextConfig(bool load_isolated)
61+
{
62+
// make a copy of a portion of the shared state because we will modify it
63+
using (var app = sharedState.IjwApp.Copy())
64+
{
65+
string[] args = {
66+
"ijwhost",
67+
app.AppDll,
68+
"NativeEntryPoint"
69+
};
70+
71+
RuntimeConfig.FromFile(app.RuntimeConfigJson)
72+
.WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", load_isolated.ToString())
73+
.Save();
74+
75+
CommandResult result = sharedState.CreateNativeHostCommand(args, TestContext.BuiltDotNet.BinPath)
76+
.Execute();
77+
78+
result.Should().Pass()
79+
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class");
80+
81+
if (load_isolated) // Assembly should be loaded in an isolated context
82+
{
83+
result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext");
84+
}
85+
else // Assembly should be loaded in the default context
86+
{
87+
result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
88+
}
89+
}
90+
}
91+
92+
5693
[Fact]
5794
public void LoadLibraryWithoutRuntimeConfigButActiveRuntime()
5895
{

src/native/corehost/ijwhost/ijwhost.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#define IJW_API SHARED_API
2121
#endif // _WIN32
2222

23-
pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate)
23+
pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context)
2424
{
2525
get_function_pointer_fn get_function_pointer;
2626
int status = load_fxr_and_get_delegate(
@@ -40,7 +40,17 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m
4040

4141
return StatusCode::Success;
4242
},
43-
[](pal::dll_t fxr, hostfxr_handle context){ },
43+
[load_context](pal::dll_t fxr, hostfxr_handle context)
44+
{
45+
*load_context = nullptr; // default load context
46+
auto get_runtime_property_value = reinterpret_cast<hostfxr_get_runtime_property_value_fn>(pal::get_symbol(fxr, "hostfxr_get_runtime_property_value"));
47+
const pal::char_t* value;
48+
if (get_runtime_property_value(context, _X("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext"), &value) == StatusCode::Success
49+
&& pal::strcasecmp(value, _X("true")) == 0)
50+
{
51+
*load_context = ISOLATED_CONTEXT; // Isolated load context
52+
}
53+
},
4454
reinterpret_cast<void**>(&get_function_pointer),
4555
true // ignore missing config file if there's an active context
4656
);

src/native/corehost/ijwhost/ijwhost.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ bool are_thunks_installed_for_module(HMODULE instance);
1515

1616
using load_in_memory_assembly_fn = void(STDMETHODCALLTYPE*)(pal::dll_t handle, const pal::char_t* path, void* load_context);
1717

18-
pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate);
18+
pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context);
1919

2020
extern HANDLE g_heapHandle;
2121

22+
#define ISOLATED_CONTEXT (void*)-1
23+
2224
#endif

src/native/corehost/ijwhost/ijwthunk.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui
119119
bootstrap_thunk *pThunk = bootstrap_thunk::get_thunk_from_cookie(cookie);
120120
load_in_memory_assembly_fn loadInMemoryAssembly;
121121
pal::dll_t moduleHandle = pThunk->get_dll_handle();
122-
pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly);
122+
123+
void* load_context = nullptr;
124+
pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly, &load_context);
123125

124126
if (status != StatusCode::Success)
125127
{
@@ -145,7 +147,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui
145147
#pragma warning (pop)
146148
}
147149

148-
loadInMemoryAssembly(moduleHandle, app_path.c_str(), nullptr);
150+
loadInMemoryAssembly(moduleHandle, app_path.c_str(), load_context);
149151

150152
std::uintptr_t thunkAddress = *(pThunk->get_slot_address());
151153

0 commit comments

Comments
 (0)