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());