Skip to content

Commit

Permalink
Add self-contained support to CsWinRT WinRT.Host
Browse files Browse the repository at this point in the history
Tries to fix microsoft#1141. Uses the same approach as commited in https://github.com/jlaanstra/CsWinRT/tree/user/jlaans/self-contained microsoft#1141 (comment) with DNNE, but within CsWinRT directly, avoiding additional binaries.
  • Loading branch information
whiskhub committed Oct 6, 2024
1 parent ad5cc38 commit d309bd1
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 16 deletions.
5 changes: 5 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.Authoring.targets
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<CsWinRTEnableLogging>$(CsWinRTEnableLogging)</CsWinRTEnableLogging>
</PropertyGroup>

<!-- Let WinRT.Host know the project is self-contained. -->
<ItemGroup Condition="'$(SelfContained)' == 'true' or '$(PublishSelfContained)' == 'true'">
<RuntimeHostConfigurationOption Include="CSWINRT_SELF_CONTAINED" Value="true" />
</ItemGroup>

<PropertyGroup>
<CsWinRTAuthoring_Platform Condition="'$(Platform)'=='AnyCPU'">x86</CsWinRTAuthoring_Platform>
<CsWinRTAuthoring_Platform Condition="'$(Platform)'!='AnyCPU'">$(Platform)</CsWinRTAuthoring_Platform>
Expand Down
102 changes: 86 additions & 16 deletions src/Authoring/WinRT.Host/WinRT.Host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef int (CORECLR_DELEGATE_CALLTYPE* get_activation_factory_fn)(
static get_activation_factory_fn get_activation_factory = nullptr;
static hostfxr_close_fn hostfxr_close = nullptr;
static hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
static hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr;
static hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr;
static hostfxr_set_error_writer_fn hostfxr_set_error_writer = nullptr;
static load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr;
Expand Down Expand Up @@ -143,11 +144,12 @@ inline void check_hostfxr_hresult(hresult const result)
}

// Using the nethost library, discover the location of hostfxr and get exports
void load_hostfxr()
void load_hostfxr(const char_t* assembly_path)
{
static const auto is_hostfxr_loaded = [&]()
{
return(hostfxr_initialize_for_runtime_config &&
hostfxr_initialize_for_dotnet_command_line &&
hostfxr_get_runtime_delegate &&
hostfxr_close &&
hostfxr_set_error_writer);
Expand All @@ -160,14 +162,19 @@ void load_hostfxr()

wchar_t buffer[MAX_PATH];
size_t buffer_size = sizeof(buffer) / sizeof(wchar_t);
check_hostfxr_hresult(get_hostfxr_path(buffer, &buffer_size, nullptr));
struct get_hostfxr_parameters params;
params.size = sizeof(params);
params.assembly_path = assembly_path;
params.dotnet_root = nullptr;
check_hostfxr_hresult(get_hostfxr_path(buffer, &buffer_size, &params));
auto lib = ::LoadLibraryW(buffer);
if (lib == 0)
{
winrt::throw_last_error();
}

if ((hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)::GetProcAddress(lib, "hostfxr_initialize_for_runtime_config")) &&
(hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)::GetProcAddress(lib, "hostfxr_initialize_for_dotnet_command_line")) &&
(hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)::GetProcAddress(lib, "hostfxr_get_runtime_delegate")) &&
(hostfxr_close = (hostfxr_close_fn)::GetProcAddress(lib, "hostfxr_close")) &&
(hostfxr_set_error_writer = (hostfxr_set_error_writer_fn)::GetProcAddress(lib, "hostfxr_set_error_writer")))
Expand Down Expand Up @@ -199,7 +206,7 @@ struct error_writer
thread_local std::wstringstream error_writer::_message;

