Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Documentation/design-docs/WinRT-activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Managed WinRT Activation of .NET Core components

As part of supporting a complete story for XAML Islands on .NET Core, we should provide a mechanism of activating .NET Core WinRT components. To do so, we will follow the path of the COM and IJW activations and provide a new host for activating these components in a manner similar to native WinRT components, which we will call the `winrthost`. We are creating a separate host instead of combining with the COM host because the COM and WinRT hosts (although generally similar in design in Windows) have very different activation and resolution paths. As a result, we would not be able to reuse much code at all. Additionally, the WinRT host does not require a `clsidmap` or similar functionality to function. If we were to combine the two hosts, we would have to emit an empty clsidmap when creating the host and modify it even though the WinRT portion has no dependencies on any embedded resources.

Comment thread
jkoritzinsky marked this conversation as resolved.
## Requirements

* Discover all installed versions of .NET Core.
* Load the appropriate version of .NET Core for the class if a .NET Core instance is not running, or validate the currently existing .NET Core instance can satisfy the class requirement.
* Return an [`IActivationFactory`](https://docs.microsoft.com/windows/desktop/api/activation/nn-activation-iactivationfactory) implementation that will construct an instance of the .NET WinRT class.

## Native WinRT Activation

In the native (C++/CX, C++/WRL, or C++/WinRT) world, building a WinRT Component named `MyComponent` produces files named `MyComponent.dll`, `MyComponent.winmd`. In this world, the `winmd` contains the metadata describing the types provided by the component, and the code implementing said types is compiled into the `dll`. When an application wants to activate a component, it will call [`RoGetActivationFactory`](https://docs.microsoft.com/windows/desktop/api/roapi/nf-roapi-rogetactivationfactory). The operating system will then search through various areas in the filesystem to find the correct component for the class name. Starting in Windows 10 19H1, there is now support for declaring that specific classes come from components that live side-by-side with the application, similar to Reg-Free COM. After finding the correct component, the OS will load the component via `CoLoadLibrary` and then call the `DllGetActivationFactory` entrypoint, which gets an activation factory for the class by name.

## Proposed Managed WinRT Activation

In the managed (.NET) world, we put all of the code that implements the component into the `winmd`. So, when running in an AppContainer/`.appx`, a .NET component only needs one file. However, when outside of an AppContainer, there needs to be some sort of host to activate runtime. We will supply a host that implements the [`DllGetActivationFactory`](https://docs.microsoft.com/previous-versions//br205771(v=vs.85)) entrypoint. When `DllGetActivationFactory` is called, the following will occur:
Comment thread
jkoritzinsky marked this conversation as resolved.

1) If a [`.runtimeconfig.json`](https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md) file exists adjacent to the shim assembly (`<shim_name>.runtimeconfig.json`), that file will be used to describe CLR configuration details. The documentation for the `.runtimeconfig.json` format defines under what circumstances this file may be optional.
Comment thread
vitek-karas marked this conversation as resolved.
2) Using the existing `hostfxr` library, attempt to discover the desired CLR and target [framework](https://docs.microsoft.com/en-us/dotnet/core/packages#frameworks).
* If a CLR is active with the process, the requested CLR version will be validated against that CLR. If version satisfiability fails, activation will fail.
* If a CLR is **not** active with the process, an attempt will be made to create a satisfying CLR instance.
* Failure to create an instance will result in activation failure.
3) A request to the CLR will be made to load the corresponding WinRT type for the given type name into the runtime.
* This request will use the runtime's current support for resolving WinRT types to correctly resolve them.
* The ability to load an assembly from memory will require exposing a new function that can be called from `hostfxr`, as well as a new API in `System.Private.CoreLib` on a new class in `Internal.Runtime.InteropServices`:

```csharp
namespace Internal.Runtime.InteropServices.WindowsRuntime
{
public static class ActivationFactoryLoader
{
public unsafe static int GetActivationFactory(
char* componentPath,
[MarshalAs(UnmanagedType.HString)] string typeName,
[MarshalAs(UnmanagedType.Interface)] out IActivationFactory activationFactory);
}
}
```

Note this API would not be exposed outside of `System.Private.CoreLib` unless we decide to do so. The runtime loads WinRT components via a different binder than .NET assemblies, so the WinRT component assemblies themselves will be loaded by the WinRT binder, but all assemblies that the WinRT component depends on will be loaded into an isolated `AssemblyLoadContext`.

To match the user-visible architecture of native WinRT Activation, when building a Windows Metadata component (`winmdobj`), we will copy the `winrthost` to the user's output directory and rename it to be the same name as the `.winmd` but with a `.dll` extension. For example, if the user creates a project `MyComponent` and is building a Windows Metadata component, we will copy the `winrthost` to the output directory for `MyComponent` and rename it to be `MyComponent.dll`. From a user's perspective, they will activate WinRT objects via `MyComponent.dll`, the same as if the component was a native WinRT component.

## Error reporting

If the runtime activation fails, we want to ensure that the user can diagnose the failure. However, we don't want to write directly to the `stderr` of a process that we don't own. So, we will redirect the trace stream to point to a local stream. In the case of failure to activate the runtime, we will report the error code via [`RoOriginateErrorW`](https://docs.microsoft.com/windows/desktop/api/roerrorapi/nf-roerrorapi-rooriginateerrorw), which will present the user with the error according to the error reporting flags they have set via [`RoSetErrorReportingFlags`](https://docs.microsoft.com/windows/desktop/api/roerrorapi/nf-roerrorapi-roseterrorreportingflags).

## Open issues

* The tool that converts the `.winmdobj` to a `.winmd`, WinMDExp, is only available in Desktop MSBuild and is not available in the .NET Core MSBuild distribution. Additionally, WinMDExp only supports full PDBs and will fail if given portable PDBs.
Comment thread
vitek-karas marked this conversation as resolved.
* To build a `.winmdobj`, the user will need to reference a contract assembly that includes the `Windows.Foundation` namespace. There are new packages that expose these contract assemblies, but we should ensure that these contract assemblies will be released by the time that we finalize our Managed WinRT Component support.
* Writing unit tests for the WinRT host without modifying system-global state requires the new Reg-Free WinRT support, which is Windows 10 19H1 only.
3 changes: 3 additions & 0 deletions signing/sign.proj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
<FilesToSign Include="$(OutDir)corehost/**/ijwhost.dll">
<Authenticode>$(CertificateId)</Authenticode>
</FilesToSign>
<FilesToSign Include="$(OutDir)corehost/**/winrthost.dll">
<Authenticode>$(CertificateId)</Authenticode>
</FilesToSign>
<FilesToSign Include="$(OutDir)corehost/**/nethost.dll">
<Authenticode>$(CertificateId)</Authenticode>
</FilesToSign>
Expand Down
3 changes: 3 additions & 0 deletions src/corehost/build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
<HostFiles Include="ijwhost">
<FileDescription>.NET Core IJW Host</FileDescription>
</HostFiles>
<HostFiles Include="winrthost">
<FileDescription>.NET Core WinRT Host</FileDescription>
</HostFiles>
<HostFiles Include="nethost">
<FileDescription>.NET Core Component Host</FileDescription>
</HostFiles>
Expand Down
1 change: 1 addition & 0 deletions src/corehost/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ add_subdirectory(test)
if(WIN32)
add_subdirectory(comhost)
add_subdirectory(ijwhost)
add_subdirectory(winrthost)
endif()
1 change: 1 addition & 0 deletions src/corehost/cli/comhost/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ set(SOURCES
comhost.cpp
../fxr_resolver.cpp
clsidmap.cpp
../redirected_error_writer.cpp
../fxr/fx_ver.cpp
../json/casablanca/src/json/json.cpp
../json/casablanca/src/json/json_parsing.cpp
Expand Down
117 changes: 38 additions & 79 deletions src/corehost/cli/comhost/comhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

#include "comhost.h"
#include "redirected_error_writer.h"
#include "hostfxr.h"
#include "fxr_resolver.h"
#include "pal.h"
Expand Down Expand Up @@ -45,69 +46,48 @@ using com_activation_fn = int(*)(com_activation_context*);

namespace
{
pal::stringstream_t & get_comhost_error_stream()
int get_com_activation_delegate(pal::string_t *app_path, com_activation_fn *delegate)
{
thread_local static pal::stringstream_t comhost_errors;
return load_fxr_and_get_delegate(
hostfxr_delegate_type::com_activation,
[](const pal::string_t& host_path, pal::string_t* app_path_out)
{
pal::string_t app_path_local{ host_path };

return comhost_errors;
}
// Strip the comhost suffix to get the 'app'
size_t idx = app_path_local.rfind(_X(".comhost.dll"));
assert(idx != pal::string_t::npos);
app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".dll"));

void reset_comhost_error_stream()
{
pal::stringstream_t newstream;
get_comhost_error_stream().swap(newstream);
}
*app_path_out = std::move(app_path_local);

void comhost_error_writer(const pal::char_t* msg)
{
get_comhost_error_stream() << msg;
return StatusCode::Success;
},
delegate,
app_path
);
}

int get_com_activation_delegate(pal::string_t *app_path, com_activation_fn *delegate)
void report_com_error_info(const GUID& guid, pal::string_t errs)
{
pal::string_t host_path;
if (!pal::get_own_module_path(&host_path) || !pal::realpath(&host_path))
{
trace::error(_X("Failed to resolve full path of the current host module [%s]"), host_path.c_str());
return StatusCode::CoreHostCurHostFindFailure;
}

pal::string_t dotnet_root;
pal::string_t fxr_path;
if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path))
ICreateErrorInfo *cei;
if (!errs.empty() && SUCCEEDED(::CreateErrorInfo(&cei)))
{
return StatusCode::CoreHostLibMissingFailure;
}
if (SUCCEEDED(cei->SetGUID(guid)))
{
if (SUCCEEDED(cei->SetDescription((LPOLESTR)errs.c_str())))
{
IErrorInfo *ei;
if (SUCCEEDED(cei->QueryInterface(__uuidof(ei), (void**)&ei)))
{
::SetErrorInfo(0, ei);
ei->Release();
}
}
}

// Load library
pal::dll_t fxr;
if (!pal::load_library(&fxr_path, &fxr))
{
trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str());
trace::error(_X(" - Installing .NET Core prerequisites might help resolve this problem."));
trace::error(_X(" %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL);
return StatusCode::CoreHostLibLoadFailure;
cei->Release();
}

// Leak fxr

auto get_runtime_delegate = (hostfxr_get_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate");
if (get_runtime_delegate == nullptr)
return StatusCode::CoreHostEntryPointFailure;

pal::string_t app_path_local{ host_path };

// Strip the comhost suffix to get the 'app'
size_t idx = app_path_local.rfind(_X(".comhost.dll"));
assert(idx != pal::string_t::npos);
app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".dll"));

*app_path = std::move(app_path_local);

auto set_error_writer_fn = (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);

return get_runtime_delegate(host_path.c_str(), dotnet_root.c_str(), app_path->c_str(), hostfxr_delegate_type::com_activation, (void**)delegate);
}
}

