* Add new comhost library for COM activation
 - Supports reading clsidmap as file or resource
 - Supports registration and unregistration of COM server

* Replace COREHOST_MAKE_DLL define with EXPORT_SHARED_API

* Convert CoreClr instance loading API from a flat C-style to a class API . This will make having multiple CoreClr instances in the same process easier in the future.

* Move internal execute* APIs to the fx_muxer.cpp compilation unit
Add initial/erroring implementation for get_com_activation_delegate() to fx_muxer_t.

* Create coreclr_property_bag_t for property management.

* Extract coreclr property creations into a reusable class.

* Report process ID during test run.

* Remove the new corehost_ensure_load() export since the previous semantics
  of reinitializing hostpolicy were designed for testing.

* Fix P/Invoke signature for hostfxr_get_native_search_directories to define
  the calling convention and remove usage of StringBuilder.

* Properly propagate the host command on reinitialization
Remove additional uses of global variables where possible

* Don't add the COM server assembly to the TPA
Ensure the COM server assembly deps.json isn't added to the AppContext

* Feedback on error propagation
This commit is contained in:
Aaron Robinson 2019-02-12 09:38:10 -08:00 коммит произвёл GitHub
Родитель 5277b856a6
Коммит 1d95ece515
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
40 изменённых файлов: 2170 добавлений и 719 удалений

Просмотреть файл

