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 dc8d8ccb4c7cca..55e42274817245 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 e7efe5eaafff84..062e6bcc58130f 100644
--- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
+++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
@@ -27,7 +27,7 @@ public void LoadLibrary()
{
string [] args = {
"ijwhost",
- sharedState.IjwLibraryPath,
+ sharedState.IjwApp.AppDll,
"NativeEntryPoint"
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
@@ -38,6 +38,41 @@ public void LoadLibrary()
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
}
+ [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, sharedState.RepoDirectories.BuiltDotnet)
+ .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");
+ }
+ }
+ }
+
[Theory]
[InlineData(true)]
[InlineData(false)]
@@ -45,7 +80,7 @@ public void ManagedHost(bool selfContained)
{
string [] args = {
"ijwhost",
- sharedState.IjwLibraryPath,
+ sharedState.IjwApp.AppDll,
"NativeEntryPoint"
};
TestProjectFixture fixture = selfContained ? sharedState.ManagedHostFixture_SelfContained : sharedState.ManagedHostFixture_FrameworkDependent;
@@ -63,7 +98,7 @@ public void ManagedHost(bool selfContained)
public class SharedTestState : SharedTestStateBase
{
- public string IjwLibraryPath { get; }
+ public TestApp IjwApp {get;}
public TestProjectFixture ManagedHostFixture_FrameworkDependent { get; }
public TestProjectFixture ManagedHostFixture_SelfContained { get; }
@@ -71,7 +106,7 @@ public class SharedTestState : SharedTestStateBase
public SharedTestState()
{
string folder = Path.Combine(BaseDirectory, "ijw");
- Directory.CreateDirectory(folder);
+ IjwApp = new TestApp(folder, "ijw");
// Copy over ijwhost
string ijwhostName = "ijwhost.dll";
@@ -79,8 +114,7 @@ public SharedTestState()
// Copy over the C++/CLI test library
string ijwLibraryName = "ijw.dll";
- IjwLibraryPath = Path.Combine(folder, ijwLibraryName);
- File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), IjwLibraryPath);
+ File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), Path.Combine(folder, ijwLibraryName));
// Create a runtimeconfig.json for the C++/CLI test library
new RuntimeConfig(Path.Combine(folder, "ijw.runtimeconfig.json"))
diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp
index 7f1684cca62630..c2e1d22a32cc50 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)
);
if (status != StatusCode::Success)
diff --git a/src/native/corehost/ijwhost/ijwhost.h b/src/native/corehost/ijwhost/ijwhost.h
index c3698a4d4acb65..adfd52f71a99c9 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 eeddf10fd931c1..2c24f4d6f92078 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());