Skip to content

Commit

Permalink
[release/8.0-staging] Add runtime config parameter to force ijwhost t…
Browse files Browse the repository at this point in the history
…o load assemblies in an isolated context (#105436)
  • Loading branch information
elinor-fung authored Jul 29, 2024
1 parent 4c77671 commit 2485745
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 14 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
46 changes: 40 additions & 6 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void LoadLibrary()
{
string [] args = {
"ijwhost",
sharedState.IjwLibraryPath,
sharedState.IjwApp.AppDll,
"NativeEntryPoint"
};
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
Expand All @@ -38,14 +38,49 @@ 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)]
public void ManagedHost(bool selfContained)
{
string [] args = {
"ijwhost",
sharedState.IjwLibraryPath,
sharedState.IjwApp.AppDll,
"NativeEntryPoint"
};
TestProjectFixture fixture = selfContained ? sharedState.ManagedHostFixture_SelfContained : sharedState.ManagedHostFixture_FrameworkDependent;
Expand All @@ -63,24 +98,23 @@ 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; }

public SharedTestState()
{
string folder = Path.Combine(BaseDirectory, "ijw");
Directory.CreateDirectory(folder);
IjwApp = new TestApp(folder, "ijw");

// Copy over ijwhost
string ijwhostName = "ijwhost.dll";
File.Copy(Path.Combine(RepoDirectories.HostArtifacts, ijwhostName), Path.Combine(folder, ijwhostName));

// 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"))
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 2485745

Please sign in to comment.