Expand All @@ -129,35 +109,14 @@ COM_API HRESULT STDMETHODCALLTYPE DllGetClassObject(
com_activation_fn act;
{
trace::setup();
reset_comhost_error_stream();

error_writer_scope_t writer_scope(comhost_error_writer);
reset_redirected_error_writer();
error_writer_scope_t writer_scope(redirected_error_writer);

int ec = get_com_activation_delegate(&app_path, &act);
if (ec != StatusCode::Success)
{
// Create an IErrorInfo instance with the failure data.
pal::string_t errs = get_comhost_error_stream().str();

ICreateErrorInfo *cei;
if (!errs.empty() && SUCCEEDED(::CreateErrorInfo(&cei)))
{
if (SUCCEEDED(cei->SetGUID(rclsid)))
{
if (SUCCEEDED(cei->SetDescription((LPOLESTR)errs.c_str())))
{
IErrorInfo *ei;
if (SUCCEEDED(cei->QueryInterface(__uuidof(ei), (void**)&ei)))
{
::SetErrorInfo(0, ei);
ei->Release();
}
}
}

cei->Release();
}

report_com_error_info(rclsid, std::move(get_redirected_error_string()));
return __HRESULT_FROM_WIN32(ec);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/corehost/cli/fxr/fx_muxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ enum class coreclr_delegate_type
{
invalid,
com_activation,
load_in_memory_assembly
load_in_memory_assembly,
winrt_activation
};

class fx_muxer_t
Expand Down
2 changes: 2 additions & 0 deletions src/corehost/cli/fxr/hostfxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ coreclr_delegate_type hostfxr_delegate_to_coreclr_delegate(hostfxr_delegate_type
return coreclr_delegate_type::com_activation;
case hostfxr_delegate_type::load_in_memory_assembly:
return coreclr_delegate_type::load_in_memory_assembly;
case hostfxr_delegate_type::winrt_activation:
return coreclr_delegate_type::winrt_activation;
}
return coreclr_delegate_type::invalid;
}
Expand Down
52 changes: 52 additions & 0 deletions src/corehost/cli/fxr_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,62 @@
#define _COREHOST_CLI_FXR_RESOLVER_H_

#include <pal.h>
#include "hostfxr.h"
#include "trace.h"
#include "utils.h"
#include "error_codes.h"

namespace fxr_resolver
{
bool try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path);
}

template<typename THostNameToAppNameCallback, typename TDelegate>
int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCallback host_path_to_app_path, TDelegate* delegate, pal::string_t* out_app_path = nullptr)
{
pal::dll_t fxr;

pal::string_t host_path;
if (!pal::get_own_module_path(&host_path) || !pal::realpath(&host_path))
{
trace::error(_X("Failed to resolve full path of the current host module [%s]"), host_path.c_str());
return StatusCode::CoreHostCurHostFindFailure;
}

pal::string_t dotnet_root;
pal::string_t fxr_path;
if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path))
{
return StatusCode::CoreHostLibMissingFailure;
}

