From 39e930d9aaff250e0fd5019eeedaa40717a6c6fe Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Wed, 29 Apr 2020 19:28:50 +1000 Subject: Add DotNetCoreBootstrapperApplicationHost for an SCD-style .NET Core BA. --- src/dnchost/coreclrhost.h | 137 +++++++++++++++++ src/dnchost/dnchost.cpp | 229 ++++++++++++++++++++++++++++ src/dnchost/dnchost.def | 6 + src/dnchost/dnchost.h | 19 +++ src/dnchost/dnchost.vcxproj | 77 ++++++++++ src/dnchost/dncutil.cpp | 359 ++++++++++++++++++++++++++++++++++++++++++++ src/dnchost/dncutil.h | 36 +++++ src/dnchost/hostfxr.h | 96 ++++++++++++ src/dnchost/packages.config | 8 + src/dnchost/precomp.cpp | 3 + src/dnchost/precomp.h | 29 ++++ 11 files changed, 999 insertions(+) create mode 100644 src/dnchost/coreclrhost.h create mode 100644 src/dnchost/dnchost.cpp create mode 100644 src/dnchost/dnchost.def create mode 100644 src/dnchost/dnchost.h create mode 100644 src/dnchost/dnchost.vcxproj create mode 100644 src/dnchost/dncutil.cpp create mode 100644 src/dnchost/dncutil.h create mode 100644 src/dnchost/hostfxr.h create mode 100644 src/dnchost/packages.config create mode 100644 src/dnchost/precomp.cpp create mode 100644 src/dnchost/precomp.h (limited to 'src/dnchost') diff --git a/src/dnchost/coreclrhost.h b/src/dnchost/coreclrhost.h new file mode 100644 index 00000000..07f28735 --- /dev/null +++ b/src/dnchost/coreclrhost.h @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +// ***** ABOUT THIS HEADER ***** +// ************************************************************************************** +// +// This is the version on 2019-12-22 from +// https://github.com/dotnet/runtime/blob/master/src/coreclr/src/coreclr/hosts/inc/coreclrhost.h +// +// ************************************************************************************** +// **************************** + + +// +// APIs for hosting CoreCLR +// + +#ifndef __CORECLR_HOST_H__ +#define __CORECLR_HOST_H__ + +#if defined(_WIN32) && defined(_M_IX86) +#define CORECLR_CALLING_CONVENTION __stdcall +#else +#define CORECLR_CALLING_CONVENTION +#endif + +// For each hosting API, we define a function prototype and a function pointer +// The prototype is useful for implicit linking against the dynamic coreclr +// library and the pointer for explicit dynamic loading (dlopen, LoadLibrary) +#define CORECLR_HOSTING_API(function, ...) \ + extern "C" int CORECLR_CALLING_CONVENTION function(__VA_ARGS__); \ + typedef int (CORECLR_CALLING_CONVENTION *function##_ptr)(__VA_ARGS__) + +// +// Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain +// +// Parameters: +// exePath - Absolute path of the executable that invoked the ExecuteAssembly (the native host application) +// appDomainFriendlyName - Friendly name of the app domain that will be created to execute the assembly +// propertyCount - Number of properties (elements of the following two arguments) +// propertyKeys - Keys of properties of the app domain +// propertyValues - Values of properties of the app domain +// hostHandle - Output parameter, handle of the created host +// domainId - Output parameter, id of the created app domain +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_initialize, + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +// +// Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_shutdown, + void* hostHandle, + unsigned int domainId); + +// +// Shutdown CoreCLR. It unloads the app domain and stops the CoreCLR host. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// latchedExitCode - Latched exit code after domain unloaded +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_shutdown_2, + void* hostHandle, + unsigned int domainId, + int* latchedExitCode); + +// +// Create a native callable function pointer for a managed method. +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// entryPointAssemblyName - Name of the assembly which holds the custom entry point +// entryPointTypeName - Name of the type which holds the custom entry point +// entryPointMethodName - Name of the method which is the custom entry point +// delegate - Output parameter, the function stores a native callable function pointer to the delegate at the specified address +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_create_delegate, + void* hostHandle, + unsigned int domainId, + const char* entryPointAssemblyName, + const char* entryPointTypeName, + const char* entryPointMethodName, + void** delegate); + +// +// Execute a managed assembly with given arguments +// +// Parameters: +// hostHandle - Handle of the host +// domainId - Id of the domain +// argc - Number of arguments passed to the executed assembly +// argv - Array of arguments passed to the executed assembly +// managedAssemblyPath - Path of the managed assembly to execute (or NULL if using a custom entrypoint). +// exitCode - Exit code returned by the executed assembly +// +// Returns: +// HRESULT indicating status of the operation. S_OK if the assembly was successfully executed +// +CORECLR_HOSTING_API(coreclr_execute_assembly, + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +#undef CORECLR_HOSTING_API + +#endif // __CORECLR_HOST_H__ \ No newline at end of file diff --git a/src/dnchost/dnchost.cpp b/src/dnchost/dnchost.cpp new file mode 100644 index 00000000..c4b0d222 --- /dev/null +++ b/src/dnchost/dnchost.cpp @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +static DNCSTATE vstate = { }; + + +// internal function declarations + +static HRESULT LoadModulePaths( + __in DNCSTATE* pState + ); +static HRESULT LoadDncConfiguration( + __in DNCSTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ); +static HRESULT LoadRuntime( + __in DNCSTATE* pState + ); +static HRESULT LoadManagedBootstrapperApplicationFactory( + __in DNCSTATE* pState + ); + + +// function definitions + +extern "C" BOOL WINAPI DllMain( + IN HINSTANCE hInstance, + IN DWORD dwReason, + IN LPVOID /* pvReserved */ + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + ::DisableThreadLibraryCalls(hInstance); + vstate.hInstance = hInstance; + break; + + case DLL_PROCESS_DETACH: + vstate.hInstance = NULL; + break; + } + + return TRUE; +} + +extern "C" HRESULT WINAPI BootstrapperApplicationCreate( + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs, + __inout BOOTSTRAPPER_CREATE_RESULTS* pResults + ) +{ + HRESULT hr = S_OK; + IBootstrapperEngine* pEngine = NULL; + + // coreclr.dll doesn't support unloading, so the rest of the .NET Core hosting stack doesn't support it either. + // This means we also can't unload. + pResults->fDisableUnloading = TRUE; + + hr = BalInitializeFromCreateArgs(pArgs, &pEngine); + ExitOnFailure(hr, "Failed to initialize Bal."); + + if (!vstate.fInitialized) + { + hr = XmlInitialize(); + BalExitOnFailure(hr, "Failed to initialize XML."); + + hr = LoadModulePaths(&vstate); + BalExitOnFailure(hr, "Failed to get the host base path."); + + hr = LoadDncConfiguration(&vstate, pArgs); + BalExitOnFailure(hr, "Failed to get the dnc configuration."); + + vstate.fInitialized = TRUE; + } + + if (!vstate.fInitializedRuntime) + { + hr = LoadRuntime(&vstate); + BalExitOnFailure(hr, "Failed to load .NET Core runtime."); + + vstate.fInitializedRuntime = TRUE; + + hr = LoadManagedBootstrapperApplicationFactory(&vstate); + BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application factory."); + } + + BalLog(BOOTSTRAPPER_LOG_LEVEL_STANDARD, "Loading .NET Core SCD bootstrapper application."); + + hr = vstate.pAppFactory->Create(pArgs, pResults); + BalExitOnFailure(hr, "Failed to create the .NET Core bootstrapper application."); + +LExit: + ReleaseNullObject(pEngine); + + return hr; +} + +extern "C" void WINAPI BootstrapperApplicationDestroy() +{ + BalUninitialize(); +} + +static HRESULT LoadModulePaths( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = PathForCurrentProcess(&pState->sczModuleFullPath, pState->hInstance); + BalExitOnFailure(hr, "Failed to get the full host path."); + + hr = PathGetDirectory(pState->sczModuleFullPath, &pState->sczAppBase); + BalExitOnFailure(hr, "Failed to get the directory of the full process path."); + + hr = PathConcat(pState->sczAppBase, DNC_ASSEMBLY_FILE_NAME, &pState->sczManagedHostPath); + BalExitOnFailure(hr, "Failed to create managed host path."); + +LExit: + return hr; +} + +static HRESULT LoadDncConfiguration( + __in DNCSTATE* pState, + __in const BOOTSTRAPPER_CREATE_ARGS* pArgs + ) +{ + HRESULT hr = S_OK; + IXMLDOMDocument* pixdManifest = NULL; + IXMLDOMNode* pixnHost = NULL; + IXMLDOMNode* pixnPayload = NULL; + LPWSTR sczPayloadId = NULL; + LPWSTR sczPayloadXPath = NULL; + LPWSTR sczPayloadName = NULL; + + hr = XmlLoadDocumentFromFile(pArgs->pCommand->wzBootstrapperApplicationDataPath, &pixdManifest); + BalExitOnFailure(hr, "Failed to load BalManifest '%ls'", pArgs->pCommand->wzBootstrapperApplicationDataPath); + + hr = XmlSelectSingleNode(pixdManifest, L"/BootstrapperApplicationData/WixBalBAFactoryAssembly", &pixnHost); + BalExitOnFailure(hr, "Failed to get WixBalBAFactoryAssembly element."); + + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + BalExitOnRootFailure(hr, "Failed to find WixBalBAFactoryAssembly element in bootstrapper application config."); + } + + hr = XmlGetAttributeEx(pixnHost, L"PayloadId", &sczPayloadId); + BalExitOnFailure(hr, "Failed to get WixBalBAFactoryAssembly/@PayloadId."); + + hr = StrAllocFormatted(&sczPayloadXPath, L"/BootstrapperApplicationData/WixPayloadProperties[@Payload='%ls']", sczPayloadId); + BalExitOnFailure(hr, "Failed to format BAFactoryAssembly payload XPath."); + + hr = XmlSelectSingleNode(pixdManifest, sczPayloadXPath, &pixnPayload); + if (S_FALSE == hr) + { + hr = E_NOTFOUND; + } + BalExitOnFailure(hr, "Failed to find WixPayloadProperties node for BAFactoryAssembly PayloadId: %ls.", sczPayloadId); + + hr = XmlGetAttributeEx(pixnPayload, L"Name", &sczPayloadName); + BalExitOnFailure(hr, "Failed to get BAFactoryAssembly payload Name."); + + hr = PathConcat(pArgs->pCommand->wzBootstrapperWorkingFolder, sczPayloadName, &pState->sczBaFactoryAssemblyPath); + BalExitOnFailure(hr, "Failed to create BaFactoryAssemblyPath."); + + LPCWSTR wzFileName = PathFile(pState->sczBaFactoryAssemblyPath); + LPCWSTR wzExtension = PathExtension(pState->sczBaFactoryAssemblyPath); + if (!wzExtension) + { + BalExitOnFailure(hr = E_FAIL, "BaFactoryAssemblyPath has no extension."); + } + + hr = StrAllocString(&pState->sczBaFactoryAssemblyName, wzFileName, wzExtension - wzFileName); + BalExitOnFailure(hr, "Failed to copy BAFactoryAssembly payload Name."); + + hr = StrAllocString(&pState->sczBaFactoryDepsJsonPath, pState->sczBaFactoryAssemblyPath, wzExtension - pState->sczBaFactoryAssemblyPath); + BalExitOnFailure(hr, "Failed to initialize deps json path."); + + hr = StrAllocString(&pState->sczBaFactoryRuntimeConfigPath, pState->sczBaFactoryDepsJsonPath, 0); + BalExitOnFailure(hr, "Failed to initialize runtime config path."); + + hr = StrAllocConcat(&pState->sczBaFactoryDepsJsonPath, L".deps.json", 0); + BalExitOnFailure(hr, "Failed to concat extension to deps json path."); + + hr = StrAllocConcat(&pState->sczBaFactoryRuntimeConfigPath, L".runtimeconfig.json", 0); + BalExitOnFailure(hr, "Failed to concat extension to runtime config path."); + +LExit: + ReleaseStr(sczPayloadName); + ReleaseObject(pixnPayload); + ReleaseStr(sczPayloadXPath); + ReleaseStr(sczPayloadId); + ReleaseObject(pixnHost); + ReleaseObject(pixdManifest); + + return hr; +} + +static HRESULT LoadRuntime( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = DnchostLoadRuntime( + &pState->hostfxrState, + pState->sczModuleFullPath, + pState->sczManagedHostPath, + pState->sczBaFactoryDepsJsonPath, + pState->sczBaFactoryRuntimeConfigPath); + + return hr; +} + +static HRESULT LoadManagedBootstrapperApplicationFactory( + __in DNCSTATE* pState + ) +{ + HRESULT hr = S_OK; + + hr = DnchostCreateFactory( + &pState->hostfxrState, + pState->sczBaFactoryAssemblyName, + pState->sczBaFactoryAssemblyPath, + &pState->pAppFactory); + + return hr; +} diff --git a/src/dnchost/dnchost.def b/src/dnchost/dnchost.def new file mode 100644 index 00000000..4488df94 --- /dev/null +++ b/src/dnchost/dnchost.def @@ -0,0 +1,6 @@ +; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +EXPORTS + BootstrapperApplicationCreate + BootstrapperApplicationDestroy diff --git a/src/dnchost/dnchost.h b/src/dnchost/dnchost.h new file mode 100644 index 00000000..e498edaf --- /dev/null +++ b/src/dnchost/dnchost.h @@ -0,0 +1,19 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +struct DNCSTATE +{ + BOOL fInitialized; + BOOL fInitializedRuntime; + HINSTANCE hInstance; + LPWSTR sczModuleFullPath; + LPWSTR sczAppBase; + LPWSTR sczManagedHostPath; + LPWSTR sczBaFactoryAssemblyName; + LPWSTR sczBaFactoryAssemblyPath; + LPWSTR sczBaFactoryDepsJsonPath; + LPWSTR sczBaFactoryRuntimeConfigPath; + HOSTFXR_STATE hostfxrState; + IBootstrapperApplicationFactory* pAppFactory; +}; diff --git a/src/dnchost/dnchost.vcxproj b/src/dnchost/dnchost.vcxproj new file mode 100644 index 00000000..e2b8b529 --- /dev/null +++ b/src/dnchost/dnchost.vcxproj @@ -0,0 +1,77 @@ + + + + + + + + + Debug + Win32 + + + Release + Win32 + + + + {B6F70281-6583-4138-BB7F-AABFEBBB3CA2} + DynamicLibrary + v141 + Unicode + dnchost + dnchost.def + + + + + ..\..\packages\runtime.win-x86.Microsoft.NETCore.DotNetAppHost.3.1.3\runtimes\win-x86\native\ + shlwapi.lib;$(NetHostPath)nethost.lib + + + + + + Create + + + + + + + + + + + + + + + + PreserveNewest + False + + + + + $(BaseOutputPath)obj;$(NetHostPath);%(AdditionalIncludeDirectories) + + + + + {0D780900-C2FF-4FA2-8CB5-8A19768724C5} + true + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/src/dnchost/dncutil.cpp b/src/dnchost/dncutil.cpp new file mode 100644 index 00000000..996bf086 --- /dev/null +++ b/src/dnchost/dncutil.cpp @@ -0,0 +1,359 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// https://github.com/dotnet/runtime/blob/master/src/installer/corehost/error_codes.h +#define HostApiBufferTooSmall 0x80008098 + +// internal function declarations + +static HRESULT GetHostfxrPath( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ); +static HRESULT LoadHostfxr( + __in HOSTFXR_STATE* pState + ); +static HRESULT InitializeHostfxr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ); +static HRESULT InitializeCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ); +static HRESULT LoadCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzCoreClrPath + ); +static HRESULT StartCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in size_t cProperties, + __in LPCWSTR* propertyKeys, + __in LPCWSTR* propertyValues + ); + + +// function definitions + +HRESULT DnchostLoadRuntime( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ) +{ + HRESULT hr = S_OK; + + hr = GetHostfxrPath(pState, wzNativeHostPath); + BalExitOnFailure(hr, "Failed to find hostfxr."); + + hr = LoadHostfxr(pState); + BalExitOnFailure(hr, "Failed to load hostfxr."); + + hr = InitializeHostfxr(pState, wzManagedHostPath, wzDepsJsonPath, wzRuntimeConfigPath); + BalExitOnFailure(hr, "Failed to initialize hostfxr."); + + hr = InitializeCoreClr(pState, wzNativeHostPath); + BalExitOnFailure(hr, "Failed to initialize coreclr."); + +LExit: + return hr; +} + +HRESULT DnchostCreateFactory( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath, + __out IBootstrapperApplicationFactory** ppAppFactory + ) +{ + HRESULT hr = S_OK; + PFNCREATEBAFACTORY pfnCreateBAFactory = NULL; + + hr = pState->pfnCoreclrCreateDelegate( + pState->pClrHandle, + pState->dwDomainId, + DNC_ASSEMBLY_FULL_NAME, + DNC_ENTRY_TYPE, + DNC_STATIC_ENTRY_METHOD, + reinterpret_cast(&pfnCreateBAFactory)); + BalExitOnFailure(hr, "Failed to create delegate in app domain."); + + *ppAppFactory = pfnCreateBAFactory(wzBaFactoryAssemblyName, wzBaFactoryAssemblyPath); + +LExit: + return hr; +} + +static HRESULT GetHostfxrPath( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ) +{ + HRESULT hr = S_OK; + get_hostfxr_parameters getHostfxrParameters = { }; + int nrc = 0; + size_t cchHostFxrPath = MAX_PATH; + + getHostfxrParameters.size = sizeof(get_hostfxr_parameters); + getHostfxrParameters.assembly_path = wzNativeHostPath; + + // get_hostfxr_path does a full search on every call, so + // minimize the number of calls + // need to loop + for (;;) + { + cchHostFxrPath *= 2; + hr = StrAlloc(&pState->sczHostfxrPath, cchHostFxrPath); + BalExitOnFailure(hr, "Failed to allocate hostFxrPath."); + + nrc = get_hostfxr_path(pState->sczHostfxrPath, &cchHostFxrPath, &getHostfxrParameters); + if (HostApiBufferTooSmall != nrc) + { + break; + } + } + if (0 != nrc) + { + BalExitOnFailure(hr = nrc, "GetHostfxrPath failed"); + } + +LExit: + return hr; +} + +static HRESULT LoadHostfxr( + __in HOSTFXR_STATE* pState + ) +{ + HRESULT hr = S_OK; + HMODULE hHostfxr; + + hHostfxr = ::LoadLibraryExW(pState->sczHostfxrPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + BalExitOnNullWithLastError(hHostfxr, hr, "Failed to load hostfxr from '%ls'.", pState->sczHostfxrPath); + + pState->pfnHostfxrInitializeForApp = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_initialize_for_dotnet_command_line")); + BalExitOnNullWithLastError(pState->pfnHostfxrInitializeForApp, hr, "Failed to get procedure address for hostfxr_initialize_for_dotnet_command_line."); + + pState->pfnHostfxrGetRuntimeProperties = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_get_runtime_properties")); + BalExitOnNullWithLastError(pState->pfnHostfxrGetRuntimeProperties, hr, "Failed to get procedure address for hostfxr_get_runtime_properties."); + + pState->pfnHostfxrSetErrorWriter = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_set_error_writer")); + BalExitOnNullWithLastError(pState->pfnHostfxrSetErrorWriter, hr, "Failed to get procedure address for hostfxr_set_error_writer."); + + pState->pfnHostfxrClose = reinterpret_cast(::GetProcAddress(hHostfxr, "hostfxr_close")); + BalExitOnNullWithLastError(pState->pfnHostfxrClose, hr, "Failed to get procedure address for hostfxr_close."); + +LExit: + // Never unload the module since it isn't meant to be unloaded. + + return hr; +} + +static void HOSTFXR_CALLTYPE DnchostErrorWriter( + __in LPCWSTR wzMessage + ) +{ + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "error from hostfxr: %ls", wzMessage); +} + +static HRESULT InitializeHostfxr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ) +{ + HRESULT hr = S_OK; + + pState->pfnHostfxrSetErrorWriter(static_cast(&DnchostErrorWriter)); + + LPCWSTR argv[] = { + L"exec", + L"--depsfile", + wzDepsJsonPath, + L"--runtimeconfig", + wzRuntimeConfigPath, + wzManagedHostPath, + }; + hr = pState->pfnHostfxrInitializeForApp(sizeof(argv)/sizeof(LPWSTR), argv, NULL, &pState->hostContextHandle); + BalExitOnFailure(hr, "HostfxrInitializeForApp failed"); + +LExit: + return hr; +} + +static HRESULT InitializeCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath + ) +{ + HRESULT hr = S_OK; + int32_t rc = 0; + LPCWSTR* rgPropertyKeys = NULL; + LPCWSTR* rgPropertyValues = NULL; + size_t cProperties = 0; + LPWSTR* rgDirectories = NULL; + UINT cDirectories = 0; + LPWSTR sczCoreClrPath = NULL; + + // We are not using hostfxr as it was intended to be used. We need to initialize hostfxr so that it properly initializes hostpolicy - + // there are pieces of the framework such as AssemblyDependencyResolver that won't work without that. We also need hostfxr to find a + // compatible framework for framework-dependent deployed BAs. We had to use hostfxr_initialize_for_dotnet_command_line since + // hostfxr_initialize_for_runtime_config doesn't currently (3.x) support self-contained deployed BAs. That means we're supposed to + // start the runtime through hostfxr_run_app, but that method shuts down the runtime before returning. We actually want to call + // hostfxr_get_runtime_delegate, but that method currently requires hostfxr to be initialized through + // hostfxr_initialize_for_runtime_config. So we're forced to locate coreclr.dll and manually load the runtime ourselves. + + // Unfortunately, that's not the only problem. hostfxr has global state that tracks whether it started the runtime. While we keep our + // hostfxr_handle open, everyone that calls the hostfxr_initialize_* methods will block until we have started the runtime through + // hostfxr or closed our handle. If we close the handle, then hostfxr could potentially try to load a second runtime into the + // process, which is not supported. We're going to just keep our handle open since no one else in the process should be trying to + // start the runtime anyway. + + rc = pState->pfnHostfxrGetRuntimeProperties(pState->hostContextHandle, &cProperties, rgPropertyKeys, rgPropertyValues); + if (HostApiBufferTooSmall != rc) + { + BalExitOnFailure(hr = rc, "HostfxrGetRuntimeProperties failed to return required size."); + } + + rgPropertyKeys = static_cast(MemAlloc(sizeof(LPWSTR) * cProperties, TRUE)); + rgPropertyValues = static_cast(MemAlloc(sizeof(LPWSTR) * cProperties, TRUE)); + if (!rgPropertyKeys || !rgPropertyValues) + { + BalExitOnFailure(hr = E_OUTOFMEMORY, "Failed to allocate buffers for runtime properties."); + } + + hr = pState->pfnHostfxrGetRuntimeProperties(pState->hostContextHandle, &cProperties, rgPropertyKeys, rgPropertyValues); + BalExitOnFailure(hr, "HostfxrGetRuntimeProperties failed."); + + for (DWORD i = 0; i < cProperties; ++i) + { + if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, 0, rgPropertyKeys[i], -1, L"NATIVE_DLL_SEARCH_DIRECTORIES", -1)) + { + hr = StrSplitAllocArray(&rgDirectories, &cDirectories, rgPropertyValues[i], L";"); + BalExitOnFailure(hr, "Failed to split NATIVE_DLL_SEARCH_DIRECTORIES '%ls'", rgPropertyValues[i]); + } + } + + for (DWORD i = 0; i < cDirectories; ++i) + { + hr = PathConcat(rgDirectories[i], L"coreclr.dll", &sczCoreClrPath); + BalExitOnFailure(hr, "Failed to allocate path to coreclr."); + + if (::PathFileExists(sczCoreClrPath)) + { + break; + } + else + { + ReleaseNullStr(sczCoreClrPath); + } + } + + if (!sczCoreClrPath) + { + for (DWORD i = 0; i < cProperties; ++i) + { + BalLog(BOOTSTRAPPER_LOG_LEVEL_ERROR, "%ls: %ls", rgPropertyKeys[i], rgPropertyValues[i]); + } + BalExitOnFailure(hr = E_FILENOTFOUND, "Failed to locate coreclr.dll."); + } + + hr = LoadCoreClr(pState, sczCoreClrPath); + BalExitOnFailure(hr, "Failed to load coreclr."); + + hr = StartCoreClr(pState, wzNativeHostPath, cProperties, rgPropertyKeys, rgPropertyValues); + BalExitOnFailure(hr, "Failed to start coreclr."); + +LExit: + MemFree(rgDirectories); + MemFree(rgPropertyValues); + MemFree(rgPropertyKeys); + ReleaseStr(sczCoreClrPath); + + return hr; +} + +static HRESULT LoadCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzCoreClrPath + ) +{ + HRESULT hr = S_OK; + HMODULE hModule = NULL; + + hModule = ::LoadLibraryExW(wzCoreClrPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + BalExitOnNullWithLastError(hModule, hr, "Failed to load coreclr.dll from '%ls'.", wzCoreClrPath); + + pState->pfnCoreclrInitialize = reinterpret_cast(::GetProcAddress(hModule, "coreclr_initialize")); + BalExitOnNullWithLastError(pState->pfnCoreclrInitialize, hr, "Failed to get procedure address for coreclr_initialize."); + + pState->pfnCoreclrCreateDelegate = reinterpret_cast(::GetProcAddress(hModule, "coreclr_create_delegate")); + BalExitOnNullWithLastError(pState->pfnCoreclrCreateDelegate, hr, "Failed to get procedure address for coreclr_create_delegate."); + +LExit: + // Never unload the module since coreclr doesn't support it. + + return hr; +} + +static HRESULT StartCoreClr( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in size_t cProperties, + __in LPCWSTR* propertyKeys, + __in LPCWSTR* propertyValues + ) +{ + HRESULT hr = S_OK; + LPSTR szNativeHostPath = NULL; + LPSTR* rgPropertyKeys = NULL; + LPSTR* rgPropertyValues = NULL; + + rgPropertyKeys = static_cast(MemAlloc(sizeof(LPSTR) * cProperties, TRUE)); + rgPropertyValues = static_cast(MemAlloc(sizeof(LPSTR) * cProperties, TRUE)); + if (!rgPropertyKeys || !rgPropertyValues) + { + BalExitOnFailure(hr = E_OUTOFMEMORY, "Failed to allocate buffers for runtime properties."); + } + + hr = StrAnsiAllocString(&szNativeHostPath, wzNativeHostPath, 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert module path to UTF8: %ls", wzNativeHostPath); + + for (DWORD i = 0; i < cProperties; ++i) + { + hr = StrAnsiAllocString(&rgPropertyKeys[i], propertyKeys[i], 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert property key to UTF8: %ls", propertyKeys[i]); + + hr = StrAnsiAllocString(&rgPropertyValues[i], propertyValues[i], 0, CP_UTF8); + BalExitOnFailure(hr, "Failed to convert property value to UTF8: %ls", propertyValues[i]); + } + + hr = pState->pfnCoreclrInitialize(szNativeHostPath, "MBA", cProperties, (LPCSTR*)rgPropertyKeys, (LPCSTR*)rgPropertyValues, &pState->pClrHandle, &pState->dwDomainId); + BalExitOnFailure(hr, "CoreclrInitialize failed."); + +LExit: + for (DWORD i = 0; i < cProperties; ++i) + { + if (rgPropertyKeys) + { + ReleaseStr(rgPropertyKeys[i]); + } + + if (rgPropertyValues) + { + ReleaseStr(rgPropertyValues[i]); + } + } + ReleaseMem(rgPropertyValues); + ReleaseMem(rgPropertyKeys); + ReleaseStr(szNativeHostPath); + + return hr; +} diff --git a/src/dnchost/dncutil.h b/src/dnchost/dncutil.h new file mode 100644 index 00000000..1a7c16e3 --- /dev/null +++ b/src/dnchost/dncutil.h @@ -0,0 +1,36 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +typedef IBootstrapperApplicationFactory* (STDMETHODCALLTYPE* PFNCREATEBAFACTORY)( + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath + ); + +struct HOSTFXR_STATE +{ + LPWSTR sczHostfxrPath; + hostfxr_handle hostContextHandle; + hostfxr_initialize_for_dotnet_command_line_fn pfnHostfxrInitializeForApp; + hostfxr_get_runtime_properties_fn pfnHostfxrGetRuntimeProperties; + hostfxr_set_error_writer_fn pfnHostfxrSetErrorWriter; + hostfxr_close_fn pfnHostfxrClose; + coreclr_initialize_ptr pfnCoreclrInitialize; + coreclr_create_delegate_ptr pfnCoreclrCreateDelegate; + void* pClrHandle; + UINT dwDomainId; +}; + +HRESULT DnchostLoadRuntime( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzNativeHostPath, + __in LPCWSTR wzManagedHostPath, + __in LPCWSTR wzDepsJsonPath, + __in LPCWSTR wzRuntimeConfigPath + ); + +HRESULT DnchostCreateFactory( + __in HOSTFXR_STATE* pState, + __in LPCWSTR wzBaFactoryAssemblyName, + __in LPCWSTR wzBaFactoryAssemblyPath, + __out IBootstrapperApplicationFactory** ppAppFactory + ); diff --git a/src/dnchost/hostfxr.h b/src/dnchost/hostfxr.h new file mode 100644 index 00000000..85e6e0ab --- /dev/null +++ b/src/dnchost/hostfxr.h @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +// ***** ABOUT THIS HEADER ***** +// ************************************************************************************** +// +// This is the version on 2019-12-22 from +// https://github.com/dotnet/runtime/blob/master/src/installer/corehost/cli/hostfxr.h +// +// ************************************************************************************** +// **************************** + + +#ifndef __HOSTFXR_H__ +#define __HOSTFXR_H__ + +#include +#include + +#if defined(_WIN32) + #define HOSTFXR_CALLTYPE __cdecl + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define HOSTFXR_CALLTYPE + typedef char char_t; +#endif + +enum hostfxr_delegate_type +{ + hdt_com_activation, + hdt_load_in_memory_assembly, + hdt_winrt_activation, + hdt_com_register, + hdt_com_unregister, + hdt_load_assembly_and_get_function_pointer +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( + const int argc, + const char_t **argv, + const char_t *host_path, + const char_t *dotnet_root, + const char_t *app_path); + +typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); +typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); + +typedef void* hostfxr_handle; +struct hostfxr_initialize_parameters +{ + size_t size; + const char_t *host_path; + const char_t *dotnet_root; +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_dotnet_command_line_fn)( + int argc, + const char_t **argv, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_runtime_config_fn)( + const char_t *runtime_config_path, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + /*out*/ const char_t **value); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_set_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t *value); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_properties_fn)( + const hostfxr_handle host_context_handle, + /*inout*/ size_t * count, + /*out*/ const char_t **keys, + /*out*/ const char_t **values); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_run_app_fn)(const hostfxr_handle host_context_handle); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + enum hostfxr_delegate_type type, + /*out*/ void **delegate); + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +#endif //__HOSTFXR_H__ \ No newline at end of file diff --git a/src/dnchost/packages.config b/src/dnchost/packages.config new file mode 100644 index 00000000..c8911ea5 --- /dev/null +++ b/src/dnchost/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/dnchost/precomp.cpp b/src/dnchost/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/dnchost/precomp.cpp @@ -0,0 +1,3 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" diff --git a/src/dnchost/precomp.h b/src/dnchost/precomp.h new file mode 100644 index 00000000..6a12ef67 --- /dev/null +++ b/src/dnchost/precomp.h @@ -0,0 +1,29 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "coreclrhost.h" +#include "hostfxr.h" +#include "dncutil.h" +#include "dnchost.h" -- cgit v1.2.3-55-g6feb