@ -1,8 +1,8 @@
# COM Activation for .NET Core on Windows
# COM activation for .NET Core on Windows
## Purpose
In order to more fully support the vast number of existing .NET Framework users in their transition to .NET Core, support of the COM activation scenario in .NET Core is required. Without this support it is not possible for many .NET Framework consumers to even consider transitioning to .NET Core. The intent of this document is to describe aspects of COM activation for a .NET class written for .NET Core. This support includes but is not limited to activation scenarios such as the [`CoCreateInstance()`](https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-cocreateinstance)API in C/C++ or from within a [Windows Script Host](https://docs.microsoft.com/en-us/windows/desktop/com/using-com-objects-in-windows-script-host) instance.
In order to more fully support the vast number of existing .NET Framework users in their transition to .NET Core, support of the COM activation scenario in .NET Core is required. Without this support it is not possible for many .NET Framework consumers to even consider transitioning to .NET Core. The intent of this document is to describe aspects of COM activation for a .NET class written for .NET Core. This support includes but is not limited to activation scenarios such as the [`CoCreateInstance()`](https://docs.microsoft.com/windows/desktop/api/combaseapi/nf-combaseapi-cocreateinstance) API in C/C++ or from within a [Windows Script Host](https://docs.microsoft.com/windows/desktop/com/using-com-objects-in-windows-script-host) instance.
COM activation in this document is currently limited to in-proc scenarios. Scenarios involving out-of-proc COM activation are deferred.
@ -10,10 +10,10 @@ COM activation in this document is currently limited to in-proc scenarios. Scena
* 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 [`IClassFactory`](https://docs.microsoft.com/en-us/windows/desktop/api/unknwnbase/nn-unknwnbase-iclassfactory) implementation that will construct an instance of the .NET class.
* Return an [`IClassFactory`](https://docs.microsoft.com/windows/desktop/api/unknwnbase/nn-unknwnbase-iclassfactory) implementation that will construct an instance of the .NET class.
* Support the discrimination of concurrently loaded CLR versions.
### Environment Matrix
### Environment matrix
The following list represents an exhaustive activation matrix.
@ -31,11 +31,11 @@ The following list represents an exhaustive activation matrix.
One of the basic issues with the activation of a .NET class within a COM environment is the loading or discovery of an appropriate CLR instance. The .NET Framework addressed this issue through a system wide shim library (described below). The .NET Core scenario has different requirements and limitations on system impact and as such an identical solution may not be optimal or tenable.
### .NET Framework Class COM Activation
### .NET Framework class COM activation
The .NET Framework uses a shim library (`mscoree.dll`) to facilitate the loading of the CLR into a process performing activation - one of the many uses of `mscoree.dll`. When .NET Framework 4.0 was released, `mscoreei.dll` was introduced to provide a level of indirection between the system installed shim (`mscoree.dll`) and a specific framework shim as well as to enable side-by-side CLR scenarios. An important consideration of the system wide shim is that of servicing. Servicing `mscoree.dll` is difficult since any process with a loaded .NET Framework instance will have the shim loaded, thus requiring a system reboot in order to service the shim.
During .NET class registration, the shim is identified as the in-proc server for the class. Additional metadata is inserted into the registry to indicate what .NET assembly to load and what type to activate. For example, in addition to the typical [in-proc server](https://docs.microsoft.com/en-us/windows/desktop/com/inprocserver32) registry values the following values are added to the registry for the `TypeLoadException` class.
During .NET class registration, the shim is identified as the in-proc server for the class. Additional metadata is inserted into the registry to indicate what .NET assembly to load and what type to activate. For example, in addition to the typical [in-proc server](https://docs.microsoft.com/windows/desktop/com/inprocserver32) registry values the following values are added to the registry for the `TypeLoadException` class.
```
"Assembly"="mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
@ -43,73 +43,115 @@ During .NET class registration, the shim is identified as the in-proc server for
"RuntimeVersion"="v1.1.4322"
```
The above registration is typically done with the [`RegAsm.exe`](https://docs.microsoft.com/en-us/dotnet/framework/tools/regasm-exe-assembly-registration-tool) tool. Alternatively, registry scripts can be generated by `RegAsm.exe`.
The above registration is typically done with the [`RegAsm.exe`](https://docs.microsoft.com/dotnet/framework/tools/regasm-exe-assembly-registration-tool) tool. Alternatively, registry scripts can be generated by `RegAsm.exe`.
### .NET Core Class COM Activation
### .NET Core class COM activation
In .NET Core, our intent will be to avoid a system wide shim library. This decision may add additional cost for deployment scenarios, but will reduce servicing and engineering costs by making deployment more explicit and less magic.
The current .NET Core hosting solutions are described in detail at [Documentation/design-docs/host-components.md](https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/host-components.md). Along with the existing hosts an additional activation host library will be added. This library (henceforth identified as 'shim') will export the required functions for COM class activation and registration and act in a way similar to `mscoree.dll`.
The current .NET Core hosting solutions are described in detail at [Documentation/design-docs/host-components.md](https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/host-components.md). Along with the existing hosts an additional customizable COM activation host library (`comhost.dll`) will be added. This library (henceforth identified as 'shim') will export the required functions for COM class activation and registration and act in a way similar to .NET Framework's `mscoree.dll`.
>[`HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);`](https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-dllgetclassobject)
>[`HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv);`](https://docs.microsoft.com/windows/desktop/api/combaseapi/nf-combaseapi-dllgetclassobject)
>[`HRESULT DllCanUnloadNow();`](https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-dllcanunloadnow)
>[`HRESULT DllCanUnloadNow();`](https://docs.microsoft.com/windows/desktop/api/combaseapi/nf-combaseapi-dllcanunloadnow)
>[`HRESULT DllRegisterServer();`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682162(v=vs.85).aspx)
>[`HRESULT DllRegisterServer();`](https://msdn.microsoft.com/library/windows/desktop/ms682162(v=vs.85).aspx)
>[`HRESULT DllUnregisterServer();`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms691457(v=vs.85).aspx)
>[`HRESULT DllUnregisterServer();`](https://msdn.microsoft.com/library/windows/desktop/ms691457(v=vs.85).aspx)
When `DllGetClassObject()` is called in a COM activation scenario, the following will occur:
When `DllGetClassObject()` is called in a COM activation scenario, the following steps will occur. The calling of `DllGetClassObject()` is usually accomplished through an implicit or explcit call to `CoCreateInstance()`.
1) Determine additional registration information needed for activation.
* The shim will check for an embedded manifest. If the shim does not contain an embedded manifest, the shim will check if a file with the `<shim_name>.clsidmap` naming format exists adjacent to it. Build tooling will be expected to handle shim customization, including renaming the shim to be based on the managed assembly's name (e.g. `NetComServer.dll` could have a custom shim called `NetComServer.shim.dll`).
* The manifest will contain a mapping from [`CLSID`](https://docs.microsoft.com/en-us/windows/desktop/com/com-class-objects-and-clsids) to managed assembly name and the [Fully-Qualified Name](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names) for the type. The exact format of this manifest is an implementation detail, but will be identical whether it is embedded or a loose file.
* The manifest will define an exhaustive list of .NET classes it is permitted to provide.
* 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.
1) 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.
1) A request to the CLR will be made via a new method for class activation within a COM environment.
* The ability to load the assembly and create an `IClassFactory` instance will require exposing a new function that can be called from `hostfxr`.
* Example of a possible API in `System.Private.CoreLib` on a new `ComActivator` class in the `System.Runtime.InteropServices` namespace:
* The shim will check for an embedded manifest. If the shim does not contain an embedded manifest, the shim will check if a file with the `<shim_name>.clsidmap` naming format exists adjacent to it. Build tooling handles shim customization, including renaming the shim to be based on the managed assembly's name (e.g. `NetComServer.dll` will have a custom shim called `NetComServer.comhost.dll`).
* The manifest will contain a mapping from [`CLSID`](https://docs.microsoft.com/windows/desktop/com/com-class-objects-and-clsids) to managed assembly name and the [Fully-Qualified Name](https://docs.microsoft.com/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names) for the type. The format of this manifest is defined below. The shim's embedded mapping always takes precedence and in the case an embedded mapping is found, a `.clsidmap` file on disk will never be used.
* The manifest will define an exhaustive list of .NET classes the shim is permitted to provide.
* If a [`.runtimeconfig.json`](https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md) file exists adjacent to the target managed assembly (`<assembly>.runtimeconfig.json`), that file is used to describe the target framework and CLR configuration. The documentation for the `.runtimeconfig.json` format defines under what circumstances this file may be optional.
1) The `DllGetClassObject()` function verifies the `CLSID` mapping has a mapping for the `CLSID`.
* If the `CLSID` is unknown in the mapping the traditional `CLASS_E_CLASSNOTAVAILABLE` is returned.
1) The shim attempts to load the latest version of the `hostfxr` library and retrieves the `hostfxr_get_com_activation_delegate()` export.
1) The target assembly name is computed by stripping off the `.comhost.dll` prefix and replacing it with `.dll`. Using the name of the target assembly, the path to the `.runtimeconfig.json` file is then computed.
1) The `hostfxr_get_com_activation_delegate()` export is called.
1) Based on the `.runtimeconfig.json` the [framework](https://docs.microsoft.com/dotnet/core/packages#frameworks) to use can be determined and the appropriate `hostpolicy` library path is computed.
1) The `hostpolicy` library is loaded and various exports are retrieved.
* If a `hostpolicy` instance is already loaded, the one presently loaded is re-used.
* **Prior to 3.0 GA** No validation is done to determine if the loaded `hostpolicy` can satisfy the current assembly's `.runtimeconfig.json`.
* **At 3.0 GA** If a CLR is active within the process, the requested CLR version will be validated against that CLR. If version satisfiability fails, activation will fail.
1) The `corehost_load()` export is called to initialize `hostpolicy`.
- Prior to .NET Core 3.0, during application activation the `corehost_load()` export would always initialize `hostpolicy` regardless if initialization had already been performed. For .NET Core 3.0, calling the function again will not re-initialize `hostpolicy`, but simply return.
1) The `corehost_get_com_activation_delegate()` export from `hostpolicy` is called.
1) The `corehost_get_com_activation_delegate()` export determines if the associated `coreclr` library has been loaded and if so, uses the existing activated CLR instance. If a CLR instance is not available, `hostpolicy` will load `coreclr` and activate a new CLR instance.
* **Prior to 3.0 GA** No validation is done to determine if the current running coreclr instance can satisfy the current assembly's `.runtimeconfig.json`.
* **At 3.0 GA** If a CLR is active within the process, the requested CLR version will be validated against that CLR. If version satisfiability fails, activation will fail.
1) A request to the CLR is made to create a managed delegate to a static "activation" method. The delegate is returned to the shim to attempt activation of the requested class.
* The details of the activation API are implementation defined, but presently reside in `System.Private.CoreLib` on the `Internal.Runtime.InteropServices.ComActivator` class:
``` csharp
[StructLayout(LayoutKind.Sequential)]
public struct ComActivationContextInternal
[CLSCompliant(false)]
public unsafe struct ComActivationContextInternal
{
public Guid ClassId;
public Guid InterfaceId;
public IntPtr AssemblyNameBuffer;
public IntPtr TypeNameBuffer;
public char* AssemblyPathBuffer;
public char* AssemblyNameBuffer;
public char* TypeNameBuffer;
public IntPtr ClassFactoryDest;
}
public static class ComActivator
{
...
[CLSCompliant(false)]
public static int GetClassFactoryForTypeInternal(ref ComActivationContextInternal context);
...
}
```
Note this API would not be exposed outside of `System.Private.CoreLib`.
* The loading of the assembly will take place in a new [`AssemblyLoadContext`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=netcore-2.1) for dependency isolation.
* **Complete details on ALC semantics as they relate to class activation and lifetime are TBD**
1) The `IClassFactory` instance will be returned to the caller of `DllGetClassObject()`.
Note this API is not exposed outside of `System.Private.CoreLib` and is subject to change at any time.
* The loading of the assembly will take place in a new [`AssemblyLoadContext`](https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext) for dependency isolation. Each assembly path will get a seperate `AssemblyLoadContext`. This means that if an assembly provides multiple COM servers all of the servers from that assembly will reside in the same `AssemblyLoadContext`.
* The created `AssemblyLoadContext` will use an [`AssemblyDependencyResolver`](https://github.com/dotnet/corefx/issues/33165) that was supplied with the path to the assembly to load assemblies.
1) The `IClassFactory` instance is returned to the caller of `DllGetClassObject()` to attempt class activation.
The `DllCanUnloadNow()` function will always return `S_FALSE` indicating the shim is never able to be unloaded. This matches .NET Framework semantics and can be adjusted in the future if needed.
The `DllCanUnloadNow()` function will always return `S_FALSE` indicating the shim is never able to be unloaded. This matches .NET Framework semantics but may be adjusted in the future if needed.
The `DllRegisterServer()` and `DllUnregisterServer()` functions will adhere to the [COM registration contract](https://docs.microsoft.com/en-us/windows/desktop/com/classes-and-servers) and enable registration and unregistration of the classes defined in the `.clsidmap` manifest.
The `DllRegisterServer()` and `DllUnregisterServer()` functions adhere to the [COM registration contract](https://docs.microsoft.com/windows/desktop/com/classes-and-servers) and enable registration and unregistration of the classes defined in the `CLSID` mapping manifest. Discovery of the mapping manifest is identical to that which occurs during a call to `DllGetClassObject()`.
#### Class Registration
##### CLSID map format
Two options exist for registration and are a function of the intent of the class's author. The .NET Core platform will impose the deployment of a shim instance with a `.clsidmap` manifest. In order to address potential security concerns, the .NET Core tool chain will also permit the creation of a customized shim instance with an embedded `.clsidmap`. This customized shim will allow for the implicit signing of the `.clsidmap` manifest.
The `CLSID` mapping manifest is a JSON format (`.clsidmap` extension when on disk) that defines a mapping from `CLSID` to an assembly name and type name tuple. Each `CLSID` mapping is a key in the outer JSON object.
##### Registry
``` json
{
"<clsid>": {
"assembly": "<assembly_name>",
"type": "<type_name>"
}
}
```
Class registration in the registry for .NET Core classes is greatly simplified and is now identical to that of a non-managed COM class. This is possible due to the pressence of the aforementioned `.clsidmap` manifest. The application developer will be able to use the traditional [`regsvr32.exe`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/regsvr32) tool for class registration.
### .NET Core COM server creation
##### Registration-Free
1) A new .NET Core class library project is created using [`dotnet.exe`][dotnet_link].
1) A class is defined that has the [`GuidAttribute("<GUID>")`][guid_link] and the [`ComVisibleAttribute(true)`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.comvisibleattribute).
- In .NET Core, unlike .NET Framework, there is no generated class interface generation (i.e. `IClassX`). This means it is advantageous for users to have the class implement a marshalable interface.
1) The `UseComHost` property is added to the project file.
- i.e. `<UseComHost>true</UseComHost>`
1) During class project build, the following actions occur if the `UseComHost` property is `true`:
1) A `.runtimeconfig.json` file is created for the assembly.
1) The resulting assembly is interrogated for classes with the attributes defined above and a `CLSID` map is created on disk (`.clsidmap`).
1) The target Framework's shim binary (i.e. `comhost.dll`) is copied to the local output directory.
1) The `comhost.dll` binary is renamed to `<assembly>.comhost.dll`.
1) The generated `CLSID` map (`.clsidmap`) is embedded as a resource in the renamed `<assembly>.comhost.dll` binary.
[RegFree COM for .NET](https://docs.microsoft.com/en-us/dotnet/framework/interop/configure-net-framework-based-com-components-for-reg) is another style of registration, but does not require registry access. This approach is complicated by the use of [application manifests](https://docs.microsoft.com/en-us/windows/desktop/SbsCs/application-manifests), but does have benefits for limiting environment impact and simplifying deployment. A severe limitation of this approach is that in order to use RegFree COM with a .NET class, the Window OS assumes the use of `mscoree.dll` for the in-proc server. Without a change in the Windows OS, this assumption in the RegFree .NET scenario makes the existing manifest approach a broken scenario for .NET Core.
### .NET Core COM server registration
Two options exist for registration and are a function of the intent of the class's author. The .NET Core platform will impose the deployment of a shim instance with a `.clsidmap` manifest. In order to address potential security concerns, the .NET Core tool chain by default will embedded the `.clsidmap` in the customized shim. When the `.clsidmap` is embedded the customized shim allows for the implicit signing of the `.clsidmap` manifest.
#### Registry
Class registration in the registry for .NET Core classes is greatly simplified and is now identical to that of a non-managed COM class. This is possible due to the presence of the aforementioned `.clsidmap` manifest. The application developer will be able to use the traditional [`regsvr32.exe`](https://docs.microsoft.com/windows-server/administration/windows-commands/regsvr32) tool for class registration.
#### Registration-Free
[RegFree COM for .NET](https://docs.microsoft.com/dotnet/framework/interop/configure-net-framework-based-com-components-for-reg) is another style of registration, but does not require registry access. This approach is complicated by the use of [application manifests](https://docs.microsoft.com/windows/desktop/SbsCs/application-manifests), but does have benefits for limiting environment impact and simplifying deployment. A severe limitation of this approach is that in order to use RegFree COM with a .NET class, the Window OS assumes the use of `mscoree.dll` for the in-proc server. Without a change in the Windows OS, this assumption in the RegFree .NET scenario makes the existing manifest approach a broken scenario for .NET Core.
An example of a RegFree manifest for a .NET Framework class is below - note the absence of specifying a hosting server library (i.e. `mscoree.dll` is implied for the `clrClass` element).
@ -132,7 +174,7 @@ An example of a RegFree manifest for a .NET Framework class is below - note the
Due to the above issues with traditional RegFree manifests and .NET classes, an alternative system must be employed to enable a low-impact style of class registration for .NET Core.
The proposed alternative for RegFree is as follows:
The .NET Core steps for RegFree are as follows:
1) The native application will still define an application manifest, but instead of specifying the managed assembly as a dependency the application will define the shim as a dependent assembly.
``` xml
@ -148,22 +190,22 @@ The proposed alternative for RegFree is as follows:
<!-- RegFree COM - CoreCLR Shim -->
<assemblyIdentity
type="win32"
name="CoreShim.X"
name="NetComServer.comhost.X"
version="1.0.0.0" />
</dependentAssembly>
</dependency>
</assembly>
```
1) The user would then also define a [SxS](https://docs.microsoft.com/en-us/windows/desktop/sbscs/about-side-by-side-assemblies-) manifest for the shim. Both the SxS manifest _and_ the shim library will need to be app-local for the scenario to work. Note that the application developer is responsible for defining the shim's manifest. An example shim manifest is defined below and with it the SxS logic would naturally know to query the shim for the desired class. Note that multiple `comClass` tags can be added.
1) The tool chain can optionally generate a [SxS](https://docs.microsoft.com/windows/desktop/sbscs/about-side-by-side-assemblies-) manifest for the shim. Both the SxS manifest _and_ the shim library will need to be app-local for the scenario to work. Note that the application developer is responsible for adding to or merging the generated shim's manifest with one the user may have defined for other scenarios. An example shim manifest is defined below and with it the SxS logic will naturally know to query the shim for the desired class. Note that multiple `comClass` tags can be added.
``` xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="CoreShim.X"
name="NetComServer.comhost.X"
version="1.0.0.0" />
<file name="CoreShim.dll">
<file name="NetComServer.comhost.dll">
<!-- NetComServer.Server -->
<comClass
clsid="{3C58BBC9-3966-4B58-8EE2-398CBBC9FDC4}"
@ -172,28 +214,27 @@ The proposed alternative for RegFree is as follows:
</assembly>
```
1) When the native application starts up, its SxS manifest will be read and dependency assemblies discovered. Exported COM classes will also be registered in the process.
1) At runtime, during a class activation call, COM will consult the SxS registration and discover the shim library should be used to load the class. The shim will then consult the `.clsidmap` manifest - first checking if the manifest is embedded - and attempt to map the `CLSID` to a managed assembly type tuple.
1) COM activation then proceeds as defined above starting with a call to the shim's `DllGetClassObject()` export.
The [`dotnet.exe`][dotnet_link] tool could be made to generate the SxS `.manifest` files.
## Compatibility Concerns
## Compatibility concerns
* Side-by-side concerns with the registration of classes that are defined in both .NET Framework and .NET Core.
- i.e. Both classes have identical [`Guid`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.guidattribute?view=netcore-2.1) values.
- i.e. Both classes have identical [`Guid`][guid_link] values.
* RegFree COM will not work the same between .NET Framework and .NET Core.
- See details above.
* Servicing of the .NET Framework shim (`mscoree.dll`) was done at the system level. In the .NET Core scenario the onus is on the application developer to have a servicing process in place for the shim.
* There is no support for different versions of .NET Core running concurrently. This is not the case in .NET Framework where a 2.0 and 4.0 runtime can run in parallel. A potential future solution would be support for out-of-proc COM servers.
## References
[Calling COM Components from .NET Clients](https://msdn.microsoft.com/en-us/library/ms973800.aspx)
[Calling COM Components from .NET Clients](https://msdn.microsoft.com/library/ms973800.aspx)
[Calling a .NET Component from a COM Component](https://msdn.microsoft.com/en-us/library/ms973802.aspx)
[Calling a .NET Component from a COM Component](https://msdn.microsoft.com/library/ms973802.aspx)
[Using COM Types in Managed Code](https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/3y76b69k%28v%3dvs.100%29)
[Using COM Types in Managed Code](https://docs.microsoft.com/previous-versions/dotnet/netframework-4.0/3y76b69k%28v%3dvs.100%29)
[Exposing .NET Framework Components to COM](https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/zsfww439(v%3dvs.100))
[Exposing .NET Framework Components to COM](https://docs.microsoft.com/dotnet/framework/interop/exposing-dotnet-components-to-com)
<!-- Common links -->
[dotnet_link]: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet?tabs=netcore21
[com_activation_context]: https://docs.microsoft.com/en-us/windows/desktop/sbscs/activation-contexts
[guid_link]: https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.guidattribute
[dotnet_link]: https://docs.microsoft.com/dotnet/core/tools/dotnet

Просмотреть файл

@ -99,6 +99,7 @@
<HostVersion Condition="'$(UseShippedHostPackage)' != 'true'">$(ProductVersion)</HostVersion>
<HostVersion Condition="'$(UseShippedHostPackage)' == 'true'">2.0.0</HostVersion>
<!-- The AppHostVersion is used for all hosts that aren't dotnet.exe -->
<UseShippedAppHostPackage>false</UseShippedAppHostPackage>
<AppHostVersion Condition="'$(UseShippedAppHostPackage)' != 'true'">$(ProductVersion)</AppHostVersion>
<AppHostVersion Condition="'$(UseShippedAppHostPackage)' == 'true'">2.0.0</AppHostVersion>

Просмотреть файл

@ -25,7 +25,7 @@ if /i "%3" == "arm64" (set cm_BaseRid=win10&&set cm_Arch=ARM64&&set __ExtraCma
set __LatestCommit=%4
set __HostVersion=%5
set __AppHostVersion=%6
set __HostResolverVersion=%7
set __HostFxrVersion=%7
set __HostPolicyVersion=%8
:: Form the base RID to be used if we are doing a portable build
@ -41,8 +41,8 @@ for /f "delims=" %%a in ('powershell -NoProfile -ExecutionPolicy ByPass "& .\Win
popd
:DoGen
echo "%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostResolverVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams%
"%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostResolverVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams%
echo "%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_COMHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams%
"%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_COMHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams%
endlocal
GOTO :DONE

Просмотреть файл

@ -34,7 +34,7 @@ if /i [%1] == [rid] ( set __TargetRid=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [toolsetDir] ( set "__ToolsetDir=%2"&&shift&&shift&goto Arg_Loop)
if /i [%1] == [hostver] (set __HostVersion=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [apphostver] (set __AppHostVersion=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [fxrver] (set __HostResolverVersion=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [fxrver] (set __HostFxrVersion=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [policyver] (set __HostPolicyVersion=%2&&shift&&shift&goto Arg_Loop)
if /i [%1] == [commit] (set __CommitSha=%2&&shift&&shift&goto Arg_Loop)
@ -122,9 +122,9 @@ exit /b 1
:GenVSSolution
:: Regenerate the VS solution
echo Calling "%__nativeWindowsDir%\gen-buildsys-win.bat %~dp0 "%__VSVersion%" %__BuildArch% %__CommitSha% %__HostVersion% %__AppHostVersion% %__HostResolverVersion% %__HostPolicyVersion%"
echo Calling "%__nativeWindowsDir%\gen-buildsys-win.bat %~dp0 "%__VSVersion%" %__BuildArch% %__CommitSha% %__HostVersion% %__AppHostVersion% %__HostFxrVersion% %__HostPolicyVersion% %__PortableBuild%"
pushd "%__IntermediatesDir%"
call "%__nativeWindowsDir%\gen-buildsys-win.bat" %~dp0 "%__VSVersion%" %__BuildArch% %__CommitSha% %__HostVersion% %__AppHostVersion% %__HostResolverVersion% %__HostPolicyVersion% %__PortableBuild%
call "%__nativeWindowsDir%\gen-buildsys-win.bat" %~dp0 "%__VSVersion%" %__BuildArch% %__CommitSha% %__HostVersion% %__AppHostVersion% %__HostFxrVersion% %__HostPolicyVersion% %__PortableBuild%
popd
:CheckForProj

Просмотреть файл

@ -83,10 +83,12 @@
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\dotnet\$(ConfigurationGroup)\dotnet.pdb" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\apphost\$(ConfigurationGroup)\apphost.exe" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\apphost\$(ConfigurationGroup)\apphost.pdb" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\comhost\$(ConfigurationGroup)\comhost.dll" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\comhost\$(ConfigurationGroup)\comhost.pdb" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\hostpolicy\$(ConfigurationGroup)\$(HostPolicyBaseName)" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\hostpolicy\$(ConfigurationGroup)\hostpolicy.pdb" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\fxr\$(ConfigurationGroup)\$(DotnetHostFxrBaseName)" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\fxr\$(ConfigurationGroup)\hostfxr.pdb" />
<CMakeOutput Include="$(IntermediateOutputRootPath)corehost\cli\fxr\$(ConfigurationGroup)\hostfxr.pdb" />
</ItemGroup>
<Copy SourceFiles="@(CMakeOutput)"

Просмотреть файл

@ -2,3 +2,7 @@ add_subdirectory(apphost)
add_subdirectory(dotnet)
add_subdirectory(fxr)
add_subdirectory(hostpolicy)
if(WIN32)
add_subdirectory(comhost)
endif()

Просмотреть файл

@ -6,14 +6,15 @@
#include "coreclr.h"
#include "libhost.h"
arguments_t::arguments_t() :
managed_application(_X("")),
host_path(_X("")),
app_root(_X("")),
app_argc(0),
app_argv(nullptr),
core_servicing(_X("")),
deps_path(_X(""))
arguments_t::arguments_t()
: managed_application(_X(""))
, host_mode(host_mode_t::invalid)
, host_path(_X(""))
, app_root(_X(""))
, app_argc(0)
, app_argv(nullptr)
, core_servicing(_X(""))
, deps_path(_X(""))
{
}
@ -62,7 +63,22 @@ bool parse_arguments(
arguments_t& args)
{
pal::string_t managed_application_path;
if (init.host_mode != host_mode_t::apphost)
if (init.host_mode == host_mode_t::apphost)
{
// Find the managed app in the same directory
managed_application_path = init.host_info.app_path;
args.app_argv = &argv[1];
args.app_argc = argc - 1;
}
else if (init.host_mode == host_mode_t::libhost)
{
// Find the managed assembly in the same directory
managed_application_path = init.host_info.app_path;
assert(argc == 0 && argv == nullptr);
}
else
{
// First argument is managed app
if (argc < 2)
@ -75,14 +91,6 @@ bool parse_arguments(
args.app_argc = argc - 2;
args.app_argv = &argv[2];
}
else
{
// Find the managed app in the same directory
managed_application_path = init.host_info.app_path;
args.app_argv = &argv[1];
args.app_argc = argc - 1;
}
return init_arguments(
managed_application_path,
@ -105,6 +113,7 @@ bool init_arguments(
const std::vector<pal::string_t>& probe_paths,
arguments_t& args)
{
args.host_mode = host_mode;
args.host_path = host_info.host_path;
args.additional_deps_serialized = additional_deps_serialized;

Просмотреть файл

@ -90,6 +90,7 @@ struct probe_config_t
struct arguments_t
{
host_mode_t host_mode;
pal::string_t host_path;
pal::string_t app_root;
pal::string_t deps_path;

Просмотреть файл

@ -7,7 +7,7 @@
#include "trace.h"
#include "breadcrumbs.h"
breadcrumb_writer_t::breadcrumb_writer_t(bool enabled, const std::unordered_set<pal::string_t>* files)
breadcrumb_writer_t::breadcrumb_writer_t(bool enabled, const std::unordered_set<pal::string_t> &files)
: m_status(false)
, m_enabled(enabled)
, m_files(files)
@ -40,8 +40,8 @@ void breadcrumb_writer_t::begin_write()
return;
}
trace::verbose(_X("Number of breadcrumb files to write is %d"), m_files->size());
if (m_files->empty())
trace::verbose(_X("Number of breadcrumb files to write is %d"), m_files.size());
if (m_files.empty())
{
m_status = true;
return;
@ -56,7 +56,7 @@ void breadcrumb_writer_t::begin_write()
void breadcrumb_writer_t::write_callback()
{
bool successful = true;
for (const auto& file : *m_files)
for (const auto& file : m_files)
{
pal::string_t file_path = m_breadcrumb_store;
pal::string_t file_name = _X("netcore,") + file;

Просмотреть файл

@ -9,7 +9,7 @@
class breadcrumb_writer_t
{
public:
breadcrumb_writer_t(bool enabled, const std::unordered_set<pal::string_t>* files);
breadcrumb_writer_t(bool enabled, const std::unordered_set<pal::string_t> &files);
~breadcrumb_writer_t();
void begin_write();
@ -21,7 +21,7 @@ private:
pal::string_t m_breadcrumb_store;
std::thread m_thread;
const std::unordered_set<pal::string_t>* m_files;
const std::unordered_set<pal::string_t> &m_files;
bool m_enabled;
volatile bool m_status;
};

Просмотреть файл

@ -0,0 +1,58 @@
# Copyright (c) .NET Foundation and contributors. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
cmake_minimum_required (VERSION 2.6)
project(comhost)
include(../setup.cmake)
# Include directories
include_directories(./)
include_directories(../)
include_directories(../fxr)
include_directories(../../)
include_directories(../../common)
include_directories(../json/casablanca/include)
set(SOURCES
../../corehost.cpp
exports.cpp
clsidmap.cpp
../../common/trace.cpp
../../common/utils.cpp
../fxr/fx_ver.cpp
../json/casablanca/src/json/json.cpp
../json/casablanca/src/json/json_parsing.cpp
../json/casablanca/src/json/json_serialization.cpp
../json/casablanca/src/utilities/asyncrt_utils.cpp
)
if(WIN32)
list(APPEND SOURCES
../../common/pal.windows.cpp
../../common/longfile.windows.cpp
Exports.def)
else()
list(APPEND SOURCES
../../common/pal.unix.cpp
${VERSION_FILE_PATH})
endif()
# Move to setup.cmake if COMHOST is ever built on non-Windows
if("${CLI_CMAKE_COMHOST_VER}" STREQUAL "")
message(FATAL_ERROR "comhost version is not specified")
else()
add_definitions(-DLIBHOST_PKG_VER="${CLI_CMAKE_COMHOST_VER}")
endif()
add_definitions(-DEXPORT_SHARED_API=1)
add_definitions(-DFEATURE_LIBHOST=1)
add_library(comhost SHARED ${SOURCES})
# Specify non-default Windows libs to be used for Arm/Arm64 builds
if (WIN32 AND (CLI_CMAKE_PLATFORM_ARCH_ARM OR CLI_CMAKE_PLATFORM_ARCH_ARM64))
target_link_libraries(comhost Advapi32.lib Ole32.lib OleAut32.lib)
endif()
install_library_and_symbols (comhost)

Просмотреть файл

@ -0,0 +1,5 @@
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE

Просмотреть файл

@ -0,0 +1,153 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "comhost.h"
#include <trace.h>
#include <utils.h>
#include <error_codes.h>
#include <cpprest/json.h>
using namespace web;
using comhost::clsid_map_entry;
using comhost::clsid_map;
namespace
{
HRESULT string_to_clsid(_In_ const pal::string_t &str, _Out_ CLSID &clsid)
{
// If the first character of the GUID is not '{' then COM will
// attempt to look up the string in the CLSID table.
if (str[0] == _X('{'))
{
if (SUCCEEDED(::CLSIDFromString(str.data(), &clsid)))
return S_OK;
}
return __HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
}
clsid_map parse_stream(_Inout_ pal::istream_t &json_map_raw)
{
skip_utf8_bom(&json_map_raw);
// Parse JSON
json::value json_map;
try
{
json_map = json::value::parse(json_map_raw);
}
catch (const json::json_exception&)
{
trace::error(_X("Embedded .clsidmap format is invalid"));
throw HResultException{ StatusCode::InvalidConfigFile };
}
json::object &json_obj = json_map.as_object();
// Process JSON and construct a map
HRESULT hr;
clsid_map mapping;
for (std::pair<utility::string_t, json::value> &prop : json_obj)
{
CLSID clsidMaybe;
hr = string_to_clsid(prop.first, clsidMaybe);
if (FAILED(hr))
{
assert(false && "Invalid CLSID");
continue;
}
clsid_map_entry e{};
json::object &val = prop.second.as_object();
e.assembly = val.at(_X("assembly")).as_string();
e.type = val.at(_X("type")).as_string();
mapping[clsidMaybe] = std::move(e);
}
return mapping;
}
class memory_buffer : public std::basic_streambuf<pal::istream_t::char_type>
{
public:
memory_buffer(_In_ DWORD dataInBytes, _In_reads_bytes_(dataInBytes) void *data)
{
auto raw_begin = reinterpret_cast<pal::istream_t::char_type*>(data);
setg(raw_begin, raw_begin, raw_begin + (dataInBytes / sizeof(pal::istream_t::char_type)));
}
};
clsid_map get_json_map_from_resource(bool &found_resource)
{
found_resource = false;
HMODULE hMod;
if (!pal::get_current_module((pal::dll_t*)&hMod))
return{};
HRSRC resHandle = ::FindResourceW(hMod, MAKEINTRESOURCEW(RESOURCEID_CLSIDMAP), MAKEINTRESOURCEW(RESOURCETYPE_CLSIDMAP));
if (resHandle == nullptr)
return {};
found_resource = true;
DWORD size = ::SizeofResource(hMod, resHandle);
HGLOBAL resData = ::LoadResource(hMod, resHandle);
if (resData == nullptr || size == 0)
throw HResultException{ HRESULT_FROM_WIN32(::GetLastError()) };
LPVOID data = ::LockResource(resData);
if (data == nullptr)
throw HResultException{ E_UNEXPECTED }; // This should never happen in Windows 7+
memory_buffer resourceBuffer{ size, data };
pal::istream_t stream{ &resourceBuffer };
return parse_stream(stream);
}
clsid_map get_json_map_from_file()
{
pal::string_t map_file_name;
if (pal::get_own_module_path(&map_file_name))
{
map_file_name += _X(".clsidmap");
if (pal::file_exists(map_file_name))
{
pal::ifstream_t file{ map_file_name };
return parse_stream(file);
}
}
return{};
}
}
clsid_map comhost::get_clsid_map()
{
// CLSID map format
// {
// "<clsid>": {
// "assembly": <assembly_name>,
// "type": <type_name>
// },
// ...
// }
// If a mapping as a resource was found, we don't
// want to fall back to looking on disk.
bool found_resource;
// Find the JSON data that describes the CLSID mapping
clsid_map mapping = get_json_map_from_resource(found_resource);
if (!found_resource && mapping.empty())
{
trace::verbose(_X("JSON map resource stream not found"));
mapping = get_json_map_from_file();
if (mapping.empty())
trace::verbose(_X("JSON map .clsidmap file not found"));
}
return mapping;
}

Просмотреть файл

@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef _COREHOST_CLI_COMHOST_COMHOST_H_
#define _COREHOST_CLI_COMHOST_COMHOST_H_
#include <pal.h>
#include <map>
#include <cassert>
#define RETURN_IF_FAILED(exp) { hr = (exp); if (FAILED(hr)) { assert(false && #exp); return hr; } }
struct HResultException
{
HRESULT hr;
};
#define RETURN_HRESULT_IF_EXCEPT(exp) try { exp; } catch (const HResultException &e) { return e.hr; } catch (const std::bad_alloc&) { return E_OUTOFMEMORY; }
// Should be shared with core-sdk for tooling support
#define RESOURCEID_CLSIDMAP 64
#define RESOURCETYPE_CLSIDMAP 1024
namespace std
{
template<>
struct less<CLSID>
{
bool operator()(const CLSID& l, const CLSID& r) const
{
return ::memcmp(&l, &r, sizeof(CLSID)) < 0;
}
};
}
namespace comhost
{
struct clsid_map_entry
{
pal::string_t assembly;
pal::string_t type;
};
using clsid_map = std::map<CLSID, clsid_map_entry>;
// Get the current CLSID map
clsid_map get_clsid_map();
}
#endif /* _COREHOST_CLI_COMHOST_COMHOST_H_ */

Просмотреть файл

@ -0,0 +1,302 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "comhost.h"
#include <corehost.h>
#include <error_codes.h>
#include <trace.h>
#include <type_traits>
using comhost::clsid_map_entry;
using comhost::clsid_map;
#if defined(_WIN32)
// COM entry points are defined without the __declspec(dllexport) attribute.
// The issue here is that the compiler will throw an error regarding linkage
// redefinion. The solution here is to the use a .def file on Windows.
#define COM_API extern "C"
#else
#define COM_API SHARED_API
#endif // _WIN32
namespace
{
pal::stringstream_t & get_comhost_error_stream()
{
thread_local static pal::stringstream_t comhost_errors;
return comhost_errors;
}
void reset_comhost_error_stream()
{
pal::stringstream_t newstream;
get_comhost_error_stream().swap(newstream);
}
void comhost_error_writer(const pal::char_t* msg)
{
get_comhost_error_stream() << msg;
}
}
COM_API HRESULT STDMETHODCALLTYPE DllGetClassObject(
_In_ REFCLSID rclsid,
_In_ REFIID riid,
_Outptr_ LPVOID FAR* ppv)
{
// Check if the CLSID map contains a mapping
clsid_map map;
RETURN_HRESULT_IF_EXCEPT(map = comhost::get_clsid_map());
clsid_map::const_iterator iter = map.find(rclsid);
if (iter == std::end(map))
return CLASS_E_CLASSNOTAVAILABLE;
HRESULT hr;
pal::string_t app_path;
com_activation_fn act;
{
reset_comhost_error_stream();
trace::set_error_writer(comhost_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();
}
return __HRESULT_FROM_WIN32(ec);
}
}
// Query the CLR for the type
IUnknown *classFactory = nullptr;
com_activation_context cxt
{
rclsid,
riid,
app_path.c_str(),
iter->second.assembly.c_str(),
iter->second.type.c_str(),
(void**)&classFactory
};
RETURN_IF_FAILED(act(&cxt));
assert(classFactory != nullptr);
hr = classFactory->QueryInterface(riid, ppv);
classFactory->Release();
return hr;
}
COM_API HRESULT STDMETHODCALLTYPE DllCanUnloadNow(void)
{
return S_FALSE;
}
#if defined(_WIN32)
namespace
{
const WCHAR EntryKeyFmt[] = L"SOFTWARE\\Classes\\CLSID\\%s";
struct OleStr : public std::unique_ptr<std::remove_pointer<LPOLESTR>::type, decltype(&::CoTaskMemFree)>
{
OleStr(_In_z_ LPOLESTR raw)
: std::unique_ptr<std::remove_pointer<LPOLESTR>::type, decltype(&::CoTaskMemFree)>(raw, ::CoTaskMemFree)
{ }
};
struct RegKey : public std::unique_ptr<std::remove_pointer<HKEY>::type, decltype(&::RegCloseKey)>
{
RegKey(_In_ HKEY raw)
: std::unique_ptr<std::remove_pointer<HKEY>::type, decltype(&::RegCloseKey)>(raw, ::RegCloseKey)
{ }
};
HRESULT RemoveClsid(_In_ REFCLSID clsid)
{
HRESULT hr;
LPOLESTR clsidAsStrRaw;
RETURN_IF_FAILED(::StringFromCLSID(clsid, &clsidAsStrRaw));
OleStr clsidAsStr{ clsidAsStrRaw };
WCHAR regKeyPath[1024];
::swprintf_s(regKeyPath, EntryKeyFmt, clsidAsStr.get());
LSTATUS res;
// Handle sub keys
{
HKEY toDeleteRaw;
res = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyPath, 0, KEY_READ | KEY_WRITE, &toDeleteRaw);
if (ERROR_FILE_NOT_FOUND == res)
{
return S_OK;
}
else if (ERROR_SUCCESS != res)
{
return __HRESULT_FROM_WIN32(res);
}
RegKey toDelete{ toDeleteRaw };
res = ::RegDeleteTreeW(toDelete.get(), nullptr);
if (ERROR_SUCCESS != res)
return __HRESULT_FROM_WIN32(res);
}
res = ::RegDeleteKeyW(HKEY_LOCAL_MACHINE, regKeyPath);
if (ERROR_SUCCESS != res)
return __HRESULT_FROM_WIN32(res);
return S_OK;
}
HRESULT RegisterClsid(_In_ REFCLSID clsid, _In_opt_z_ const WCHAR *threadingModel)
{
HRESULT hr;
// Remove the CLSID in case it exists and has undesirable settings
RETURN_IF_FAILED(RemoveClsid(clsid));
LPOLESTR clsidAsStrRaw;
RETURN_IF_FAILED(::StringFromCLSID(clsid, &clsidAsStrRaw));
OleStr clsidAsStr{ clsidAsStrRaw };
WCHAR regKeyClsidPath[1024];
::swprintf_s(regKeyClsidPath, EntryKeyFmt, clsidAsStr.get());
HKEY regKeyRaw;
DWORD disp;
LSTATUS res = ::RegCreateKeyExW(
HKEY_LOCAL_MACHINE,
regKeyClsidPath,
0,
REG_NONE,
REG_OPTION_NON_VOLATILE,
(KEY_READ | KEY_WRITE),
nullptr,
&regKeyRaw,
&disp);
if (res != ERROR_SUCCESS)
return __HRESULT_FROM_WIN32(res);
RegKey regKey{ regKeyRaw };
WCHAR regKeyServerPath[ARRAYSIZE(regKeyClsidPath) * 2];
::swprintf_s(regKeyServerPath, L"%s\\InProcServer32", regKeyClsidPath);
HKEY regServerKeyRaw;
res = ::RegCreateKeyExW(
HKEY_LOCAL_MACHINE,
regKeyServerPath,
0,
REG_NONE,
REG_OPTION_NON_VOLATILE,
(KEY_READ | KEY_WRITE),
nullptr,
&regServerKeyRaw,
&disp);
if (res != ERROR_SUCCESS)
return __HRESULT_FROM_WIN32(res);
regKey.reset(regServerKeyRaw);
HMODULE mod;
if (FALSE == ::GetModuleHandleExW(
(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT),
reinterpret_cast<LPCWSTR>(&RegisterClsid),
&mod))
{
return HRESULT_FROM_WIN32(::GetLastError());
}
pal::string_t modPath;
if (!pal::get_own_module_path(&modPath))
return E_UNEXPECTED;
// The default value for the key is the path to the DLL
res = ::RegSetValueExW(
regKey.get(),
nullptr,
0,
REG_SZ,
reinterpret_cast<const BYTE*>(modPath.data()),
static_cast<DWORD>(modPath.size() + 1) * sizeof(modPath[0]));
if (res != ERROR_SUCCESS)
return __HRESULT_FROM_WIN32(res);
// Set the threading model if provided
if (threadingModel != nullptr)
{
res = ::RegSetValueExW(
regKey.get(),
_X("ThreadingModel"),
0,
REG_SZ,
reinterpret_cast<const BYTE*>(threadingModel),
static_cast<DWORD>(::wcslen(threadingModel) + 1) * sizeof(threadingModel[0]));
if (res != ERROR_SUCCESS)
return __HRESULT_FROM_WIN32(res);
}
return S_OK;
}
}
COM_API HRESULT STDMETHODCALLTYPE DllRegisterServer(void)
{
// Step 1: Get CLSID mapping
clsid_map map;
RETURN_HRESULT_IF_EXCEPT(map = comhost::get_clsid_map());
// Step 2: Register each CLSID
HRESULT hr;
for (clsid_map::const_reference p : map)
RETURN_IF_FAILED(RegisterClsid(p.first, _X("Both")));
return S_OK;
}
COM_API HRESULT STDMETHODCALLTYPE DllUnregisterServer(void)
{
// Step 1: Get CLSID mapping
clsid_map map;
RETURN_HRESULT_IF_EXCEPT(map = comhost::get_clsid_map());
// Step 2: Unregister each CLSID
HRESULT hr;
for (clsid_map::const_reference p : map)
RETURN_IF_FAILED(RemoveClsid(p.first));
return S_OK;
}
#endif // _WIN32

Просмотреть файл

@ -5,95 +5,133 @@
#include "coreclr.h"
#include "utils.h"
static pal::dll_t g_coreclr = nullptr;
#include "error_codes.h"
// Prototype of the coreclr_initialize function from coreclr.dll
typedef pal::hresult_t(STDMETHODCALLTYPE *coreclr_initialize_fn)(
using coreclr_initialize_fn = pal::hresult_t(STDMETHODCALLTYPE *)(
const char* exePath,
const char* appDomainFriendlyName,
int propertyCount,
const char** propertyKeys,
const char** propertyValues,
coreclr::host_handle_t* hostHandle,
coreclr_t::host_handle_t* hostHandle,
unsigned int* domainId);
// Prototype of the coreclr_shutdown function from coreclr.dll
typedef pal::hresult_t(STDMETHODCALLTYPE *coreclr_shutdown_fn)(
coreclr::host_handle_t hostHandle,
using coreclr_shutdown_fn = pal::hresult_t(STDMETHODCALLTYPE *)(
coreclr_t::host_handle_t hostHandle,
unsigned int domainId,
int* latchedExitCode);
// Prototype of the coreclr_execute_assembly function from coreclr.dll
typedef pal::hresult_t(STDMETHODCALLTYPE *coreclr_execute_assembly_fn)(
coreclr::host_handle_t hostHandle,
using coreclr_execute_assembly_fn = pal::hresult_t(STDMETHODCALLTYPE *)(
coreclr_t::host_handle_t hostHandle,
unsigned int domainId,
int argc,
const char** argv,
const char* managedAssemblyPath,
unsigned int* exitCode);
static coreclr_shutdown_fn coreclr_shutdown = nullptr;
static coreclr_initialize_fn coreclr_initialize = nullptr;
static coreclr_execute_assembly_fn coreclr_execute_assembly = nullptr;
// Prototype of the coreclr_create_delegate function from coreclr.dll
using coreclr_create_delegate_fn = pal::hresult_t(STDMETHODCALLTYPE *)(
coreclr_t::host_handle_t hostHandle,
unsigned int domainId,
const char* entryPointAssemblyName,
const char* entryPointTypeName,
const char* entryPointMethodName,
void** delegate);
bool coreclr::bind(const pal::string_t& libcoreclr_path)
namespace
{
assert(g_coreclr == nullptr);
pal::dll_t g_coreclr = nullptr;
coreclr_shutdown_fn coreclr_shutdown = nullptr;
coreclr_initialize_fn coreclr_initialize = nullptr;
coreclr_execute_assembly_fn coreclr_execute_assembly = nullptr;
coreclr_create_delegate_fn coreclr_create_delegate = nullptr;
pal::string_t coreclr_dll_path(libcoreclr_path);
append_path(&coreclr_dll_path, LIBCORECLR_NAME);
if (!pal::load_library(&coreclr_dll_path, &g_coreclr))
bool coreclr_bind(const pal::string_t& libcoreclr_path)
{
return false;
assert(g_coreclr == nullptr);
pal::string_t coreclr_dll_path(libcoreclr_path);
append_path(&coreclr_dll_path, LIBCORECLR_NAME);
if (!pal::load_library(&coreclr_dll_path, &g_coreclr))
{
return false;
}
coreclr_initialize = (coreclr_initialize_fn)pal::get_symbol(g_coreclr, "coreclr_initialize");
coreclr_shutdown = (coreclr_shutdown_fn)pal::get_symbol(g_coreclr, "coreclr_shutdown_2");
coreclr_execute_assembly = (coreclr_execute_assembly_fn)pal::get_symbol(g_coreclr, "coreclr_execute_assembly");
coreclr_create_delegate = (coreclr_create_delegate_fn)pal::get_symbol(g_coreclr, "coreclr_create_delegate");
assert(coreclr_initialize != nullptr
&& coreclr_shutdown != nullptr
&& coreclr_execute_assembly != nullptr
&& coreclr_create_delegate != nullptr);
return true;
}
void coreclr_unload()
{
assert(g_coreclr != nullptr && coreclr_initialize != nullptr);
coreclr_initialize = (coreclr_initialize_fn)pal::get_symbol(g_coreclr, "coreclr_initialize");
coreclr_shutdown = (coreclr_shutdown_fn)pal::get_symbol(g_coreclr, "coreclr_shutdown_2");
coreclr_execute_assembly = (coreclr_execute_assembly_fn)pal::get_symbol(g_coreclr, "coreclr_execute_assembly");
return true;
// [TODO] Unloading coreclr is not presently supported
// pal::unload_library(g_coreclr);
}
}
void coreclr::unload()
{
assert(g_coreclr != nullptr && coreclr_initialize != nullptr);
pal::unload_library(g_coreclr);
}
pal::hresult_t coreclr::initialize(
pal::hresult_t coreclr_t::create(
const pal::string_t& libcoreclr_path,
const char* exe_path,
const char* app_domain_friendly_name,
const char** property_keys,
const char** property_values,
int property_count,
host_handle_t* host_handle,
domain_id_t* domain_id)
coreclr_property_bag_t &properties,
std::unique_ptr<coreclr_t> &inst)
{
if (!coreclr_bind(libcoreclr_path))
{
trace::error(_X("Failed to bind to CoreCLR at '%s'"), libcoreclr_path.c_str());
return StatusCode::CoreClrBindFailure;
}
assert(g_coreclr != nullptr && coreclr_initialize != nullptr);
return coreclr_initialize(
host_handle_t host_handle;
domain_id_t domain_id;
pal::hresult_t hr;
hr = coreclr_initialize(
exe_path,
app_domain_friendly_name,
property_count,
property_keys,
property_values,
host_handle,
domain_id);
properties.count(),
properties.keys(),
properties.values(),
&host_handle,
&domain_id);
if (!SUCCEEDED(hr))
return hr;
inst.reset(new coreclr_t(host_handle, domain_id));
return StatusCode::Success;
}
pal::hresult_t coreclr::shutdown(host_handle_t host_handle, domain_id_t domain_id, int* latchedExitCode)
coreclr_t::coreclr_t(host_handle_t host_handle, domain_id_t domain_id)
: _is_shutdown{}
, _host_handle{ host_handle }
, _domain_id{ domain_id }
{
assert(g_coreclr != nullptr && coreclr_shutdown != nullptr);
return coreclr_shutdown(host_handle, domain_id, latchedExitCode);
}
pal::hresult_t coreclr::execute_assembly(
host_handle_t host_handle,
domain_id_t domain_id,
coreclr_t::~coreclr_t()
{
(void)shutdown(nullptr);
coreclr_unload();
}
pal::hresult_t coreclr_t::execute_assembly(
int argc,
const char** argv,
const char* managed_assembly_path,
@ -102,10 +140,144 @@ pal::hresult_t coreclr::execute_assembly(
assert(g_coreclr != nullptr && coreclr_execute_assembly != nullptr);
return coreclr_execute_assembly(
host_handle,
domain_id,
_host_handle,
_domain_id,
argc,
argv,
managed_assembly_path,
exit_code);
}
pal::hresult_t coreclr_t::create_delegate(
const char* entryPointAssemblyName,
const char* entryPointTypeName,
const char* entryPointMethodName,
void** delegate)
{
assert(g_coreclr != nullptr && coreclr_execute_assembly != nullptr);
return coreclr_create_delegate(
_host_handle,
_domain_id,
entryPointAssemblyName,
entryPointTypeName,
entryPointMethodName,
delegate);
}
pal::hresult_t coreclr_t::shutdown(int* latchedExitCode)
{
assert(g_coreclr != nullptr && coreclr_shutdown != nullptr);
bool is_false = false;
// If already shut down return success since the result
// has already been reported to a previous caller.
if (!_is_shutdown.compare_exchange_strong(is_false, true))
{
if (latchedExitCode != nullptr)
*latchedExitCode = StatusCode::Success;
return StatusCode::Success;
}
return coreclr_shutdown(_host_handle, _domain_id, latchedExitCode);
}
namespace
{
const char *PropertyNameMapping[] =
{
"TRUSTED_PLATFORM_ASSEMBLIES",
"NATIVE_DLL_SEARCH_DIRECTORIES",
"PLATFORM_RESOURCE_ROOTS",
"AppDomainCompatSwitch",
"APP_CONTEXT_BASE_DIRECTORY",
"APP_CONTEXT_DEPS_FILES",
"FX_DEPS_FILE",
"PROBING_DIRECTORIES",
"FX_PRODUCT_VERSION",
"JIT_PATH",
"STARTUP_HOOKS",
"APP_PATHS",
"APP_NI_PATHS"
};
static_assert((sizeof(PropertyNameMapping) / sizeof(*PropertyNameMapping)) == static_cast<size_t>(common_property::Last), "Invalid property count");
}
coreclr_property_bag_t::coreclr_property_bag_t()
{
// Optimize the bag for at least twice as many common properties.
const size_t init_size = 2 * static_cast<size_t>(common_property::Last);
_keys.reserve(init_size);
_values.reserve(init_size);
}
void coreclr_property_bag_t::add(common_property key, const char *value)
{
int idx = static_cast<int>(key);
assert(0 <= idx && idx < static_cast<int>(common_property::Last));
add(PropertyNameMapping[idx], value);
}
void coreclr_property_bag_t::add(const char *key, const char *value)
{
if (key == nullptr || value == nullptr)
return;
assert(_keys.size() == _values.size());
_keys.push_back(key);
_values.push_back(value);
}
bool coreclr_property_bag_t::try_get(common_property key, const char **value)
{
int idx = static_cast<int>(key);
assert(0 <= idx && idx < static_cast<int>(common_property::Last));
return try_get(PropertyNameMapping[idx], value);
}
bool coreclr_property_bag_t::try_get(const char *key, const char **value)
{
assert(key != nullptr && value != nullptr);
for (int i = 0; i < count(); ++i)
{
if (0 == pal::cstrcasecmp(_keys[i], key))
{
*value = _values[i];
return true;
}
}
return false;
}
void coreclr_property_bag_t::log_properties()
{
for (int i = 0; i < count(); ++i)
{
pal::string_t key, val;
pal::clr_palstring(_keys[i], &key);
pal::clr_palstring(_values[i], &val);
trace::verbose(_X("Property %s = %s"), key.c_str(), val.c_str());
}
}
int coreclr_property_bag_t::count()
{
assert(_keys.size() == _values.size());
return static_cast<int>(_keys.size());
}
const char** coreclr_property_bag_t::keys()
{
return _keys.data();
}
const char** coreclr_property_bag_t::values()
{
return _values.data();
}

Просмотреть файл

@ -1,39 +1,100 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef CLR_H
#define CLR_H
#ifndef _COREHOST_CLI_CORECLR_H_
#define _COREHOST_CLI_CORECLR_H_
#include "pal.h"
#include "trace.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <vector>
namespace coreclr
class coreclr_property_bag_t;
class coreclr_t
{
typedef void* host_handle_t;
typedef unsigned int domain_id_t;
bool bind(const pal::string_t& libcoreclr_path);
void unload();
pal::hresult_t initialize(
public: // static
static pal::hresult_t create(
const pal::string_t& libcoreclr_path,
const char* exe_path,
const char* app_domain_friendly_name,
const char** property_keys,
const char** property_values,
int property_count,
host_handle_t* host_handle,
domain_id_t* domain_id);
coreclr_property_bag_t &properties,
std::unique_ptr<coreclr_t> &inst);
pal::hresult_t shutdown(host_handle_t host_handle, domain_id_t domain_id, int* latchedExitCode);
public:
using host_handle_t = void*;
using domain_id_t = std::uint32_t;
coreclr_t(host_handle_t host_handle, domain_id_t domain_id);
~coreclr_t();
pal::hresult_t execute_assembly(
host_handle_t host_handle,
domain_id_t domain_id,
int argc,
const char** argv,
const char* managed_assembly_path,
unsigned int* exit_code);
pal::hresult_t create_delegate(
const char* entryPointAssemblyName,
const char* entryPointTypeName,
const char* entryPointMethodName,
void** delegate);
pal::hresult_t shutdown(int* latchedExitCode);
private:
std::atomic_bool _is_shutdown;
host_handle_t _host_handle;
domain_id_t _domain_id;
};
#endif
enum class common_property
{
TrustedPlatformAssemblies,
NativeDllSearchDirectories,
PlatformResourceRoots,
AppDomainCompatSwitch,
AppContextBaseDirectory,
AppContextDepsFiles,
FxDepsFile,
ProbingDirectories,
FxProductVersion,
JitPath,
StartUpHooks,
AppPaths,
AppNIPaths,
// Sentinel value - new values should be defined above
Last
};
class coreclr_property_bag_t
{
public:
coreclr_property_bag_t();
void add(common_property key, const char *value);
void add(const char *key, const char *value);
bool try_get(common_property key, const char **value);
bool try_get(const char *key, const char **value);
void log_properties();
public:
int count();
const char** keys();
const char** values();
private:
std::vector<const char*> _keys;
std::vector<const char*> _values;
};
#endif // _COREHOST_CLI_CORECLR_H_

Просмотреть файл

@ -499,30 +499,38 @@ bool deps_resolver_t::resolve_tpa_list(
}
};
// First add managed assembly to the TPA.
// TODO: Remove: the deps should contain the managed DLL.
// Workaround for: csc.deps.json doesn't have the csc.dll
deps_asset_t asset(get_filename_without_ext(m_managed_app), get_filename(m_managed_app), version_t(), version_t());
deps_resolved_asset_t resolved_asset(asset, m_managed_app);
add_tpa_asset(resolved_asset, &items);
// Add the app's entries
const auto& deps_entries = get_deps().get_entries(deps_entry_t::asset_types::runtime);
for (const auto& entry : deps_entries)
if (m_host_mode != host_mode_t::libhost)
{
if (!process_entry(m_app_dir, entry, 0))
// First add managed assembly to the TPA.
// TODO: Remove: the deps should contain the managed DLL.
// Workaround for: csc.deps.json doesn't have the csc.dll
deps_asset_t asset(get_filename_without_ext(m_managed_app), get_filename(m_managed_app), version_t(), version_t());
deps_resolved_asset_t resolved_asset(asset, m_managed_app);
add_tpa_asset(resolved_asset, &items);
// Add the app's entries
const auto& deps_entries = get_deps().get_entries(deps_entry_t::asset_types::runtime);
for (const auto& entry : deps_entries)
{
return false;
if (!process_entry(m_app_dir, entry, 0))
{
return false;
}
}
// If the deps file wasn't present or has missing entries, then
// add the app local assemblies to the TPA. This is only valid
// in non-libhost scenarios (e.g. comhost).
if (!get_deps().exists())
{
// Obtain the local assemblies in the app dir.
get_dir_assemblies(m_app_dir, _X("local"), &items);
}
}
// If the deps file wasn't present or has missing entries, then
// add the app local assemblies to the TPA.
if (!get_deps().exists())
{
// Obtain the local assemblies in the app dir.
get_dir_assemblies(m_app_dir, _X("local"), &items);
}
// There should be no additional deps files in a libhost scenario.
// See comments during additional deps.json resolution.
assert(m_additional_deps.empty() || m_host_mode != host_mode_t::libhost);
// If additional deps files were specified that need to be treated as part of the
// application, then add them to the mix as well.
@ -590,13 +598,19 @@ void deps_resolver_t::init_known_entry_path(const deps_entry_t& entry, const pal
void deps_resolver_t::resolve_additional_deps(const arguments_t& args, const deps_json_t::rid_fallback_graph_t& rid_fallback_graph)
{
if (!m_is_framework_dependent)
if (!m_is_framework_dependent
|| m_host_mode == host_mode_t::libhost)
{
// Additional deps.json support is only available for framework-dependent apps due to the following constraints:
//
// 1) Unlike framework-dependent Apps, self-contained apps do not have details of the SharedFX and Version they target.
// 2) Unlike framework-dependent Apps, self-contained apps do not have RID fallback graph that is required for looking up
// the correct native assets from nuget packages.
//
// Additional deps.json support is not available for libhost scenarios. For example, if CoreCLR is instantiated from a
// library context (i.e. comhost) the activation of classes are assumed to be performed in an AssemblyLoadContext. This
// assumption is made because it is possible an existing CoreCLR was already activated and it may not satisfy the current
// needs of the new class.
return;
}
@ -695,6 +709,26 @@ void deps_resolver_t::resolve_additional_deps(const arguments_t& args, const dep
}
}
void deps_resolver_t::get_app_fx_definition_range(fx_definition_vector_t::iterator *begin, fx_definition_vector_t::iterator *end) const
{
assert(begin != nullptr && end != nullptr);
auto begin_iter = m_fx_definitions.begin();
auto end_iter = m_fx_definitions.end();
if (m_host_mode == host_mode_t::libhost
&& begin_iter != end_iter)
{
// In a libhost scenario the app definition shouldn't be
// included in the creation of the application.
assert(begin_iter->get() == &get_app(m_fx_definitions));
++begin_iter;
}
*begin = begin_iter;
*end = end_iter;
}
/**
* Resolve native and culture assembly directories based on "asset_type" parameter.
*/

Просмотреть файл

@ -51,6 +51,7 @@ public:
, m_managed_app(args.managed_application)
, m_is_framework_dependent(is_framework_dependent)
, m_core_servicing(args.core_servicing)
, m_host_mode(args.host_mode)
{
int lowest_framework = m_fx_definitions.size() - 1;
int root_framework = -1;
@ -159,6 +160,8 @@ public:
return get_app(m_fx_definitions).get_deps_file();
}
void get_app_fx_definition_range(fx_definition_vector_t::iterator *begin, fx_definition_vector_t::iterator *end) const;
const fx_definition_vector_t& get_fx_definitions() const
{
return m_fx_definitions;
@ -174,6 +177,17 @@ public:
return m_is_framework_dependent;
}
const pal::string_t &get_app_dir() const
{
if (m_host_mode == host_mode_t::libhost)
{
static const pal::string_t s_empty;
return s_empty;
}
return m_app_dir;
}
private:
static pal::string_t get_fx_deps(const pal::string_t& fx_dir, const pal::string_t& fx_name)
@ -217,6 +231,9 @@ private:
const deps_resolved_asset_t& asset,
name_to_resolved_asset_map_t* items);
// Mode in which the host is being run. This can dictate how dependencies should be discovered.
const host_mode_t m_host_mode;
// The managed application the dependencies are being resolved for.
pal::string_t m_managed_app;

Просмотреть файл

@ -62,7 +62,7 @@ endif()
add_definitions(-D_NO_ASYNCRTIMP)
add_definitions(-D_NO_PPLXIMP)
add_definitions(-DCOREHOST_MAKE_DLL=1)
add_definitions(-DEXPORT_SHARED_API=1)
set(RESOURCES)
if(WIN32)

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <cassert>
#include <mutex>
#include "args.h"
#include "cpprest/json.h"
#include "deps_format.h"
@ -20,6 +21,192 @@
#include "trace.h"
#include "utils.h"
using corehost_load_fn = int(*) (const host_interface_t* init);
using corehost_main_fn = int(*) (const int argc, const pal::char_t* argv[]);
using corehost_get_com_activation_delegate_fn = int(*) (void **delegate);
using corehost_main_with_output_buffer_fn = int(*) (const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size);
using corehost_unload_fn = int(*) ();
using corehost_error_writer_fn = void(*) (const pal::char_t* message);
using corehost_set_error_writer_fn = corehost_error_writer_fn(*) (corehost_error_writer_fn error_writer);
struct hostpolicy_contract
{
// Required API contracts
corehost_load_fn load;
corehost_unload_fn unload;
// 3.0+ contracts
corehost_set_error_writer_fn set_error_writer;
};
namespace
{
std::mutex g_hostpolicy_lock;
pal::dll_t g_hostpolicy;
hostpolicy_contract g_hostpolicy_contract;
}
int load_hostpolicy_common(
const pal::string_t& lib_dir,
pal::string_t& host_path,
pal::dll_t* h_host,
hostpolicy_contract &host_contract)
{
std::lock_guard<std::mutex> lock{ g_hostpolicy_lock };
if (g_hostpolicy == nullptr)
{
if (!library_exists_in_dir(lib_dir, LIBHOSTPOLICY_NAME, &host_path))
{
return StatusCode::CoreHostLibMissingFailure;
}
// Load library
if (!pal::load_library(&host_path, &g_hostpolicy))
{
trace::info(_X("Load library of %s failed"), host_path.c_str());
return StatusCode::CoreHostLibLoadFailure;
}
// Obtain entrypoint symbols
g_hostpolicy_contract.load = (corehost_load_fn)pal::get_symbol(g_hostpolicy, "corehost_load");
g_hostpolicy_contract.unload = (corehost_unload_fn)pal::get_symbol(g_hostpolicy, "corehost_unload");
if ((g_hostpolicy_contract.load == nullptr) || (g_hostpolicy_contract.unload == nullptr))
return StatusCode::CoreHostEntryPointFailure;
g_hostpolicy_contract.set_error_writer = (corehost_set_error_writer_fn)pal::get_symbol(g_hostpolicy, "corehost_set_error_writer");
// It's possible to not have corehost_set_error_writer, since this was only introduced in 3.0
// so 2.0 hostpolicy would not have the export. In this case we will not propagate the error writer
// and errors will still be reported to stderr.
}
// Return global values
*h_host = g_hostpolicy;
host_contract = g_hostpolicy_contract;
return StatusCode::Success;
}
template<typename T>
int load_hostpolicy(
const pal::string_t& lib_dir,
pal::dll_t* h_host,
hostpolicy_contract &host_contract,
const char *main_entry_symbol,
T* main_fn)
{
assert(main_entry_symbol != nullptr && main_fn != nullptr);
pal::string_t host_path;
int rc = load_hostpolicy_common(lib_dir, host_path, h_host, host_contract);
if (rc != StatusCode::Success)
{
trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, lib_dir.c_str());
return rc;
}
// Obtain entrypoint symbol
*main_fn = (T)pal::get_symbol(*h_host, main_entry_symbol);
if (*main_fn == nullptr)
return StatusCode::CoreHostEntryPointFailure;
return StatusCode::Success;
}
static int execute_app(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[])
{
pal::dll_t corehost;
hostpolicy_contract host_contract{};
corehost_main_fn host_main = nullptr;
int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_main", &host_main);
if (code != StatusCode::Success)
return code;
// Previous hostfxr trace messages must be printed before calling trace::setup in hostpolicy
trace::flush();
{
propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
const host_interface_t& intf = init->get_host_init_data();
if ((code = host_contract.load(&intf)) == StatusCode::Success)
{
code = host_main(argc, argv);
(void)host_contract.unload();
}
}
return code;
}
static int execute_host_command(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[],
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size)
{
pal::dll_t corehost;
hostpolicy_contract host_contract{};
corehost_main_with_output_buffer_fn host_main = nullptr;
int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_main_with_output_buffer", &host_main);
if (code != StatusCode::Success)
return code;
// Previous hostfxr trace messages must be printed before calling trace::setup in hostpolicy
trace::flush();
{
propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
const host_interface_t& intf = init->get_host_init_data();
if ((code = host_contract.load(&intf)) == StatusCode::Success)
{
code = host_main(argc, argv, result_buffer, buffer_size, required_buffer_size);
(void)host_contract.unload();
}
}
return code;
}
static int get_com_activation_delegate_internal(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
void **delegate)
{
pal::dll_t corehost;
hostpolicy_contract host_contract{};
corehost_get_com_activation_delegate_fn host_entry = nullptr;
int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_get_com_activation_delegate", &host_entry);
if (code != StatusCode::Success)
return code;
// Previous hostfxr trace messages must be printed before calling trace::setup in hostpolicy
trace::flush();
{
propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
const host_interface_t& intf = init->get_host_init_data();
if ((code = host_contract.load(&intf)) == StatusCode::Success)
{
code = host_entry(delegate);
}
}
return code;
}
/**
* When the framework is not found, display detailed error message
* about available frameworks and installation of new framework.
@ -293,9 +480,17 @@ bool fx_muxer_t::resolve_hostpolicy_dir(
// app dir.
// 2. When activated with native exe, the standalone host, check own directory.
assert(mode != host_mode_t::invalid);
expected = (mode == host_mode_t::apphost)
? dotnet_root
: get_directory(specified_deps_file.empty() ? app_candidate : specified_deps_file);
switch (mode)
{
case host_mode_t::apphost:
case host_mode_t::libhost:
expected = dotnet_root;
break;
default:
expected = get_directory(specified_deps_file.empty() ? app_candidate : specified_deps_file);
break;
}
}
// Check if hostpolicy exists in "expected" directory.
@ -722,7 +917,7 @@ int fx_muxer_t::parse_args(
{
trace::error(_X(" %-37s %s"), (arg.option + _X(" ") + arg.argument).c_str(), arg.description.c_str());
}
return InvalidArgFailure;
return StatusCode::InvalidArgFailure;
}
app_candidate = host_info.app_path;
@ -750,7 +945,7 @@ int fx_muxer_t::parse_args(
if (!exec_mode)
{
// Route to CLI.
return AppArgNotRunnable;
return StatusCode::AppArgNotRunnable;
}
}
@ -761,7 +956,7 @@ int fx_muxer_t::parse_args(
if (!exec_mode)
{
// Route to CLI.
return AppArgNotRunnable;
return StatusCode::AppArgNotRunnable;
}
}
@ -769,7 +964,7 @@ int fx_muxer_t::parse_args(
{
assert(exec_mode == true);
trace::error(_X("dotnet exec needs a managed .dll or .exe extension. The application specified was '%s'"), app_candidate.c_str());
return InvalidArgFailure;
return StatusCode::InvalidArgFailure;
}
}
@ -777,10 +972,10 @@ int fx_muxer_t::parse_args(
if (!doesAppExist)
{
trace::error(_X("The application to execute does not exist: '%s'"), app_candidate.c_str());
return InvalidArgFailure;
return StatusCode::InvalidArgFailure;
}
return 0;
return StatusCode::Success;
}
int read_config(
@ -816,7 +1011,7 @@ int read_config(
return StatusCode::InvalidConfigFile;
}
return 0;
return StatusCode::Success;
}
int fx_muxer_t::soft_roll_forward_helper(
@ -833,7 +1028,7 @@ int fx_muxer_t::soft_roll_forward_helper(
{
updated_newest.merge_roll_forward_settings_from(older);
newest_references[fx_name] = updated_newest;
return 0;
return StatusCode::Success;
}
if (older.is_roll_forward_compatible(newer.get_fx_version_number()))
@ -850,16 +1045,16 @@ int fx_muxer_t::soft_roll_forward_helper(
if (older_is_hard_roll_forward)
{
display_retry_framework_trace(older, newer);
return FrameworkCompatRetry;
return StatusCode::FrameworkCompatRetry;
}
display_compatible_framework_trace(newer.get_fx_version(), older);
return 0;
return StatusCode::Success;
}
// Error condition - not compatible with the other reference
display_incompatible_framework_error(newer.get_fx_version(), older);
return FrameworkCompatFailure;
return StatusCode::FrameworkCompatFailure;
}
// Peform a "soft" roll-forward meaning we don't read any physical framework folders
@ -904,7 +1099,7 @@ int fx_muxer_t::read_framework(
}
}
int rc = 0;
int rc = StatusCode::Success;
// Loop through each reference and resolve the framework
for (const fx_reference_t& fx_ref : config.get_frameworks())
@ -1037,7 +1232,7 @@ int fx_muxer_t::read_config_and_execute(
return rc;
}
auto app_config = app->get_runtime_config();
runtime_config_t app_config = app->get_runtime_config();
bool is_framework_dependent = app_config.get_is_framework_dependent();
// Apply the --fx-version option to the first framework
@ -1075,13 +1270,13 @@ int fx_muxer_t::read_config_and_execute(
fx_name_to_fx_reference_map_t oldest_references;
// Read the shared frameworks; retry is necessary when a framework is already resolved, but then a newer compatible version is processed.
int rc = 0;
int rc = StatusCode::Success;
int retry_count = 0;
do
{
fx_definitions.resize(1); // Erase any existing frameworks for re-try
rc = read_framework(host_info, override_settings, app_config, newest_references, oldest_references, fx_definitions);
} while (rc == FrameworkCompatRetry && retry_count++ < Max_Framework_Resolve_Retries);
} while (rc == StatusCode::FrameworkCompatRetry && retry_count++ < Max_Framework_Resolve_Retries);
assert(retry_count < Max_Framework_Resolve_Retries);
@ -1217,6 +1412,84 @@ int fx_muxer_t::execute(
return result;
}
int fx_muxer_t::get_com_activation_delegate(
const host_startup_info_t &host_info,
void **delegate)
{
assert(host_info.is_valid());
const host_mode_t mode = host_mode_t::libhost;
// Read config
fx_definition_vector_t fx_definitions;
auto app = new fx_definition_t();
fx_definitions.push_back(std::unique_ptr<fx_definition_t>(app));
pal::string_t runtime_config = _X("");
fx_reference_t override_settings;
int rc = read_config(*app, host_info.app_path, runtime_config, override_settings);
if (rc != StatusCode::Success)
return rc;
runtime_config_t app_config = app->get_runtime_config();
bool is_framework_dependent = app_config.get_is_framework_dependent();
if (is_framework_dependent)
{
fx_name_to_fx_reference_map_t newest_references;
fx_name_to_fx_reference_map_t oldest_references;
// Read the shared frameworks; retry is necessary when a framework is already resolved, but then a newer compatible version is processed.
rc = StatusCode::Success;
int retry_count = 0;
do
{
fx_definitions.resize(1); // Erase any existing frameworks for re-try
rc = read_framework(host_info, override_settings, app_config, newest_references, oldest_references, fx_definitions);
} while (rc == StatusCode::FrameworkCompatRetry && retry_count++ < Max_Framework_Resolve_Retries);
assert(retry_count < Max_Framework_Resolve_Retries);
if (rc != StatusCode::Success)
{
return rc;
}
display_summary_of_frameworks(fx_definitions, newest_references);
}
// Append specified probe paths first and then config file probe paths into realpaths.
std::vector<pal::string_t> probe_realpaths;
// The tfm is taken from the app.
pal::string_t tfm = get_app(fx_definitions).get_runtime_config().get_tfm();
// Each framework can add probe paths
for (const auto& fx : fx_definitions)
{
for (const auto& path : fx->get_runtime_config().get_probe_paths())
{
append_probe_realpath(path, &probe_realpaths, tfm);
}
}
trace::verbose(_X("Libhost loading occurring as a %s app as per config file [%s]"),
(is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str());
pal::string_t deps_file;
pal::string_t impl_dir;
if (!resolve_hostpolicy_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &impl_dir))
{
return StatusCode::CoreHostLibMissingFailure;
}
pal::string_t additional_deps_serialized;
corehost_init_t init(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions);
rc = get_com_activation_delegate_internal(impl_dir, &init, delegate);
return rc;
}
int fx_muxer_t::handle_exec(
const host_startup_info_t& host_info,
const pal::string_t& app_candidate,

Просмотреть файл

@ -7,25 +7,11 @@ class fx_definition_t;
struct fx_ver_t;
struct host_startup_info_t;
#include <corehost.h>
#include "libhost.h"
const int Max_Framework_Resolve_Retries = 100;
int execute_app(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[]);
int execute_host_command(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[],
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size);
class fx_muxer_t
{
public:
@ -37,7 +23,9 @@ public:
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size);
static bool resolve_sdk_dotnet_path(const pal::string_t& dotnet_root, const pal::string_t& cwd, pal::string_t* cli_sdk);
static int get_com_activation_delegate(
const host_startup_info_t &host_info,
void **delegate);
private:
static int parse_args(
const host_startup_info_t& host_info,

Просмотреть файл

@ -4,7 +4,7 @@
#ifndef __FX_VER_H__
#define __FX_VER_H__
#include "pal.h"
#include <pal.h>
// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not
// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11

Просмотреть файл

@ -9,177 +9,11 @@
#include "fx_muxer.h"
#include "error_codes.h"
#include "libhost.h"
#include "corehost.h"
#include "runtime_config.h"
#include "sdk_info.h"
#include "sdk_resolver.h"
typedef int(*corehost_load_fn) (const host_interface_t* init);
typedef int(*corehost_main_fn) (const int argc, const pal::char_t* argv[]);
typedef int(*corehost_main_with_output_buffer_fn) (const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size);
typedef int(*corehost_unload_fn) ();
typedef void(*corehost_error_writer_fn) (const pal::char_t* message);
typedef corehost_error_writer_fn(*corehost_set_error_writer_fn) (corehost_error_writer_fn error_writer);
int load_host_library_common(
const pal::string_t& lib_dir,
pal::string_t& host_path,
pal::dll_t* h_host,
corehost_load_fn* load_fn,
corehost_unload_fn* unload_fn,
corehost_set_error_writer_fn* set_error_writer_fn)
{
if (!library_exists_in_dir(lib_dir, LIBHOSTPOLICY_NAME, &host_path))
{
return StatusCode::CoreHostLibMissingFailure;
}
// Load library
if (!pal::load_library(&host_path, h_host))
{
trace::info(_X("Load library of %s failed"), host_path.c_str());
return StatusCode::CoreHostLibLoadFailure;
}
// Obtain entrypoint symbols
*load_fn = (corehost_load_fn)pal::get_symbol(*h_host, "corehost_load");
*unload_fn = (corehost_unload_fn)pal::get_symbol(*h_host, "corehost_unload");
*set_error_writer_fn = (corehost_set_error_writer_fn)pal::get_symbol(*h_host, "corehost_set_error_writer");
// It's possible to not have corehost_set_error_writer, since this was only introduced in 3.0
// so 2.0 hostpolicy would not have the export. In this case we will not propagate the error writer
// and errors will still be reported to stderr.
return (*load_fn != nullptr) && (*unload_fn != nullptr)
? StatusCode::Success
: StatusCode::CoreHostEntryPointFailure;
}
int load_host_library(
const pal::string_t& lib_dir,
pal::dll_t* h_host,
corehost_load_fn* load_fn,
corehost_main_fn* main_fn,
corehost_unload_fn* unload_fn,
corehost_set_error_writer_fn* set_error_writer_fn)
{
pal::string_t host_path;
int rc = load_host_library_common(lib_dir, host_path, h_host, load_fn, unload_fn, set_error_writer_fn);
if (rc != StatusCode::Success)
{
return rc;
}
// Obtain entrypoint symbol
*main_fn = (corehost_main_fn)pal::get_symbol(*h_host, "corehost_main");
return (*main_fn != nullptr)
? StatusCode::Success
: StatusCode::CoreHostEntryPointFailure;
}
int load_host_library_with_return(
const pal::string_t& lib_dir,
pal::dll_t* h_host,
corehost_load_fn* load_fn,
corehost_main_with_output_buffer_fn* main_fn,
corehost_unload_fn* unload_fn,
corehost_set_error_writer_fn* set_error_writer_fn)
{
pal::string_t host_path;
int rc = load_host_library_common(lib_dir, host_path, h_host, load_fn, unload_fn, set_error_writer_fn);
if (rc != StatusCode::Success)
{
return rc;
}
// Obtain entrypoint symbol
*main_fn = (corehost_main_with_output_buffer_fn)pal::get_symbol(*h_host, "corehost_main_with_output_buffer");
return (*main_fn != nullptr)
? StatusCode::Success
: StatusCode::CoreHostEntryPointFailure;
}
int execute_app(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[])
{
pal::dll_t corehost;
corehost_main_fn host_main = nullptr;
corehost_load_fn host_load = nullptr;
corehost_unload_fn host_unload = nullptr;
corehost_set_error_writer_fn host_set_error_writer = nullptr;
int code = load_host_library(impl_dll_dir, &corehost, &host_load, &host_main, &host_unload, &host_set_error_writer);
if (code != StatusCode::Success)
{
trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, impl_dll_dir.c_str());
return code;
}
// Previous hostfxr trace messages must be printed before calling trace::setup in hostpolicy
trace::flush();
{
propagate_error_writer_t propagate_error_writer_to_corehost(host_set_error_writer);
const host_interface_t& intf = init->get_host_init_data();
if ((code = host_load(&intf)) == 0)
{
code = host_main(argc, argv);
(void)host_unload();
}
}
pal::unload_library(corehost);
return code;
}
int execute_host_command(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[],
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size)
{
pal::dll_t corehost;
corehost_main_with_output_buffer_fn host_main = nullptr;
corehost_load_fn host_load = nullptr;
corehost_unload_fn host_unload = nullptr;
corehost_set_error_writer_fn host_set_error_writer = nullptr;
int code = load_host_library_with_return(impl_dll_dir, &corehost, &host_load, &host_main, &host_unload, &host_set_error_writer);
if (code != StatusCode::Success)
{
trace::error(_X("An error occurred while loading required library %s from [%s] for a host command"), LIBHOSTPOLICY_NAME, impl_dll_dir.c_str());
return code;
}
// Previous hostfxr trace messages must be printed before calling trace::setup in hostpolicy
trace::flush();
{
propagate_error_writer_t propagate_error_writer_to_corehost(host_set_error_writer);
const host_interface_t& intf = init->get_host_init_data();
if ((code = host_load(&intf)) == 0)
{
code = host_main(argc, argv, result_buffer, buffer_size, required_buffer_size);
(void)host_unload();
}
}
pal::unload_library(corehost);
return code;
}
SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path)
{
trace::setup();
@ -188,8 +22,7 @@ SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[
host_startup_info_t startup_info(host_path, dotnet_root, app_path);
fx_muxer_t muxer;
return muxer.execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr);
return fx_muxer_t::execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr);
}
SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[])
@ -201,8 +34,7 @@ SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[])
host_startup_info_t startup_info;
startup_info.parse(argc, argv);
fx_muxer_t muxer;
return muxer.execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr);
return fx_muxer_t::execute(pal::string_t(), argc, argv, startup_info, nullptr, 0, nullptr);
}
// [OBSOLETE] Replaced by hostfxr_resolve_sdk2
@ -509,7 +341,7 @@ SHARED_API int32_t hostfxr_get_available_sdks(
//
// Return value:
// 0 on success, otherwise failure
// 0x800080980 - Buffer is too small (HostApiBufferTooSmall)
// 0x80008098 - Buffer is too small (HostApiBufferTooSmall)
//
// String encoding:
// Windows - UTF-16 (pal::char_t is 2 byte wchar_t)
@ -530,13 +362,10 @@ SHARED_API int32_t hostfxr_get_native_search_directories(const int argc, const p
host_startup_info_t startup_info;
startup_info.parse(argc, argv);
fx_muxer_t muxer;
int rc = muxer.execute(_X("get-native-search-directories"), argc, argv, startup_info, buffer, buffer_size, required_buffer_size);
int rc = fx_muxer_t::execute(_X("get-native-search-directories"), argc, argv, startup_info, buffer, buffer_size, required_buffer_size);
return rc;
}
typedef void(*hostfxr_error_writer_fn)(const pal::char_t* message);
//
@ -566,3 +395,37 @@ SHARED_API hostfxr_error_writer_fn hostfxr_set_error_writer(hostfxr_error_writer
{
return trace::set_error_writer(error_writer);
}
//
// Gets a typed delegate to perform an action on the currently loaded CoreCLR or on a newly created one.
//
// Parameters:
// libhost_path
// Absolute path of the entry hosting library
// dotnet_root
// app_path
// delegate
// COM activation delegate.
// Return value:
// The error code result.
//
// A new CoreCLR instance will be created or reused if the existing instance can satisfy the configuration
// requirements supplied by the runtimeconfig.json file.
//
SHARED_API int32_t hostfxr_get_com_activation_delegate(
const pal::char_t* libhost_path,
const pal::char_t* dotnet_root,
const pal::char_t* app_path,
void **delegate)
{
if (libhost_path == nullptr || dotnet_root == nullptr || delegate == nullptr)
return StatusCode::InvalidArgFailure;
trace::setup();
trace::info(_X("--- Invoked hostfxr comhost [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH));
host_startup_info_t startup_info{ libhost_path, dotnet_root, app_path };
return fx_muxer_t::get_com_activation_delegate(startup_info, delegate);
}

Просмотреть файл

@ -24,7 +24,7 @@ struct host_startup_info_t
static int get_host_path(int argc, const pal::char_t* argv[], pal::string_t* host_path);
pal::string_t host_path; // The path to the current executable.
pal::string_t host_path; // The path to the current hosting binary.
pal::string_t dotnet_root; // The path to the framework.
pal::string_t app_path; // For apphost, the path to the app dll; for muxer, not applicable as this information is not yet parsed.
};

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <mutex>
#include "pal.h"
#include "args.h"
#include "trace.h"
@ -8,272 +9,368 @@
#include "fx_muxer.h"
#include "utils.h"
#include "coreclr.h"
#include "corehost.h"
#include "cpprest/json.h"
#include "libhost.h"
#include "error_codes.h"
#include "breadcrumbs.h"
#include "host_startup_info.h"
hostpolicy_init_t g_init;
int run(const arguments_t& args, pal::string_t* out_host_command_result = nullptr)
namespace
{
// Load the deps resolver
deps_resolver_t resolver(
args,
g_init.fx_definitions,
/* root_framework_rid_fallback_graph */ nullptr, // This means that the fx_definitions contains the root framework
g_init.is_framework_dependent);
std::mutex g_init_lock;
bool g_init_done;
hostpolicy_init_t g_init;
pal::string_t resolver_errors;
if (!resolver.valid(&resolver_errors))
std::shared_ptr<coreclr_t> g_coreclr;
std::mutex g_lib_lock;
std::weak_ptr<coreclr_t> g_lib_coreclr;
class prepare_to_run_t
{
trace::error(_X("Error initializing the dependency resolver: %s"), resolver_errors.c_str());
return StatusCode::ResolverInitFailure;
public:
prepare_to_run_t(
hostpolicy_init_t &hostpolicy_init,
const arguments_t& args,
bool breadcrumbs_enabled)
: _hostpolicy_init{ hostpolicy_init }
, _resolver
{
args,
hostpolicy_init.fx_definitions,
/* root_framework_rid_fallback_graph */ nullptr, // This means that the fx_definitions contains the root framework
hostpolicy_init.is_framework_dependent
}
, _breadcrumbs_enabled{ breadcrumbs_enabled }
{ }
int build_coreclr_properties(
coreclr_property_bag_t &properties,
pal::string_t &clr_path,
pal::string_t &clr_dir)
{
pal::string_t resolver_errors;
if (!_resolver.valid(&resolver_errors))
{
trace::error(_X("Error initializing the dependency resolver: %s"), resolver_errors.c_str());
return StatusCode::ResolverInitFailure;
}
probe_paths_t probe_paths;
// Setup breadcrumbs.
if (_breadcrumbs_enabled)
{
pal::string_t policy_name = _STRINGIFY(HOST_POLICY_PKG_NAME);
pal::string_t policy_version = _STRINGIFY(HOST_POLICY_PKG_VER);
// Always insert the hostpolicy that the code is running on.
_breadcrumbs.insert(policy_name);
_breadcrumbs.insert(policy_name + _X(",") + policy_version);
if (!_resolver.resolve_probe_paths(&probe_paths, &_breadcrumbs))
{
return StatusCode::ResolverResolveFailure;
}
}
else
{
if (!_resolver.resolve_probe_paths(&probe_paths, nullptr))
{
return StatusCode::ResolverResolveFailure;
}
}
clr_path = probe_paths.coreclr;
if (clr_path.empty() || !pal::realpath(&clr_path))
{
trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting COREHOST_TRACE environment variable to 1"));;
return StatusCode::CoreClrResolveFailure;
}
// Get path in which CoreCLR is present.
clr_dir = get_directory(clr_path);
// System.Private.CoreLib.dll is expected to be next to CoreCLR.dll - add its path to the TPA list.
pal::string_t corelib_path = clr_dir;
append_path(&corelib_path, CORELIB_NAME);
// Append CoreLib path
if (probe_paths.tpa.back() != PATH_SEPARATOR)
{
probe_paths.tpa.push_back(PATH_SEPARATOR);
}
probe_paths.tpa.append(corelib_path);
probe_paths.tpa.push_back(PATH_SEPARATOR);
pal::string_t clrjit_path = probe_paths.clrjit;
if (clrjit_path.empty())
{
trace::warning(_X("Could not resolve CLRJit path"));
}
else if (pal::realpath(&clrjit_path))
{
trace::verbose(_X("The resolved JIT path is '%s'"), clrjit_path.c_str());
}
else
{
clrjit_path.clear();
trace::warning(_X("Could not resolve symlink to CLRJit path '%s'"), probe_paths.clrjit.c_str());
}
pal::pal_clrstring(probe_paths.tpa, &_tpa_paths_cstr);
pal::pal_clrstring(_resolver.get_app_dir(), &_app_base_cstr);
pal::pal_clrstring(probe_paths.native, &_native_dirs_cstr);
pal::pal_clrstring(probe_paths.resources, &_resources_dirs_cstr);
const fx_definition_vector_t &fx_definitions = _resolver.get_fx_definitions();
pal::string_t fx_deps_str;
if (_resolver.is_framework_dependent())
{
// Use the root fx to define FX_DEPS_FILE
fx_deps_str = get_root_framework(fx_definitions).get_deps_file();
}
pal::pal_clrstring(fx_deps_str, &_fx_deps);
fx_definition_vector_t::iterator fx_begin;
fx_definition_vector_t::iterator fx_end;
_resolver.get_app_fx_definition_range(&fx_begin, &fx_end);
pal::string_t app_context_deps_str;
fx_definition_vector_t::iterator fx_curr = fx_begin;
while (fx_curr != fx_end)
{
if (fx_curr != fx_begin)
app_context_deps_str += _X(';');
app_context_deps_str += (*fx_curr)->get_deps_file();
++fx_curr;
}
pal::pal_clrstring(app_context_deps_str, &_app_context_deps);
pal::pal_clrstring(_resolver.get_lookup_probe_directories(), &_probe_directories);
if (_resolver.is_framework_dependent())
{
pal::pal_clrstring(get_root_framework(fx_definitions).get_found_version(), &_clr_library_version);
}
else
{
pal::pal_clrstring(_resolver.get_coreclr_library_version(), &_clr_library_version);
}
properties.add(common_property::TrustedPlatformAssemblies, _tpa_paths_cstr.data());
properties.add(common_property::NativeDllSearchDirectories, _native_dirs_cstr.data());
properties.add(common_property::PlatformResourceRoots, _resources_dirs_cstr.data());
properties.add(common_property::AppDomainCompatSwitch, "UseLatestBehaviorWhenTFMNotSpecified");
properties.add(common_property::AppContextBaseDirectory, _app_base_cstr.data());
properties.add(common_property::AppContextDepsFiles, _app_context_deps.data());
properties.add(common_property::FxDepsFile, _fx_deps.data());
properties.add(common_property::ProbingDirectories, _probe_directories.data());
properties.add(common_property::FxProductVersion, _clr_library_version.data());
if (!clrjit_path.empty())
{
pal::pal_clrstring(clrjit_path, &_clrjit_path_cstr);
properties.add(common_property::JitPath, _clrjit_path_cstr.data());
}
bool set_app_paths = false;
// Runtime options config properties.
for (int i = 0; i < _hostpolicy_init.cfg_keys.size(); ++i)
{
// Provide opt-in compatible behavior by using the switch to set APP_PATHS
if (pal::cstrcasecmp(_hostpolicy_init.cfg_keys[i].data(), "Microsoft.NETCore.DotNetHostPolicy.SetAppPaths") == 0)
{
set_app_paths = (pal::cstrcasecmp(_hostpolicy_init.cfg_values[i].data(), "true") == 0);
}
properties.add(_hostpolicy_init.cfg_keys[i].data(), _hostpolicy_init.cfg_values[i].data());
}
// App paths and App NI paths.
// Note: Keep this check outside of the loop above since the _last_ key wins
// and that could indicate the app paths shouldn't be set.
if (set_app_paths)
{
properties.add(common_property::AppPaths, _app_base_cstr.data());
properties.add(common_property::AppNIPaths, _app_base_cstr.data());
}
// Startup hooks
pal::string_t startup_hooks;
if (pal::getenv(_X("DOTNET_STARTUP_HOOKS"), &startup_hooks))
{
pal::pal_clrstring(startup_hooks, &_startup_hooks_cstr);
properties.add(common_property::StartUpHooks, _startup_hooks_cstr.data());
}
return StatusCode::Success;
}
const std::unordered_set<pal::string_t>& breadcrumbs() const
{
return _breadcrumbs;
}
private:
hostpolicy_init_t &_hostpolicy_init;
deps_resolver_t _resolver;
const bool _breadcrumbs_enabled;
std::unordered_set<pal::string_t> _breadcrumbs;
// Note: these variables' lifetime should be longer than a call to coreclr_initialize.
std::vector<char> _tpa_paths_cstr;
std::vector<char> _app_base_cstr;
std::vector<char> _native_dirs_cstr;
std::vector<char> _resources_dirs_cstr;
std::vector<char> _fx_deps;
std::vector<char> _app_context_deps;
std::vector<char> _clrjit_path_cstr;
std::vector<char> _probe_directories;
std::vector<char> _clr_library_version;
std::vector<char> _startup_hooks_cstr;
};
}
int run_as_lib(
hostpolicy_init_t &hostpolicy_init,
const arguments_t &args,
std::shared_ptr<coreclr_t> &coreclr)
{
coreclr = g_lib_coreclr.lock();
if (coreclr != nullptr)
{
// [TODO] Validate the current CLR instance is acceptable for this request
trace::info(_X("Using existing CoreClr instance"));
return StatusCode::Success;
}
// Setup breadcrumbs. Breadcrumbs are not enabled for API calls because they do not execute
{
std::lock_guard<std::mutex> lock{ g_lib_lock };
coreclr = g_lib_coreclr.lock();
if (coreclr != nullptr)
{
trace::info(_X("Using existing CoreClr instance"));
return StatusCode::Success;
}
prepare_to_run_t prep{ hostpolicy_init, args, false /* breadcrumbs_enabled */ };
// Build variables for CoreCLR instantiation
coreclr_property_bag_t properties;
pal::string_t clr_path;
pal::string_t clr_dir;
int rc = prep.build_coreclr_properties(properties, clr_path, clr_dir);
if (rc != StatusCode::Success)
return rc;
// Verbose logging
if (trace::is_enabled())
{
properties.log_properties();
}
std::vector<char> host_path;
pal::pal_clrstring(args.host_path, &host_path);
// Create a CoreCLR instance
trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), clr_path.c_str(), clr_dir.c_str());
std::unique_ptr<coreclr_t> coreclr_local;
auto hr = coreclr_t::create(
clr_dir,
host_path.data(),
"clr_libhost",
properties,
coreclr_local);
if (!SUCCEEDED(hr))
{
trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr);
return StatusCode::CoreClrInitFailure;
}
assert(g_coreclr == nullptr);
g_coreclr = std::move(coreclr_local);
g_lib_coreclr = g_coreclr;
}
coreclr = g_coreclr;
return StatusCode::Success;
}
int run_as_app(
hostpolicy_init_t &hostpolicy_init,
const arguments_t &args,
pal::string_t* out_host_command_result = nullptr)
{
// Breadcrumbs are not enabled for API calls because they do not execute
// the app and may be re-entry
probe_paths_t probe_paths;
std::unordered_set<pal::string_t> breadcrumbs;
bool breadcrumbs_enabled = (out_host_command_result == nullptr);
if (breadcrumbs_enabled)
{
pal::string_t policy_name = _STRINGIFY(HOST_POLICY_PKG_NAME);
pal::string_t policy_version = _STRINGIFY(HOST_POLICY_PKG_VER);
prepare_to_run_t prep{ hostpolicy_init, args, breadcrumbs_enabled };
// Always insert the hostpolicy that the code is running on.
breadcrumbs.insert(policy_name);
breadcrumbs.insert(policy_name + _X(",") + policy_version);
if (!resolver.resolve_probe_paths(&probe_paths, &breadcrumbs))
{
return StatusCode::ResolverResolveFailure;
}
}
else
{
if (!resolver.resolve_probe_paths(&probe_paths, nullptr))
{
return StatusCode::ResolverResolveFailure;
}
}
pal::string_t clr_path = probe_paths.coreclr;
if (clr_path.empty() || !pal::realpath(&clr_path))
{
trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting COREHOST_TRACE environment variable to 1"));;
return StatusCode::CoreClrResolveFailure;
}
// Get path in which CoreCLR is present.
pal::string_t clr_dir = get_directory(clr_path);
// System.Private.CoreLib.dll is expected to be next to CoreCLR.dll - add its path to the TPA list.
pal::string_t corelib_path = clr_dir;
append_path(&corelib_path, CORELIB_NAME);
// Append CoreLib path
if (probe_paths.tpa.back() != PATH_SEPARATOR)
{
probe_paths.tpa.push_back(PATH_SEPARATOR);
}
probe_paths.tpa.append(corelib_path);
probe_paths.tpa.push_back(PATH_SEPARATOR);
pal::string_t clrjit_path = probe_paths.clrjit;
if (clrjit_path.empty())
{
trace::warning(_X("Could not resolve CLRJit path"));
}
else if (pal::realpath(&clrjit_path))
{
trace::verbose(_X("The resolved JIT path is '%s'"), clrjit_path.c_str());
}
else
{
clrjit_path.clear();
trace::warning(_X("Could not resolve symlink to CLRJit path '%s'"), probe_paths.clrjit.c_str());
}
// Build CoreCLR properties
std::vector<const char*> property_keys = {
"TRUSTED_PLATFORM_ASSEMBLIES",
"NATIVE_DLL_SEARCH_DIRECTORIES",
"PLATFORM_RESOURCE_ROOTS",
"AppDomainCompatSwitch",
// Workaround: mscorlib does not resolve symlinks for AppContext.BaseDirectory dotnet/coreclr/issues/2128
"APP_CONTEXT_BASE_DIRECTORY",
"APP_CONTEXT_DEPS_FILES",
"FX_DEPS_FILE",
"PROBING_DIRECTORIES",
"FX_PRODUCT_VERSION"
};
// Note: these variables' lifetime should be longer than coreclr_initialize.
std::vector<char> tpa_paths_cstr, app_base_cstr, native_dirs_cstr, resources_dirs_cstr, fx_deps, deps, clrjit_path_cstr, probe_directories, clr_library_version, startup_hooks_cstr;
pal::pal_clrstring(probe_paths.tpa, &tpa_paths_cstr);
pal::pal_clrstring(args.app_root, &app_base_cstr);
pal::pal_clrstring(probe_paths.native, &native_dirs_cstr);
pal::pal_clrstring(probe_paths.resources, &resources_dirs_cstr);
pal::string_t fx_deps_str;
if (resolver.get_fx_definitions().size() >= 2)
{
// Use the root fx to define FX_DEPS_FILE
fx_deps_str = get_root_framework(resolver.get_fx_definitions()).get_deps_file();
}
pal::pal_clrstring(fx_deps_str, &fx_deps);
// Get all deps files
pal::string_t allDeps;
for (int i = 0; i < resolver.get_fx_definitions().size(); ++i)
{
allDeps += resolver.get_fx_definitions()[i]->get_deps_file();
if (i < resolver.get_fx_definitions().size() - 1)
{
allDeps += _X(";");
}
}
pal::pal_clrstring(allDeps, &deps);
pal::pal_clrstring(resolver.get_lookup_probe_directories(), &probe_directories);
if (resolver.is_framework_dependent())
{
pal::pal_clrstring(get_root_framework(resolver.get_fx_definitions()).get_found_version() , &clr_library_version);
}
else
{
pal::pal_clrstring(resolver.get_coreclr_library_version(), &clr_library_version);
}
std::vector<const char*> property_values = {
// TRUSTED_PLATFORM_ASSEMBLIES
tpa_paths_cstr.data(),
// NATIVE_DLL_SEARCH_DIRECTORIES
native_dirs_cstr.data(),
// PLATFORM_RESOURCE_ROOTS
resources_dirs_cstr.data(),
// AppDomainCompatSwitch
"UseLatestBehaviorWhenTFMNotSpecified",
// APP_CONTEXT_BASE_DIRECTORY
app_base_cstr.data(),
// APP_CONTEXT_DEPS_FILES,
deps.data(),
// FX_DEPS_FILE
fx_deps.data(),
//PROBING_DIRECTORIES
probe_directories.data(),
//FX_PRODUCT_VERSION
clr_library_version.data()
};
if (!clrjit_path.empty())
{
pal::pal_clrstring(clrjit_path, &clrjit_path_cstr);
property_keys.push_back("JIT_PATH");
property_values.push_back(clrjit_path_cstr.data());
}
bool set_app_paths = false;
// Runtime options config properties.
for (int i = 0; i < g_init.cfg_keys.size(); ++i)
{
// Provide opt-in compatible behavior by using the switch to set APP_PATHS
if (pal::cstrcasecmp(g_init.cfg_keys[i].data(), "Microsoft.NETCore.DotNetHostPolicy.SetAppPaths") == 0)
{
set_app_paths = (pal::cstrcasecmp(g_init.cfg_values[i].data(), "true") == 0);
}
property_keys.push_back(g_init.cfg_keys[i].data());
property_values.push_back(g_init.cfg_values[i].data());
}
// App paths and App NI paths
if (set_app_paths)
{
property_keys.push_back("APP_PATHS");
property_keys.push_back("APP_NI_PATHS");
property_values.push_back(app_base_cstr.data());
property_values.push_back(app_base_cstr.data());
}
// Startup hooks
pal::string_t startup_hooks;
if (pal::getenv(_X("DOTNET_STARTUP_HOOKS"), &startup_hooks))
{
pal::pal_clrstring(startup_hooks, &startup_hooks_cstr);
property_keys.push_back("STARTUP_HOOKS");
property_values.push_back(startup_hooks_cstr.data());
}
size_t property_size = property_keys.size();
assert(property_keys.size() == property_values.size());
unsigned int exit_code = 1;
// Build variables for CoreCLR instantiation
coreclr_property_bag_t properties;
pal::string_t clr_path;
pal::string_t clr_dir;
int rc = prep.build_coreclr_properties(properties, clr_path, clr_dir);
if (rc != StatusCode::Success)
return rc;
// Check for host command(s)
if (pal::strcasecmp(g_init.host_command.c_str(), _X("get-native-search-directories")) == 0)
if (pal::strcasecmp(hostpolicy_init.host_command.c_str(), _X("get-native-search-directories")) == 0)
{
// Verify property_keys[1] contains the correct information
if (pal::cstrcasecmp(property_keys[1], "NATIVE_DLL_SEARCH_DIRECTORIES"))
const char *value;
if (!properties.try_get(common_property::NativeDllSearchDirectories, &value))
{
trace::error(_X("get-native-search-directories failed to find NATIVE_DLL_SEARCH_DIRECTORIES property"));
exit_code = HostApiFailed;
}
else
{
assert(out_host_command_result != nullptr);
pal::clr_palstring(property_values[1], out_host_command_result);
exit_code = 0; // Success
return StatusCode::HostApiFailed;
}
return exit_code;
}
// Bind CoreCLR
trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), clr_path.c_str(), clr_dir.c_str());
if (!coreclr::bind(clr_dir))
{
trace::error(_X("Failed to bind to CoreCLR at '%s'"), clr_path.c_str());
return StatusCode::CoreClrBindFailure;
assert(out_host_command_result != nullptr);
pal::clr_palstring(value, out_host_command_result);
return StatusCode::Success;
}
// Verbose logging
if (trace::is_enabled())
{
for (size_t i = 0; i < property_size; ++i)
{
pal::string_t key, val;
pal::clr_palstring(property_keys[i], &key);
pal::clr_palstring(property_values[i], &val);
trace::verbose(_X("Property %s = %s"), key.c_str(), val.c_str());
}
properties.log_properties();
}
std::vector<char> host_path;
pal::pal_clrstring(args.host_path, &host_path);
// Initialize CoreCLR
coreclr::host_handle_t host_handle;
coreclr::domain_id_t domain_id;
auto hr = coreclr::initialize(
// Create a CoreCLR instance
trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), clr_path.c_str(), clr_dir.c_str());
std::unique_ptr<coreclr_t> coreclr;
auto hr = coreclr_t::create(
clr_dir,
host_path.data(),
"clrhost",
property_keys.data(),
property_values.data(),
property_size,
&host_handle,
&domain_id);
properties,
coreclr);
if (!SUCCEEDED(hr))
{
trace::error(_X("Failed to initialize CoreCLR, HRESULT: 0x%X"), hr);
trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr);
return StatusCode::CoreClrInitFailure;
}
assert(g_coreclr == nullptr);
g_coreclr = std::move(coreclr);
{
std::lock_guard<std::mutex> lock{ g_lib_lock };
g_lib_coreclr = g_coreclr;
}
// Initialize clr strings for arguments
std::vector<std::vector<char>> argv_strs(args.app_argc);
std::vector<const char*> argv(args.app_argc);
@ -301,16 +398,15 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt
pal::pal_clrstring(args.managed_application, &managed_app);
// Leave breadcrumbs for servicing.
breadcrumb_writer_t writer(breadcrumbs_enabled, &breadcrumbs);
breadcrumb_writer_t writer(breadcrumbs_enabled, prep.breadcrumbs());
writer.begin_write();
// Previous hostpolicy trace messages must be printed before executing assembly
trace::flush();
// Execute the application
hr = coreclr::execute_assembly(
host_handle,
domain_id,
unsigned int exit_code;
hr = g_coreclr->execute_assembly(
argv.size(),
argv.data(),
managed_app.data(),
@ -322,15 +418,15 @@ int run(const arguments_t& args, pal::string_t* out_host_command_result = nullpt
return StatusCode::CoreClrExeFailure;
}
trace::info(_X("Execute managed assembly exit code: 0x%X"), exit_code);
// Shut down the CoreCLR
hr = coreclr::shutdown(host_handle, domain_id, (int*)&exit_code);
hr = g_coreclr->shutdown((int*)&exit_code);
if (!SUCCEEDED(hr))
{
trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
}
coreclr::unload();
return exit_code;
// The breadcrumb destructor will join to the background thread to finish writing
@ -347,22 +443,46 @@ void trace_hostpolicy_entrypoint_invocation(const pal::string_t& entryPointName)
entryPointName.c_str());
}
//
// Loads and initilizes the hostpolicy.
//
// If hostpolicy is already initalized, the library will not be
// reinitialized.
//
SHARED_API int corehost_load(host_interface_t* init)
{
assert(init != nullptr);
std::lock_guard<std::mutex> lock{ g_init_lock };
if (g_init_done)
{
// Since the host command is set during load _and_
// load is considered re-entrant due to how testing is
// done, permit the re-initialization of the host command.
hostpolicy_init_t::init_host_command(init, &g_init);
return StatusCode::Success;
}
trace::setup();
// Re-initialize global state in case of re-entry
g_init = hostpolicy_init_t();
g_init = hostpolicy_init_t{};
if (!hostpolicy_init_t::init(init, &g_init))
{
g_init_done = false;
return StatusCode::LibHostInitFailure;
}
return 0;
g_init_done = true;
return StatusCode::Success;
}
int corehost_main_init(const int argc, const pal::char_t* argv[], const pal::string_t& location, arguments_t& args)
int corehost_main_init(
hostpolicy_init_t &hostpolicy_init,
const int argc,
const pal::char_t* argv[],
const pal::string_t& location,
arguments_t& args)
{
if (trace::is_enabled())
{
@ -374,36 +494,36 @@ int corehost_main_init(const int argc, const pal::char_t* argv[], const pal::str
}
trace::info(_X("}"));
trace::info(_X("Deps file: %s"), g_init.deps_file.c_str());
for (const auto& probe : g_init.probe_paths)
trace::info(_X("Deps file: %s"), hostpolicy_init.deps_file.c_str());
for (const auto& probe : hostpolicy_init.probe_paths)
{
trace::info(_X("Additional probe dir: %s"), probe.c_str());
}
}
// Take care of arguments
if (!g_init.host_info.is_valid())
if (!hostpolicy_init.host_info.is_valid())
{
// For backwards compat (older hostfxr), default the host_info
g_init.host_info.parse(argc, argv);
hostpolicy_init.host_info.parse(argc, argv);
}
if (!parse_arguments(g_init, argc, argv, args))
if (!parse_arguments(hostpolicy_init, argc, argv, args))
{
return StatusCode::LibHostInvalidArgs;
}
args.trace();
return 0;
return StatusCode::Success;
}
SHARED_API int corehost_main(const int argc, const pal::char_t* argv[])
{
arguments_t args;
int rc = corehost_main_init(argc, argv, _X("corehost_main"), args);
int rc = corehost_main_init(g_init, argc, argv, _X("corehost_main"), args);
if (!rc)
{
rc = run(args);
rc = run_as_app(g_init, args);
}
return rc;
@ -412,14 +532,13 @@ SHARED_API int corehost_main(const int argc, const pal::char_t* argv[])
SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size)
{
arguments_t args;
int rc = corehost_main_init(argc, argv, _X("corehost_main_with_output_buffer "), args);
int rc = corehost_main_init(g_init, argc, argv, _X("corehost_main_with_output_buffer"), args);
if (!rc)
{
if (g_init.host_command == _X("get-native-search-directories"))
{
pal::string_t output_string;
rc = run(args, &output_string);
rc = run_as_app(g_init, args, &output_string);
if (!rc)
{
// Get length in character count not including null terminator
@ -427,7 +546,7 @@ SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_
if (len + 1 > buffer_size)
{
rc = HostApiBufferTooSmall;
rc = StatusCode::HostApiBufferTooSmall;
*required_buffer_size = len + 1;
trace::info(_X("get-native-search-directories failed with buffer too small"), output_string.c_str());
}
@ -443,16 +562,62 @@ SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_
else
{
trace::error(_X("Unknown command: %s"), g_init.host_command.c_str());
rc = LibHostUnknownCommand;
rc = StatusCode::LibHostUnknownCommand;
}
}
return rc;
}
int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_t& location, arguments_t& args)
{
if (trace::is_enabled())
{
trace_hostpolicy_entrypoint_invocation(location);
trace::info(_X("}"));
trace::info(_X("Deps file: %s"), hostpolicy_init.deps_file.c_str());
for (const auto& probe : hostpolicy_init.probe_paths)
{
trace::info(_X("Additional probe dir: %s"), probe.c_str());
}
}
// Host info should always be valid in the delegate scenario
assert(hostpolicy_init.host_info.is_valid());
if (!parse_arguments(hostpolicy_init, 0, nullptr, args))
{
return StatusCode::LibHostInvalidArgs;
}
args.trace();
return StatusCode::Success;
}
SHARED_API int corehost_get_com_activation_delegate(void **delegate)
{
arguments_t args;
int rc = corehost_libhost_init(g_init, _X("corehost_get_com_activation_delegate"), args);
if (rc != StatusCode::Success)
return rc;
std::shared_ptr<coreclr_t> coreclr;
rc = run_as_lib(g_init, args, coreclr);
if (rc != StatusCode::Success)
return rc;
return coreclr->create_delegate(
"System.Private.CoreLib",
"Internal.Runtime.InteropServices.ComActivator",
"GetClassFactoryForTypeInternal",
delegate);
}
SHARED_API int corehost_unload()
{
return 0;
return StatusCode::Success;
}
typedef void(*corehost_resolve_component_dependencies_result_fn)(
@ -491,14 +656,17 @@ SHARED_API int corehost_resolve_component_dependencies(
return StatusCode::CoreHostLibLoadFailure;
}
// If the current host mode is libhost, use apphost instead.
host_mode_t host_mode = g_init.host_mode == host_mode_t::libhost ? host_mode_t::apphost : g_init.host_mode;
// Initialize arguments (basically the structure describing the input app/component to resolve)
arguments_t args;
if (!init_arguments(
component_main_assembly_path,
g_init.host_info,
g_init.tfm,
g_init.host_mode,
/* additional_deps_serialized */ pal::string_t(), // Additiona deps - don't use those from the app, they're already in the app
host_mode,
/* additional_deps_serialized */ pal::string_t(), // Additional deps - don't use those from the app, they're already in the app
/* deps_file */ pal::string_t(), // Avoid using any other deps file than the one next to the component
g_init.probe_paths,
args))

Просмотреть файл

@ -62,7 +62,7 @@ endif()
add_definitions(-D_NO_ASYNCRTIMP)
add_definitions(-D_NO_PPLXIMP)
add_definitions(-DCOREHOST_MAKE_DLL=1)
add_definitions(-DEXPORT_SHARED_API=1)
set(RESOURCES)
if(WIN32)

Просмотреть файл

@ -18,9 +18,11 @@ enum host_mode_t
apphost, // Invoked as <appname>.exe from the application base; this is the renamed "apphost.exe".
split_fx // Invoked as "corehost.exe" for xunit scenarios. Supported for backwards compat for 1.x apps.
split_fx, // Invoked as "corehost.exe" for xunit scenarios. Supported for backwards compat for 1.x apps.
// Split FX means, the host is operating like "corerun.exe" in a split location from the application base (CORE_ROOT equivalent),
// but it has its "hostfxr.dll" next to it.
libhost, // Invoked from a non-exe scenario (e.g. COM Activation or self-hosting native application)
};
class fx_ver_t;
@ -378,10 +380,8 @@ struct hostpolicy_init_t
}
}
if (input->version_lo >= offsetof(host_interface_t, host_command) + sizeof(input->host_command))
{
init->host_command = input->host_command;
}
// Initialize the host command
init_host_command(input, init);
if (input->version_lo >= offsetof(host_interface_t, host_info_host_path) + sizeof(input->host_info_host_path))
{
@ -394,6 +394,14 @@ struct hostpolicy_init_t
return true;
}
static void init_host_command(host_interface_t* input, hostpolicy_init_t* init)
{
if (input->version_lo >= offsetof(host_interface_t, host_command) + sizeof(input->host_command))
{
init->host_command = input->host_command;
}
}
private:
static void make_palstr_arr(int argc, const pal::char_t** argv, std::vector<pal::string_t>* out)
{

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef _COREHOST_COMMON_COREHOST_H_
#define _COREHOST_COMMON_COREHOST_H_
#if FEATURE_LIBHOST
#include <pal.h>
//
// See ComActivator class in System.Private.CoreLib
//
struct com_activation_context
{
GUID class_id;
GUID interface_id;
const pal::char_t *assembly_path;
const pal::char_t *assembly_name;
const pal::char_t *type_name;
void **class_factory_dest;
};
using com_activation_fn = int(*)(com_activation_context*);
int get_com_activation_delegate(
pal::string_t *app_path,
com_activation_fn *delegate);
#endif
#endif //_COREHOST_COMMON_COREHOST_H_

Просмотреть файл

@ -92,7 +92,7 @@
namespace pal
{
#if defined(_WIN32)
#ifdef COREHOST_MAKE_DLL
#ifdef EXPORT_SHARED_API
#define SHARED_API extern "C" __declspec(dllexport)
#else
#define SHARED_API
@ -110,6 +110,7 @@ namespace pal
// typedef std::basic_ifstream<pal::char_t> ifstream_t.
typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef std::basic_istream<char> istream_t;
typedef HRESULT hresult_t;
typedef HMODULE dll_t;
typedef FARPROC proc_t;
@ -135,7 +136,7 @@ namespace pal
bool clr_palstring(const char* cstr, pal::string_t* out);
#else
#ifdef COREHOST_MAKE_DLL
#ifdef EXPORT_SHARED_API
#define SHARED_API extern "C" __attribute__((__visibility__("default")))
#else
#define SHARED_API
@ -157,6 +158,7 @@ namespace pal
typedef std::stringstream stringstream_t;
typedef std::basic_ifstream<char> ifstream_t;
typedef std::istreambuf_iterator<ifstream_t::char_type> istreambuf_iterator_t;
typedef std::basic_istream<char> istream_t;
typedef int hresult_t;
typedef void* dll_t;
typedef void* proc_t;
@ -213,6 +215,8 @@ namespace pal
void readdir_onlydirectories(const string_t& path, std::vector<pal::string_t>* list);
bool get_own_executable_path(string_t* recv);
bool get_own_module_path(string_t* recv);
bool get_current_module(dll_t *mod);
bool getenv(const char_t* name, string_t* recv);
bool get_default_servicing_directory(string_t* recv);

Просмотреть файл

@ -507,6 +507,16 @@ bool pal::get_own_executable_path(pal::string_t* recv)
}
#endif
bool pal::get_own_module_path(string_t* recv)
{
return false;
}
bool pal::get_current_module(dll_t *mod)
{
return false;
}
// Returns true only if an env variable can be read successfully to be non-empty.
bool pal::getenv(const pal::char_t* name, pal::string_t* recv)
{

Просмотреть файл

@ -15,22 +15,30 @@
bool GetModuleFileNameWrapper(HMODULE hModule, pal::string_t* recv)
{
pal::string_t path;
DWORD dwModuleFileName = MAX_PATH / 2;
size_t dwModuleFileName = MAX_PATH / 2;
do
{
path.resize(dwModuleFileName * 2);
dwModuleFileName = GetModuleFileNameW(hModule, (LPWSTR)path.data(), path.size());
dwModuleFileName = GetModuleFileNameW(hModule, (LPWSTR)path.data(), static_cast<DWORD>(path.size()));
} while (dwModuleFileName == path.size());
if (dwModuleFileName != 0)
{
*recv = path;
return true;
}
if (dwModuleFileName == 0)
return false;
return false;
path.resize(dwModuleFileName);
*recv = path;
return true;
}
bool GetModuleHandleFromAddress(void *addr, HMODULE *hModule)
{
BOOL res = ::GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(addr),
hModule);
return (res != FALSE);
}
pal::string_t pal::to_lower(const pal::string_t& in)
@ -145,7 +153,7 @@ pal::proc_t pal::get_symbol(dll_t library, const char* name)
auto result = ::GetProcAddress(library, name);
if (result == nullptr)
{
trace::info(_X("Probed for and did not resolve library symbol %s"), name);
trace::info(_X("Probed for and did not resolve library symbol %S"), name);
}
return result;
@ -424,6 +432,25 @@ bool pal::get_own_executable_path(string_t* recv)
return GetModuleFileNameWrapper(NULL, recv);
}
bool pal::get_current_module(dll_t *mod)
{
HMODULE hmod = nullptr;
if (!GetModuleHandleFromAddress(&get_current_module, &hmod))
return false;
*mod = (pal::dll_t)hmod;
return true;
}
bool pal::get_own_module_path(string_t* recv)
{
HMODULE hmod;
if (!GetModuleHandleFromAddress(&get_own_module_path, &hmod))
return false;
return GetModuleFileNameWrapper(hmod, recv);
}
static bool wchar_convert_helper(DWORD code_page, const char* cstr, int len, pal::string_t* out)
{
out->clear();

Просмотреть файл

@ -253,7 +253,7 @@ bool parse_known_args(
}
// Try to match 0xEF 0xBB 0xBF byte sequence (no endianness here.)
bool skip_utf8_bom(pal::ifstream_t* stream)
bool skip_utf8_bom(pal::istream_t* stream)
{
if (stream->eof() || !stream->good())
{

Просмотреть файл

@ -53,7 +53,7 @@ bool parse_known_args(
const std::vector<host_option>& known_opts,
opt_map_t* opts,
int* num_args);
bool skip_utf8_bom(pal::ifstream_t* stream);
bool skip_utf8_bom(pal::istream_t* stream);
bool get_env_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm);
bool get_global_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm);
bool multilevel_lookup_enabled();

Просмотреть файл

@ -1,26 +1,42 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include "pal.h"
#include <corehost.h>
#include "error_codes.h"
#include "fx_ver.h"
#include "pal.h"
#include "trace.h"
#include "utils.h"
#if FEATURE_APPHOST
#define CURHOST_TYPE _X("apphost")
#define CUREXE_PKG_VER APPHOST_PKG_VER
#else // !FEATURE_APPHOST
#define CURHOST_TYPE _X("dotnet")
#define CUREXE_PKG_VER HOST_PKG_VER
#endif // !FEATURE_APPHOST
// Declarations of hostfxr entry points
using hostfxr_main_fn = int(*)(const int argc, const pal::char_t* argv[]);
using hostfxr_main_startupinfo_fn = int(*)(
const int argc,
const pal::char_t* argv[],
const pal::char_t* host_path,
const pal::char_t* dotnet_root,
const pal::char_t* app_path);
using hostfxr_get_com_activation_delegate_fn = int(*)(
const pal::char_t* host_path,
const pal::char_t* dotnet_root,
const pal::char_t* app_path,
void **delegate);
bool get_latest_fxr(pal::string_t fxr_root, pal::string_t* out_fxr_path);
// Forward declaration of required custom feature APIs
typedef int(*hostfxr_main_fn) (const int argc, const pal::char_t* argv[]);
typedef int(*hostfxr_main_startupinfo_fn) (const int argc, const pal::char_t* argv[], const pal::char_t* host_path, const pal::char_t* dotnet_root, const pal::char_t* app_path);
typedef void(*hostfxr_error_writer_fn) (const pal::char_t* message);
typedef hostfxr_error_writer_fn(*hostfxr_set_error_writer_fn) (hostfxr_error_writer_fn error_writer);
// Attempt to resolve fxr and the dotnet root using host specific logic
bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path);
#if FEATURE_APPHOST
#define CURHOST_TYPE _X("apphost")
#define CUREXE_PKG_VER APPHOST_PKG_VER
#define CURHOST_EXE
/**
* Detect if the apphost executable is allowed to load and execute a managed assembly.
@ -76,15 +92,10 @@ bool is_exe_enabled_for_execution(pal::string_t* app_dll)
trace::info(_X("The managed DLL bound to this executable is: '%s'"), app_dll->c_str());
return true;
}
#endif
bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_root, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path)
bool resolve_fxr_path(const pal::string_t& app_root, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path)
{
pal::string_t host_dir;
host_dir.assign(get_directory(host_path));
#if FEATURE_APPHOST
// If a hostfxr exists in app_root, then assumed self-contained.
// If a hostfxr exists in app_root, then assume self-contained.
if (library_exists_in_dir(app_root, LIBFXR_NAME, out_fxr_path))
{
trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str());
@ -113,15 +124,10 @@ bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_r
}
pal::string_t fxr_dir = *out_dotnet_root;
#else
out_dotnet_root->assign(host_dir);
pal::string_t fxr_dir = host_dir;
#endif
append_path(&fxr_dir, _X("host"));
append_path(&fxr_dir, _X("fxr"));
if (!pal::directory_exists(fxr_dir))
{
#if FEATURE_APPHOST
if (default_install_location.empty())
{
pal::get_default_installation_dir(&default_install_location);
@ -134,16 +140,106 @@ bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_r
app_root.c_str(),
default_install_location.c_str(),
dotnet_root_env_var_name.c_str());
#else
trace::error(_X("A fatal error occurred, the folder [%s] does not exist"), fxr_dir.c_str());
#endif
return false;
}
trace::info(_X("Reading fx resolver directory=[%s]"), fxr_dir.c_str());
if (!get_latest_fxr(std::move(fxr_dir), out_fxr_path))
return false;
return true;
}
#elif FEATURE_LIBHOST
#define CURHOST_TYPE _X("libhost")
#define CUREXE_PKG_VER LIBHOST_PKG_VER
#define CURHOST_LIB
bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path)
{
// If a hostfxr exists in root_path, then assume self-contained.
if (library_exists_in_dir(root_path, LIBFXR_NAME, out_fxr_path))
{
trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str());
out_dotnet_root->assign(root_path);
return true;
}
// For framework-dependent apps, use DOTNET_ROOT
pal::string_t default_install_location;
pal::string_t dotnet_root_env_var_name = get_dotnet_root_env_var_name();
if (get_file_path_from_env(dotnet_root_env_var_name.c_str(), out_dotnet_root))
{
trace::info(_X("Using environment variable %s=[%s] as runtime location."), dotnet_root_env_var_name.c_str(), out_dotnet_root->c_str());
}
else
{
pal::string_t default_install_location;
// Check default installation root as fallback
if (!pal::get_default_installation_dir(&default_install_location))
{
trace::error(_X("A fatal error occurred, the default install location cannot be obtained."));
return false;
}
trace::info(_X("Using default installation location [%s] as runtime location."), default_install_location.c_str());
out_dotnet_root->assign(default_install_location);
}
pal::string_t fxr_dir = *out_dotnet_root;
append_path(&fxr_dir, _X("host"));
append_path(&fxr_dir, _X("fxr"));
if (!pal::directory_exists(fxr_dir))
{
trace::error(_X("A fatal error occurred, the required library %s could not be found.\n"
"If this is a self-contained application, that library should exist in [%s].\n"
"If this is a framework-dependent application, install the runtime in the default location [%s]."),
LIBFXR_NAME,
root_path.c_str(),
default_install_location.c_str());
return false;
}
if (!get_latest_fxr(std::move(fxr_dir), out_fxr_path))
return false;
return true;
}
#else // !FEATURE_APPHOST && !FEATURE_LIBHOST
#define CURHOST_TYPE _X("dotnet")
#define CUREXE_PKG_VER HOST_PKG_VER
#define CURHOST_EXE
bool resolve_fxr_path(const pal::string_t& host_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path)
{
pal::string_t host_dir;
host_dir.assign(get_directory(host_path));
out_dotnet_root->assign(host_dir);
pal::string_t fxr_dir = *out_dotnet_root;
append_path(&fxr_dir, _X("host"));
append_path(&fxr_dir, _X("fxr"));
if (!pal::directory_exists(fxr_dir))
{
trace::error(_X("A fatal error occurred, the folder [%s] does not exist"), fxr_dir.c_str());
return false;
}
if (!get_latest_fxr(std::move(fxr_dir), out_fxr_path))
return false;
return true;
}
#endif // !FEATURE_APPHOST && !FEATURE_LIBHOST
bool get_latest_fxr(pal::string_t fxr_root, pal::string_t* out_fxr_path)
{
trace::info(_X("Reading fx resolver directory=[%s]"), fxr_root.c_str());
std::vector<pal::string_t> list;
pal::readdir_onlydirectories(fxr_dir, &list);
pal::readdir_onlydirectories(fxr_root, &list);
fx_ver_t max_ver;
for (const auto& dir : list)
@ -161,32 +257,83 @@ bool resolve_fxr_path(const pal::string_t& host_path, const pal::string_t& app_r
if (max_ver == fx_ver_t())
{
trace::error(_X("A fatal error occurred, the folder [%s] does not contain any version-numbered child folders"), fxr_dir.c_str());
trace::error(_X("A fatal error occurred, the folder [%s] does not contain any version-numbered child folders"), fxr_root.c_str());
return false;
}
pal::string_t max_ver_str = max_ver.as_str();
append_path(&fxr_dir, max_ver_str.c_str());
trace::info(_X("Detected latest fxr version=[%s]..."), fxr_dir.c_str());
append_path(&fxr_root, max_ver_str.c_str());
trace::info(_X("Detected latest fxr version=[%s]..."), fxr_root.c_str());
if (library_exists_in_dir(fxr_dir, LIBFXR_NAME, out_fxr_path))
if (library_exists_in_dir(fxr_root, LIBFXR_NAME, out_fxr_path))
{
trace::info(_X("Resolved fxr [%s]..."), out_fxr_path ->c_str());
trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str());
return true;
}
trace::error(_X("A fatal error occurred, the required library %s could not be found in [%s]"), LIBFXR_NAME, fxr_dir.c_str());
trace::error(_X("A fatal error occurred, the required library %s could not be found in [%s]"), LIBFXR_NAME, fxr_root.c_str());
return false;
}
int run(const int argc, const pal::char_t* argv[])
#if defined(CURHOST_LIB)
int get_com_activation_delegate(pal::string_t *app_path, com_activation_fn *delegate)
{
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 (!resolve_fxr_path(host_path, &dotnet_root, &fxr_path))
{
return StatusCode::CoreHostLibMissingFailure;
}
// 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;
}
// Leak fxr
auto get_com_delegate = (hostfxr_get_com_activation_delegate_fn)pal::get_symbol(fxr, "hostfxr_get_com_activation_delegate");
if (get_com_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_com_delegate(host_path.c_str(), dotnet_root.c_str(), app_path->c_str(), (void**)delegate);
}
#elif defined(CURHOST_EXE)
int exe_start(const int argc, const pal::char_t* argv[])
{
pal::string_t host_path;
if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path))
{
trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str());
return StatusCode::CoreHostCurExeFindFailure;
return StatusCode::CoreHostCurHostFindFailure;
}
pal::string_t app_path;
@ -226,7 +373,7 @@ int run(const int argc, const pal::char_t* argv[])
if (pal::strcasecmp(own_name.c_str(), CURHOST_TYPE) != 0)
{
trace::error(_X("A fatal error was encountered. Cannot execute %s when renamed to %s."), CURHOST_TYPE,own_name.c_str());
trace::error(_X("A fatal error was encountered. Cannot execute %s when renamed to %s."), CURHOST_TYPE, own_name.c_str());
return StatusCode::CoreHostEntryPointFailure;
}
@ -247,15 +394,15 @@ int run(const int argc, const pal::char_t* argv[])
return StatusCode::InvalidArgFailure;
}
app_root.assign(get_directory(host_path));
app_path.assign(app_root);
app_root.assign(host_path);
app_path.assign(get_directory(app_root));
append_path(&app_path, own_name.c_str());
app_path.append(_X(".dll"));
#endif
pal::string_t dotnet_root;
pal::string_t fxr_path;
if (!resolve_fxr_path(host_path, app_root, &dotnet_root, &fxr_path))
if (!resolve_fxr_path(app_root, &dotnet_root, &fxr_path))
{
return StatusCode::CoreHostLibMissingFailure;
}
@ -377,7 +524,7 @@ int main(const int argc, const pal::char_t* argv[])
}
#endif
int exit_code = run(argc, argv);
int exit_code = exe_start(argc, argv);
// Flush traces before exit - just to be sure, and also if we're showing a popup below the error should show up in traces
// by the time the popup is displayed.
@ -400,3 +547,9 @@ int main(const int argc, const pal::char_t* argv[])
return exit_code;
}
#else
#error A host binary format must be defined
#endif

Просмотреть файл

@ -10,7 +10,7 @@ enum StatusCode
CoreHostLibLoadFailure = 0x80008082,
CoreHostLibMissingFailure = 0x80008083,
CoreHostEntryPointFailure = 0x80008084,
CoreHostCurExeFindFailure = 0x80008085,
CoreHostCurHostFindFailure = 0x80008085,
CoreHostResolveModeFailure = 0x80008086,
CoreClrResolveFailure = 0x80008087,
CoreClrBindFailure = 0x80008088,

Просмотреть файл

@ -9,13 +9,14 @@ namespace HostApiInvokerApp
{
internal static class hostfxr
{
[DllImport(nameof(hostfxr), CharSet = Utils.OSCharSet)]
[DllImport(nameof(hostfxr), CharSet = Utils.OSCharSet, CallingConvention = CallingConvention.Cdecl)]
internal static extern uint hostfxr_get_native_search_directories(
int argc,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]
string[] argv,
StringBuilder buffer,
int bufferSize,
string[] argv,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]
char[] buffer,
int bufferSize,
ref int required_buffer_size);
[Flags]
@ -83,8 +84,8 @@ namespace HostApiInvokerApp
string[] argv = new[] { pathToDotnet, pathToApp };
// Start with 0 bytes allocated to test re-entry and required_buffer_size
StringBuilder buffer = new StringBuilder(0);
int required_buffer_size = 0;
char[] buffer = new char[required_buffer_size];
uint rc = 0;
StringBuilder errorBuilder = new StringBuilder();
@ -95,15 +96,11 @@ namespace HostApiInvokerApp
});
try
{
for (int i = 0; i < 2; i++)
rc = hostfxr.hostfxr_get_native_search_directories(argv.Length, argv, buffer, buffer.Length, ref required_buffer_size);
if (rc == hostfxr.HostApiBufferTooSmall)
{
rc = hostfxr.hostfxr_get_native_search_directories(argv.Length, argv, buffer, buffer.Capacity + 1, ref required_buffer_size);
if (rc != hostfxr.HostApiBufferTooSmall)
{
break;
}
buffer = new StringBuilder(required_buffer_size);
buffer = new char[required_buffer_size];
rc = hostfxr.hostfxr_get_native_search_directories(argv.Length, argv, buffer, buffer.Length, ref required_buffer_size);
}
}
finally
@ -114,7 +111,7 @@ namespace HostApiInvokerApp
if (rc == 0)
{
Console.WriteLine("hostfxr_get_native_search_directories:Success");
Console.WriteLine($"hostfxr_get_native_search_directories buffer:[{buffer}]");
Console.WriteLine($"hostfxr_get_native_search_directories buffer:[{new string(buffer)}]");
}
else
{
@ -151,7 +148,7 @@ namespace HostApiInvokerApp
}
Console.WriteLine("negative buffer size.");
StringBuilder buffer = new StringBuilder(100);
char[] buffer = new char[100];
rc = hostfxr.hostfxr_get_native_search_directories(0, null, buffer, -1, ref required_buffer_size);
Console.WriteLine($"hostfxr_get_native_search_directories error code: {rc}");
if (rc != hostfxr.InvalidArgFailure)

Просмотреть файл

@ -12,6 +12,11 @@ namespace Microsoft.DotNet.Cli.Build.Framework
Reporter.Output.WriteLine($"[{type.PadRight(10)} >]".Green() + $" [....] [{(DateTime.Now - _initialTime).ToString(TimeSpanFormat)}]".Blue() + $" {name}");
}
public static void SectionComment(string type, string comment)
{
Reporter.Output.WriteLine($"[{type.PadRight(10)} -]".Green() + $" [....] [{(DateTime.Now - _initialTime).ToString(TimeSpanFormat)}]".Blue() + $" {comment}");
}
public static void EndSection(string type, string name, bool success)
{
var header = $"[{type.PadRight(10)} <]";

Просмотреть файл

@ -210,6 +210,8 @@ namespace Microsoft.DotNet.Cli.Build.Framework
public CommandResult WaitForExit(bool fExpectedToFail)
{
ReportExecWaitOnExit();
_process.WaitForExit();
var exitCode = _process.ExitCode;
@ -361,6 +363,14 @@ namespace Microsoft.DotNet.Cli.Build.Framework
}
}
private void ReportExecWaitOnExit()
{
if (!_quietBuildReporter)
{
BuildReporter.SectionComment("EXEC", $"Waiting for process {_process.Id} to exit...");
}
}
private void ReportExecEnd(int exitCode, bool fExpectedToFail)
{
if (!_quietBuildReporter)

Просмотреть файл

@ -85,6 +85,7 @@
</PropertyGroup>
<PropertyGroup>
<TestArgs>--no-restore $(MSBuildPassThroughPropertyList)</TestArgs>
<IsCrossArch>false</IsCrossArch>
<IsCrossArch Condition="'$(TargetArchitecture)' == 'arm' or '$(TargetArchitecture)' == 'arm64' or '$(TargetArchitecture)' == 'armel'">true</IsCrossArch>
<TestResultsXml>$(SystemPathTestsOutputDir)$(TestProjectFilename)-testResults.trx</TestResultsXml>
</PropertyGroup>
@ -102,9 +103,10 @@
<TestResults Include="$(TestProject)">
<ResultsXml>$(TestResultsXml)</ResultsXml>
<ExitCode>$(_ErrorCode)</ExitCode>
<ExitCode Condition="'$(_ErrorCode)' == ''">0</ExitCode>
</TestResults>
</ItemGroup>
<Message Importance="High" Condition="'$(_ErrorCode)' != '0'" Text="Test run failed with exit code '$(_ErrorCode)', see $(TestResultsXml)" />
<Message Importance="High" Condition="'$(_ErrorCode)' != '0' and '$(_ErrorCode)' != ''" Text="Test run failed with exit code '$(_ErrorCode)', see $(TestResultsXml)" />
</Target>
<Target Name="RunTests"