diff --git a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs
index dc8d8ccb4c7cc..55e4227481724 100644
--- a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs
+++ b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs
@@ -49,7 +49,7 @@ private static unsafe void LoadInMemoryAssemblyInContextWhenSupported(IntPtr mod
///
/// The native module handle for the assembly.
/// The path to the assembly (as a pointer to a UTF-16 C string).
- /// Load context (currently must be IntPtr.Zero)
+ /// Load context (currently must be either IntPtr.Zero for default ALC or -1 for isolated ALC)
[UnmanagedCallersOnly]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
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
if (!IsSupported)
throw new NotSupportedException(SR.NotSupported_CppCli);
- ArgumentOutOfRangeException.ThrowIfNotEqual(loadContext, IntPtr.Zero);
+ if ((loadContext != IntPtr.Zero) && (loadContext != -1))
+ {
+ throw new ArgumentOutOfRangeException(nameof(loadContext));
+ }
- LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, AssemblyLoadContext.Default);
+ LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, (loadContext == IntPtr.Zero) ? AssemblyLoadContext.Default : null);
}
[RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")]
diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
index 44d04bafb7777..fa00b0419a85e 100644
--- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
+++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
@@ -31,6 +31,7 @@ public void LoadLibrary(bool no_runtimeconfig)
app.AppDll,
"NativeEntryPoint"
};
+
if (no_runtimeconfig)
{
File.Delete(app.RuntimeConfigJson);
@@ -53,6 +54,42 @@ public void LoadLibrary(bool no_runtimeconfig)
}
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void LoadLibrary_ContextConfig(bool load_isolated)
+ {
+ // make a copy of a portion of the shared state because we will modify it
+ using (var app = sharedState.IjwApp.Copy())
+ {
+ string[] args = {
+ "ijwhost",
+ app.AppDll,
+ "NativeEntryPoint"
+ };
+
+ RuntimeConfig.FromFile(app.RuntimeConfigJson)
+ .WithProperty("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext", load_isolated.ToString())
+ .Save();
+
+ CommandResult result = sharedState.CreateNativeHostCommand(args, TestContext.BuiltDotNet.BinPath)
+ .Execute();
+
+ result.Should().Pass()
+ .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class");
+
+ if (load_isolated) // Assembly should be loaded in an isolated context
+ {
+ result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext");
+ }
+ else // Assembly should be loaded in the default context
+ {
+ result.Should().HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
+ }
+ }
+ }
+
+
[Fact]
public void LoadLibraryWithoutRuntimeConfigButActiveRuntime()
{
diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp
index 4d9925366e6b9..e64e01a56580f 100644
--- a/src/native/corehost/ijwhost/ijwhost.cpp
+++ b/src/native/corehost/ijwhost/ijwhost.cpp
@@ -20,7 +20,7 @@
#define IJW_API SHARED_API
#endif // _WIN32
-pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate)
+pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context)
{
get_function_pointer_fn get_function_pointer;
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
return StatusCode::Success;
},
- [](pal::dll_t fxr, hostfxr_handle context){ },
+ [load_context](pal::dll_t fxr, hostfxr_handle context)
+ {
+ *load_context = nullptr; // default load context
+ auto get_runtime_property_value = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_get_runtime_property_value"));
+ const pal::char_t* value;
+ if (get_runtime_property_value(context, _X("System.Runtime.InteropServices.CppCLI.LoadComponentInIsolatedContext"), &value) == StatusCode::Success
+ && pal::strcasecmp(value, _X("true")) == 0)
+ {
+ *load_context = ISOLATED_CONTEXT; // Isolated load context
+ }
+ },
reinterpret_cast(&get_function_pointer),
true // ignore missing config file if there's an active context
);
diff --git a/src/native/corehost/ijwhost/ijwhost.h b/src/native/corehost/ijwhost/ijwhost.h
index c3698a4d4acb6..adfd52f71a99c 100644
--- a/src/native/corehost/ijwhost/ijwhost.h
+++ b/src/native/corehost/ijwhost/ijwhost.h
@@ -15,8 +15,10 @@ bool are_thunks_installed_for_module(HMODULE instance);
using load_in_memory_assembly_fn = void(STDMETHODCALLTYPE*)(pal::dll_t handle, const pal::char_t* path, void* load_context);
-pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate);
+pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate, void **load_context);
extern HANDLE g_heapHandle;
+#define ISOLATED_CONTEXT (void*)-1
+
#endif
diff --git a/src/native/corehost/ijwhost/ijwthunk.cpp b/src/native/corehost/ijwhost/ijwthunk.cpp
index eeddf10fd931c..2c24f4d6f9207 100644
--- a/src/native/corehost/ijwhost/ijwthunk.cpp
+++ b/src/native/corehost/ijwhost/ijwthunk.cpp
@@ -119,7 +119,9 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui
bootstrap_thunk *pThunk = bootstrap_thunk::get_thunk_from_cookie(cookie);
load_in_memory_assembly_fn loadInMemoryAssembly;
pal::dll_t moduleHandle = pThunk->get_dll_handle();
- pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly);
+
+ void* load_context = nullptr;
+ pal::hresult_t status = get_load_in_memory_assembly_delegate(moduleHandle, &loadInMemoryAssembly, &load_context);
if (status != StatusCode::Success)
{
@@ -145,7 +147,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui
#pragma warning (pop)
}
- loadInMemoryAssembly(moduleHandle, app_path.c_str(), nullptr);
+ loadInMemoryAssembly(moduleHandle, app_path.c_str(), load_context);
std::uintptr_t thunkAddress = *(pThunk->get_slot_address());