// Load and initialize .NET runtime and get assembly load function pointer
void init_runtime(const wchar_t* host_path, const wchar_t* host_config)
void init_runtime(const wchar_t* host_path, const wchar_t* host_config, bool is_self_contained)
{
struct hostfxr_context
{
Expand All @@ -215,7 +222,21 @@ void init_runtime(const wchar_t* host_path, const wchar_t* host_config)
hostfxr_context context;

error_writer writer;
HRESULT hr = hostfxr_initialize_for_runtime_config(host_config, nullptr, &context._handle);
HRESULT hr{};

if (is_self_contained)
{
// Self-contained scenario support is experimental and relies upon the application scenario
// entry-point. The logic here is to trick the hosting API into initializing as an application
// but call the "load assembly and get delegate" instead of "run main". This has impact
// on the TPA make-up and hence assembly loading in general since the TPA populates the default ALC.
hr = hostfxr_initialize_for_dotnet_command_line(1, &host_path, nullptr, &context._handle);
}
else
{
hr = hostfxr_initialize_for_runtime_config(host_config, nullptr, &context._handle);
}

if (hr == Success_HostAlreadyInitialized || hr == Success_DifferentRuntimeProperties)
{
hr = Success;
Expand All @@ -237,25 +258,38 @@ void init_runtime(const wchar_t* host_path, const wchar_t* host_config)
}
}

std::wstring find_mapped_target_assembly(std::filesystem::path host_config, winrt::hstring class_id)
std::optional<JsonObject> load_config_file(std::filesystem::path host_config)
{
std::wstring target_assembly;

try
{
auto config_file = StorageFile::GetFileFromPathAsync(host_config.c_str()).get();
auto json_string = FileIO::ReadTextAsync(config_file).get();
JsonObject root_object;
if (JsonObject::TryParse(json_string, root_object))
{
if (auto classes = root_object.TryLookup(L"activatableClasses"); classes)
return root_object;
}
}
catch (const winrt::hresult_error&)
{
}

return std::nullopt;
}

std::wstring find_mapped_target_assembly(JsonObject& host_config, winrt::hstring class_id)
{
std::wstring target_assembly;

try
{
if (auto classes = host_config.TryLookup(L"activatableClasses"); classes)
{
if (auto value_type = classes.ValueType(); value_type == JsonValueType::Object)
{
if (auto value_type = classes.ValueType(); value_type == JsonValueType::Object)
if (auto class_path = classes.GetObject().TryLookup(class_id); class_path)
{
if (auto class_path = classes.GetObject().TryLookup(class_id); class_path)
{
target_assembly = class_path.GetString().c_str();
}
target_assembly = class_path.GetString().c_str();
}
}
}
Expand All @@ -267,6 +301,31 @@ std::wstring find_mapped_target_assembly(std::filesystem::path host_config, winr
return target_assembly;
}

bool is_self_contained_requested(JsonObject& host_config)
{
if (auto runtime_options = host_config.TryLookup(L"runtimeOptions"); runtime_options)
{
if (runtime_options.ValueType() == JsonValueType::Object)
{
if (auto config_properties = runtime_options.GetObject().TryLookup(L"configProperties"); config_properties)
{
if (config_properties.ValueType() == JsonValueType::Object)
{
if (auto is_self_contained = config_properties.GetObject().TryLookup(L"CSWINRT_SELF_CONTAINED"); is_self_contained)
{
if (is_self_contained.ValueType() == JsonValueType::Boolean)
{
return is_self_contained.GetBoolean();
}
}
}
}
}
}

return false;
}

std::filesystem::path probe_for_target_assembly(std::filesystem::path host_module, winrt::hstring class_id)
{
auto host_file = host_module.filename();
Expand Down Expand Up @@ -346,18 +405,26 @@ void GetActivationFactory(void* hstr_class_id, void** activation_factory)
host_path.remove_filename();

// Load HostFxr and get exported hosting functions
load_hostfxr();
load_hostfxr(host_module.wstring().c_str());

// Determine host runtimeconfig.json from module name and load the runtime with it
winrt::hstring class_id;
winrt::copy_from_abi(class_id, hstr_class_id);
std::filesystem::path host_config = host_module;
host_config.replace_extension(L".runtimeconfig.json");
std::filesystem::path target_path;
bool is_self_contained = false;
if (std::filesystem::exists(host_config))
{
// Parse host config as JSON
auto host_config_json = load_config_file(host_config);
if (!host_config_json.has_value())
{
throw_hostfxr_hresult(InvalidConfigFile);
}

// If host runtimeconfig.json found, look for a target assembly mapping in it
auto target_assembly = find_mapped_target_assembly(host_config, class_id);
auto target_assembly = find_mapped_target_assembly(host_config_json.value(), class_id);
if (!target_assembly.empty())
{
target_path = host_module;
Expand All @@ -367,14 +434,17 @@ void GetActivationFactory(void* hstr_class_id, void** activation_factory)
throw_hostfxr_hresult(InvalidConfigFile);
}
}

// Check if self contained .NET runtime is requested
is_self_contained = is_self_contained_requested(host_config_json.value());
}
else
{
// TODO: create a reasonable default runtimeconfig.json?
throw_hostfxr_hresult(InvalidConfigFile);
}

init_runtime(host_module.wstring().c_str(), host_config.c_str());
init_runtime(host_module.wstring().c_str(), host_config.c_str(), is_self_contained);

// If no explicit target assembly mapping found, probe for it by naming convention
if (target_path.empty())
Expand Down

0 comments on commit d309bd1

Please sign in to comment.