diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index e7efe5eaafff84..ac14c328ecfaf8 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -22,20 +22,66 @@ public Ijwhost(SharedTestState sharedTestState) sharedState = sharedTestState; } - [Fact] - public void LoadLibrary() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LoadLibrary(bool no_runtimeconfig) { - string [] args = { - "ijwhost", - sharedState.IjwLibraryPath, - "NativeEntryPoint" - }; - CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet) - .Execute(); + // 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" + }; + if (no_runtimeconfig) + { + File.Delete(app.RuntimeConfigJson); + } + + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet) + .Execute(); + + if (no_runtimeconfig) + { + result.Should().Fail() + .And.HaveStdErrContaining($"Expected active runtime context because runtimeconfig.json [{app.RuntimeConfigJson}] does not exist."); + } + else + { + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + } + } + } - result.Should().Pass() - .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + [Fact] + public void LoadLibraryWithoutRuntimeConfigButActiveRuntime() + { + // make a copy of a portion of the shared state because we will modify it + using (var app = sharedState.IjwApp.Copy()) + { + // construct runtimeconfig.json + var startupConfigPath = Path.Combine(Path.GetDirectoryName(app.RuntimeConfigJson),"host.runtimeconfig.json"); + string [] args = { + "ijwhost", + app.AppDll, + "NativeEntryPoint", + sharedState.HostFxrPath, // optional 4th and 5th arguments that tell nativehost to start the runtime before loading the C++/CLI library + startupConfigPath + }; + + File.Move(app.RuntimeConfigJson, startupConfigPath); + + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet) + .Execute(); + + result.Should().Pass() + .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); + } } [Theory] @@ -45,7 +91,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,24 +109,24 @@ public void ManagedHost(bool selfContained) public class SharedTestState : SharedTestStateBase { - public string IjwLibraryPath { get; } - + public string HostFxrPath { get; } public TestProjectFixture ManagedHostFixture_FrameworkDependent { get; } public TestProjectFixture ManagedHostFixture_SelfContained { get; } + public TestApp IjwApp {get;} public SharedTestState() { + var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(RepoDirectories.BuiltDotnet); + HostFxrPath = dotNet.GreatestVersionHostFxrFilePath; 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")) diff --git a/src/native/corehost/comhost/comhost.cpp b/src/native/corehost/comhost/comhost.cpp index c5317bf31503fa..79050d4d7ff4db 100644 --- a/src/native/corehost/comhost/comhost.cpp +++ b/src/native/corehost/comhost/comhost.cpp @@ -94,7 +94,7 @@ namespace delegates.delegate_no_load_cxt = nullptr; get_function_pointer_fn get_function_pointer; - int status = load_fxr_and_get_delegate( + int status = load_fxr_and_get_delegate( hostfxr_delegate_type::hdt_get_function_pointer, [app_path](const pal::string_t& host_path, pal::string_t* config_path_out) { @@ -123,7 +123,8 @@ namespace *load_context = nullptr; // Default context } }, - reinterpret_cast(&get_function_pointer) + reinterpret_cast(&get_function_pointer), + false // do not ignore missing config file if there's an active context ); if (status != StatusCode::Success) return status; diff --git a/src/native/corehost/fxr_resolver.h b/src/native/corehost/fxr_resolver.h index 3df6c2de3053d3..8d6301f0befe2b 100644 --- a/src/native/corehost/fxr_resolver.h +++ b/src/native/corehost/fxr_resolver.h @@ -18,7 +18,7 @@ namespace fxr_resolver } template -int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate) +int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate, bool try_ignore_missing_config) { pal::dll_t fxr; @@ -67,34 +67,45 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb if (status != StatusCode::Success) return status; - hostfxr_initialize_parameters parameters { - sizeof(hostfxr_initialize_parameters), - host_path.c_str(), - dotnet_root.c_str() - }; - hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast(pal::get_symbol(fxr, "hostfxr_set_error_writer")); - { propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn); + if (!try_ignore_missing_config || pal::file_exists(config_path)) + { + hostfxr_initialize_parameters parameters { + sizeof(hostfxr_initialize_parameters), + host_path.c_str(), + dotnet_root.c_str() + }; - hostfxr_handle context; - int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), ¶meters, &context); - if (!STATUS_CODE_SUCCEEDED(rc)) - return rc; + hostfxr_handle context; + int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), ¶meters, &context); + if (!STATUS_CODE_SUCCEEDED(rc)) + return rc; + + on_before_run(fxr, context); - on_before_run(fxr, context); + rc = hostfxr_get_runtime_delegate(context, type, delegate); - rc = hostfxr_get_runtime_delegate(context, type, delegate); + int rcClose = hostfxr_close(context); + if (rcClose != StatusCode::Success) + { + assert(false && "Failed to close host context"); + trace::verbose(_X("Failed to close host context: 0x%x"), rcClose); + } - int rcClose = hostfxr_close(context); - if (rcClose != StatusCode::Success) + return rc; + } + else { - assert(false && "Failed to close host context"); - trace::verbose(_X("Failed to close host context: 0x%x"), rcClose); + // null context means use the current one, if none exists it will fail + int rc = hostfxr_get_runtime_delegate(nullptr, type, delegate); + if (rc == StatusCode::HostInvalidState) + { + trace::error(_X("Expected active runtime context because runtimeconfig.json [%s] does not exist."), config_path.c_str()); + } + return rc; } - - return rc; } } diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 7f1684cca62630..4d9925366e6b97 100644 --- a/src/native/corehost/ijwhost/ijwhost.cpp +++ b/src/native/corehost/ijwhost/ijwhost.cpp @@ -41,7 +41,8 @@ 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){ }, - reinterpret_cast(&get_function_pointer) + reinterpret_cast(&get_function_pointer), + true // ignore missing config file if there's an active context ); if (status != StatusCode::Success) return status; diff --git a/src/native/corehost/test/nativehost/nativehost.cpp b/src/native/corehost/test/nativehost/nativehost.cpp index 214e2b9af9122c..09ec47b259b7bf 100644 --- a/src/native/corehost/test/nativehost/nativehost.cpp +++ b/src/native/corehost/test/nativehost/nativehost.cpp @@ -525,6 +525,19 @@ int main(const int argc, const pal::char_t *argv[]) return -1; } + // 2 optional arguments indicating whether we should start the runtime + if (argc > 5) + { + const pal::string_t hostfxr_path = argv[4]; + const pal::char_t* config_path = argv[5]; + pal::stringstream_t test_output; + if (!host_context_test::config(host_context_test::check_properties::none, hostfxr_path, config_path, 0, nullptr, test_output)) + { + std::cout << "Failed to start runtime from path: " << tostr(hostfxr_path).data() << std::endl; + return EXIT_FAILURE; + } + } + const pal::string_t ijw_library_path = argv[2]; std::vector entry_point_name = tostr(argv[3]); @@ -543,8 +556,16 @@ int main(const int argc, const pal::char_t *argv[]) std::cout << "Failed to find entry point: " << entry_point_name.data() << std::endl; return EXIT_FAILURE; } - - entry_point(); + try + { + entry_point(); + } + catch (...) + { + // entry_point will throw in some tests, this is expected. + // We must catch this exception to ensure that the CRT does not pop a modal dialog + return EXIT_FAILURE; + } return EXIT_SUCCESS; } #endif