Skip to content

Commit

Permalink
Add runtime config parameter to force ijwhost to load assemblies in a…
Browse files Browse the repository at this point in the history
…n isolated context (dotnet#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
  • Loading branch information
mikeoliphant authored and elinor-fung committed Jul 24, 2024
1 parent 07c469f commit e16de04
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 8 deletions.
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
35 changes: 35 additions & 0 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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");
}
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
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)
);
if (status != StatusCode::Success)
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

0 comments on commit e16de04

Please sign in to comment.