// Load library
if (!pal::load_library(&fxr_path, &fxr))
{
trace::error(_X("The library %s was found, but loading it from %s failed"), LIBFXR_NAME, fxr_path.c_str());
trace::error(_X(" - Installing .NET Core prerequisites might help resolve this problem."));
trace::error(_X(" %s"), DOTNET_CORE_INSTALL_PREREQUISITES_URL);
return StatusCode::CoreHostLibLoadFailure;
}

// Leak fxr

auto get_delegate_from_hostfxr = (hostfxr_get_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_runtime_delegate");
if (get_delegate_from_hostfxr == nullptr)
return StatusCode::CoreHostEntryPointFailure;

pal::string_t app_path;

pal::string_t* app_path_to_use = out_app_path != nullptr ? out_app_path : &app_path;

pal::hresult_t status = host_path_to_app_path(host_path, app_path_to_use);

if (status != StatusCode::Success)
{
return status;
}

return get_delegate_from_hostfxr(host_path.c_str(), dotnet_root.c_str(), app_path_to_use->c_str(), type, (void**)delegate);
}

#endif //_COREHOST_CLI_FXR_RESOLVER_H_
3 changes: 2 additions & 1 deletion src/corehost/cli/hostfxr.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ using hostfxr_main_startupinfo_fn = int32_t(*)(
enum class hostfxr_delegate_type
{
com_activation,
load_in_memory_assembly
load_in_memory_assembly,
winrt_activation
};

using hostfxr_get_delegate_fn = int32_t(*)(
Expand Down
7 changes: 7 additions & 0 deletions src/corehost/cli/hostpolicy/hostpolicy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,13 @@ SHARED_API int corehost_get_coreclr_delegate(coreclr_delegate_type type, void**
"Internal.Runtime.InteropServices.InMemoryAssemblyLoader",
"LoadInMemoryAssembly",
delegate);
case coreclr_delegate_type::winrt_activation:
return coreclr->create_delegate(
"System.Private.CoreLib",
"Internal.Runtime.InteropServices.WindowsRuntime.ActivationFactoryLoader",
"GetActivationFactory",
delegate
);
default:
return StatusCode::LibHostInvalidArgs;
}
Expand Down
Loading