Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add runtime config parameter to force ijwhost to load assemblies in an isolated context #105337

Merged
merged 6 commits into from
Jul 24, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static unsafe void LoadInMemoryAssemblyInContextWhenSupported(IntPtr mod
/// </summary>
/// <param name="moduleHandle">The native module handle for the assembly.</param>
/// <param name="assemblyPath">The path to the assembly (as a pointer to a UTF-16 C string).</param>
/// <param name="loadContext">Load context (currently must be IntPtr.Zero)</param>
/// <param name="loadContext">Load context (currently must be either IntPtr.Zero for default ALC or -1 for isolated ALC)</param>
[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.")]
Expand All @@ -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")]
Expand Down
37 changes: 37 additions & 0 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public void LoadLibrary(bool no_runtimeconfig)
app.AppDll,
"NativeEntryPoint"
};

if (no_runtimeconfig)
{
File.Delete(app.RuntimeConfigJson);
Expand All @@ -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()
{
Expand Down
14 changes: 12 additions & 2 deletions src/native/corehost/ijwhost/ijwhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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<hostfxr_get_runtime_property_value_fn>(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<void**>(&get_function_pointer),
true // ignore missing config file if there's an active context
);
Expand Down
4 changes: 3 additions & 1 deletion src/native/corehost/ijwhost/ijwhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions src/native/corehost/ijwhost/ijwthunk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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());

Expand Down
Loading