Skip to content
Merged
97 changes: 82 additions & 15 deletions src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 the shared state because we will modify it
using (var testState = sharedState.Copy())
{
string [] args = {
"ijwhost",
testState.IjwLibraryPath,
"NativeEntryPoint"
};
if (no_runtimeconfig)
{
File.Delete(testState.IjwLibraryRuntimeConfigPath);
}

CommandResult result = testState.CreateNativeHostCommand(args, testState.RepoDirectories.BuiltDotnet)
.Execute();

if (no_runtimeconfig)
{
result.Should().Fail()
.And.HaveStdErrContaining($"Expected active runtime context because runtimeconfig.json [{testState.IjwLibraryRuntimeConfigPath}] 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 the shared state because we will modify it
using (var testState = sharedState.Copy())
{
// construct runtimeconfig.json
var startupConfigPath = Path.Combine(Path.GetDirectoryName(testState.IjwLibraryRuntimeConfigPath),"host.runtimeconfig.json");
string [] args = {
"ijwhost",
testState.IjwLibraryPath,
"NativeEntryPoint",
testState.HostFxrPath, // optional 4th and 5th arguments that tell nativehost to start the runtime before loading the C++/CLI library
startupConfigPath
};

File.Move(testState.IjwLibraryRuntimeConfigPath, startupConfigPath);

CommandResult result = testState.CreateNativeHostCommand(args, testState.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]
Expand Down Expand Up @@ -63,14 +109,28 @@ public void ManagedHost(bool selfContained)

public class SharedTestState : SharedTestStateBase
{
public string HostFxrPath { get; }
public string IjwLibraryPath { get; }

public string IjwLibraryRuntimeConfigPath { get; }
public TestProjectFixture ManagedHostFixture_FrameworkDependent { get; }
public TestProjectFixture ManagedHostFixture_SelfContained { get; }

public SharedTestState()
:this(null)
{}

private SharedTestState(SharedTestState other = null)
{
string folder = Path.Combine(BaseDirectory, "ijw");
var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(RepoDirectories.BuiltDotnet);
HostFxrPath = dotNet.GreatestVersionHostFxrFilePath;

string folder = Path.Combine(BaseDirectory, other==null?"ijw":"ijw_copy");

// make sure we start with a clean slate
if (Directory.Exists(folder))
{
Directory.Delete(folder);
}
Directory.CreateDirectory(folder);

// Copy over ijwhost
Expand All @@ -83,7 +143,8 @@ public SharedTestState()
File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), IjwLibraryPath);

// Create a runtimeconfig.json for the C++/CLI test library
new RuntimeConfig(Path.Combine(folder, "ijw.runtimeconfig.json"))
IjwLibraryRuntimeConfigPath = Path.Combine(folder, "ijw.runtimeconfig.json");
new RuntimeConfig(IjwLibraryRuntimeConfigPath)
.WithFramework(new RuntimeConfig.Framework(Constants.MicrosoftNETCoreApp, RepoDirectories.MicrosoftNETCoreAppVersion))
.Save();

Expand All @@ -103,6 +164,12 @@ protected override void Dispose(bool disposing)

base.Dispose(disposing);
}

public SharedTestState Copy()
{
return new SharedTestState(this);
}

}
}
}
5 changes: 3 additions & 2 deletions src/native/corehost/comhost/comhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -123,7 +123,8 @@ namespace
*load_context = nullptr; // Default context
}
},
reinterpret_cast<void**>(&get_function_pointer)
reinterpret_cast<void**>(&get_function_pointer),
false // do not ignore missing config file if there's an active context
);
if (status != StatusCode::Success)
return status;
Expand Down
51 changes: 31 additions & 20 deletions src/native/corehost/fxr_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace fxr_resolver
}

template<typename THostPathToConfigCallback, typename TBeforeRunCallback>
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;

Expand Down Expand Up @@ -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<hostfxr_set_error_writer_fn>(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(), &parameters, &context);
if (!STATUS_CODE_SUCCEEDED(rc))
return rc;
hostfxr_handle context;
int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &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;
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/native/corehost/ijwhost/ijwhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void**>(&get_function_pointer)
reinterpret_cast<void**>(&get_function_pointer),
true // ignore missing config file if there's an active context
);
if (status != StatusCode::Success)
return status;
Expand Down
25 changes: 23 additions & 2 deletions src/native/corehost/test/nativehost/nativehost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> entry_point_name = tostr(argv[3]);

Expand All @@ -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
Expand Down