Add host context-based entry points for native hosting (dotnet/core-setup#5859)
authorElinor Fung <47805090+elinor-fung@users.noreply.github.com>
Thu, 2 May 2019 18:36:50 +0000 (11:36 -0700)
committerGitHub <noreply@github.com>
Thu, 2 May 2019 18:36:50 +0000 (11:36 -0700)
- Track existing hostpolicy_context in hostpolicy
- Add host_context to hostfxr to represent active (first) and secondary contexts
- Switch com/ijw/winrt hosts to use host context-based APIs
- Update nativehost test executable to exercise new APIs
- Make non-context-based entry points check for existing context and create an empty context
- Basic automated tests for context-based entry points

Commit migrated from https://github.com/dotnet/core-setup/commit/4f7fefe81748bcf7fe65ad3492ed2ca22a2b834a

39 files changed:
docs/installer/design-docs/COM-activation.md
docs/installer/design-docs/hosting-layer-apis.md
src/installer/corehost/cli/comhost/comhost.cpp
src/installer/corehost/cli/corehost_context_contract.h [new file with mode: 0644]
src/installer/corehost/cli/fxr/CMakeLists.txt
src/installer/corehost/cli/fxr/corehost_init.cpp
src/installer/corehost/cli/fxr/fx_muxer.cpp
src/installer/corehost/cli/fxr/fx_muxer.h
src/installer/corehost/cli/fxr/host_context.cpp [new file with mode: 0644]
src/installer/corehost/cli/fxr/host_context.h [new file with mode: 0644]
src/installer/corehost/cli/fxr/hostfxr.cpp
src/installer/corehost/cli/fxr/hostpolicy_resolver.cpp
src/installer/corehost/cli/fxr/hostpolicy_resolver.h
src/installer/corehost/cli/fxr_resolver.cpp
src/installer/corehost/cli/fxr_resolver.h
src/installer/corehost/cli/hostfxr.h
src/installer/corehost/cli/hostpolicy/CMakeLists.txt
src/installer/corehost/cli/hostpolicy/args.cpp
src/installer/corehost/cli/hostpolicy/coreclr.cpp
src/installer/corehost/cli/hostpolicy/coreclr.h
src/installer/corehost/cli/hostpolicy/hostpolicy.cpp
src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp
src/installer/corehost/cli/hostpolicy/hostpolicy_context.h
src/installer/corehost/cli/ijwhost/ijwhost.cpp
src/installer/corehost/cli/test/mockcoreclr/mockcoreclr.cpp
src/installer/corehost/cli/test/nativehost/CMakeLists.txt
src/installer/corehost/cli/test/nativehost/host_context_test.cpp [new file with mode: 0644]
src/installer/corehost/cli/test/nativehost/host_context_test.h [new file with mode: 0644]
src/installer/corehost/cli/test/nativehost/nativehost.cpp
src/installer/corehost/cli/winrthost/winrthost.cpp
src/installer/corehost/common/pal.unix.cpp
src/installer/corehost/common/utils.cpp
src/installer/corehost/common/utils.h
src/installer/corehost/error_codes.h
src/installer/test/HostActivationTests/NativeHosting/HostContext.cs [new file with mode: 0644]
src/installer/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs [new file with mode: 0644]
src/installer/test/HostActivationTests/NativeHosting/Nethost.cs
src/installer/test/HostActivationTests/NativeHosting/SharedTestStateBase.cs
src/installer/test/TestUtils/Assertions/CommandResultAssertions.cs

index a08fc19..a0c71f6 100644 (file)
@@ -68,9 +68,9 @@ When `DllGetClassObject()` is called in a COM activation scenario, the following
     * 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 shim attempts to load the latest version of the `hostfxr` library and retrieves the `hostfxr_initialize_for_runtime_config()` and `hostfxr_get_runtime_delegate()` exports.
 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) The `hostfxr_initialize_for_runtime_config()` 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.
@@ -78,8 +78,8 @@ When `DllGetClassObject()` is called in a COM activation scenario, the following
     * **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.
+1) The `hostfxr_get_runtime_delegate()` export is called
+1) The `hostfxr_get_runtime_delegate()` export calls into `hostpolicy` and 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.
index c274d4e..5ef43ab 100644 (file)
@@ -20,7 +20,9 @@ int hostfxr_main(const int argc, const char_t *argv[])
 Run an application.
 * `argc` / `argv` - command-line arguments
 
-This function does not return until the application completes execution.
+This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes.
+
+If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure.
 
 ### .NET Core 2.0+
 
@@ -51,7 +53,9 @@ Run an application.
 * `dotnet_root` - path to the .NET Core installation root
 * `app_path` - path to the application to run
 
-This function does not return until the application completes execution.
+This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes.
+
+If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure.
 
 ``` C
 enum hostfxr_resolve_sdk2_flags_t
@@ -138,6 +142,112 @@ The error writer is registered per-thread. On each thread, only one callback can
 If `hostfxr` invokes functions in `hostpolicy` as part of its operation, the error writer will be propagated to `hostpolicy` for the duration of the call. This means that errors from both `hostfxr` and `hostpolicy` will be reported through the same error writer.
 
 
+``` C
+int hostfxr_initialize_for_app(
+    int argc,
+    const char_t *argv[],
+    const char_t *app_path,
+    const hostfxr_initialize_parameters *parameters,
+    hostfxr_handle * host_context_handle
+);
+```
+Initialize the hosting components for running a managed application.
+* `argc` / `argv` - command-line arguments
+* `app_path` - path to the application to run
+* `parameters` - optional additional parameters
+* `host_context_handle` - if initialization is successful, this receives an opaque value which identifies the initialized host context.
+
+See [Native hosting](native-hosting.md#initialize-host-context)
+
+``` C
+int hostfxr_initialize_for_runtime_config(
+    const char_t *runtime_config_path,
+    const hostfxr_initialize_parameters *parameters,
+    hostfxr_handle *host_context_handle
+);
+```
+Initialize the  hosting components for a runtime configuration (`.runtimeconfig.json`).
+* `runtime_config_path` - path to the `.runtimeconfig.json` file to process
+* `parameters` - optional additional parameters
+* `host_context_handle` - if initialization is successful, this receives an opaque value which identifies the initialized host context.
+
+See [Native hosting](native-hosting.md#initialize-host-context)
+
+``` C
+int hostfxr_get_runtime_property_value(
+    const hostfxr_handle host_context_handle,
+    const char_t *name,
+    const char_t **value);
+```
+
+Get the value of a runtime property specified by its name.
+* `host_context_handle` - initialized host context. If set to `nullptr` the function will operate on the first host context in the process.
+* `name` - name of the runtime property to get
+* `value` - returns a pointer to a buffer with the property value
+
+See [Native hosting](native-hosting.md#runtime-properties)
+
+``` C
+int hostfxr_set_runtime_property_value(
+    const hostfxr_handle host_context_handle,
+    const char_t *name,
+    const char_t *value);
+```
+
+Set the value of a property.
+* `host_context_handle` - initialized host context
+* `name` - name of the runtime property to set
+* `value` - value of the property to set. If the property already has a value in the host context, this function will overwrite it. When set to `nullptr` and if the property already has a value then the property is removed.
+
+See [Native hosting](native-hosting.md#runtime-properties)
+
+``` C
+int hostfxr_get_runtime_properties(
+    const hostfxr_handle host_context_handle,
+    size_t * count,
+    const char_t **keys,
+    const char_t **values);
+```
+Get all runtime properties for the specified host context.
+* `host_context_handle` - initialized host context. If set to `nullptr` the function will operate on the first host context in the process.
+* `count` - in/out parameter which must not be `nullptr`. On input it specifies the size of the the `keys` and `values` buffers. On output it contains the number of entries used from `keys` and `values` buffers - the number of properties returned.
+* `keys` - buffer which acts as an array of pointers to buffers with keys for the runtime properties.
+* `values` - buffer which acts as an array of pointer to buffers with values for the runtime properties.
+
+If `count` is less than the minimum required buffer size or `keys` or `values` is `nullptr`, this function will return `HostApiBufferTooSmall` and `keys` and `values` will be unchanged.
+
+See [Native hosting](native-hosting.md#runtime-properties)
+
+``` C
+int hostfxr_run_app(const hostfxr_handle host_context_handle);
+```
+Run the application specified by `hostfxr_initialize_for_app`.
+* `host_context_handle` - handle to the initialized host context.
+
+This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes.
+
+If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure.
+
+See [Native hosting](native-hosting.md#runtime-properties)
+
+``` C
+int hostfxr_get_runtime_delegate(const hostfxr_handle host_context_handle, hostfxr_delegate_type type, void ** delegate);
+```
+Start the runtime and get a function pointer to specified functionality of the runtime.
+* `host_context_handle` - initialized host context
+* `type` - type of runtime functionality requested
+* `delegate` - on success, this is populated with the native function pointer to the requested runtime functionality
+
+See [Native hosting](native-hosting.md#getting-a-delegate-for-runtime-functionality)
+
+``` C
+int hostfxr_close(const hostfxr_handle host_context_handle);
+```
+Close a host context.
+* `host_context_handle` - initialized host context to close.
+
+See [Native hosting](native-hosting.md#cleanup)
+
 ## Host Policy
 
 All exported functions and function pointers in the `hostpolicy` library use the `__cdecl` calling convention on the x86 platform.
@@ -162,6 +272,8 @@ Run an application.
 
 This function does not return until the application completes execution. It will shutdown CoreCLR after the application executes.
 
+If the application is successfully executed, this value will return the exit code of the application. Otherwise, it will return an error code indicating the failure.
+
 ``` C
 int corehost_unload()
 ```
@@ -189,14 +301,6 @@ If `buffer_size` is less than the minimum required buffer size, this function wi
 ### .NET Core 3.0+
 
 ``` C
-int corehost_get_coreclr_delegate(coreclr_delegate_type type, void **delegate)
-```
-
-Get a delegate for CoreCLR functionality
-* `type` - requested type of runtime functionality
-* `delegate` - function pointer to the requested runtime functionality
-
-``` C
 typedef void(*corehost_resolve_component_dependencies_result_fn)(
     const char_t *assembly_paths,
     const char_t *native_search_paths,
@@ -226,14 +330,6 @@ The return value is the previouly registered callback (which is now unregistered
 
 The error writer is registered per-thread. On each thread, only one callback can be registered. Subsequent registrations overwrite the previous ones.
 
-### [Proposed] .NET Core 3.0+
-
-#### Removal
-
-`corehost_get_coreclr_delegate` will be removed and the equivalent functionality provided through the proposed additions below. Since this function is new in 3.0, it should not be required for backwards compatibility.
-
-#### Addition
-
 ``` C
 typedef void* context_handle;
 
@@ -242,18 +338,16 @@ struct corehost_context_contract
     size_t version;
     context_handle instance;
     int (*get_property_value)(
-        context_handle instance,
         const char_t *key,
         const char_t **value);
     int (*set_property_value)(
-        context_handle instance,
         const char_t *key,
         const char_t *value);
     int (*get_properties)(
-        context_handle instance,
         size_t *count,
         const char_t **keys,
         const char_t **values);
+    int (*load_runtime)();
     int (*run_app)(
         const context_handle instance,
         const int argc,
@@ -265,7 +359,7 @@ struct corehost_context_contract
 };
 ```
 
-Contract for performing operations on an initialized host context.
+Contract for performing operations on an initialized hostpolicy.
 * `version` - version of the struct.
 * `instance` - opaque handle to the `corehost_context_contract` state.
 * `get_property_value` - function pointer for getting a property on the host context.
@@ -278,6 +372,7 @@ Contract for performing operations on an initialized host context.
   * `count` - size of `keys` and `values`. If the size is too small, it will be populated with the required size. Otherwise, it will be populated with the size used.
   * `keys` - buffer to populate with the property keys.
   * `values` - buffer to populate with the property values.
+* `load_runtime` - function pointer for loading CoreCLR
 * `run_app` - function pointer for running an application.
   * `argc` / `argv` - command-line arguments.
 * `get_runtime_delegate` - function pointer for getting a delegate for CoreCLR functionality
@@ -285,16 +380,17 @@ Contract for performing operations on an initialized host context.
   * `delegate` - function pointer to the requested runtime functionality
 
 ``` C
-int corehost_initialize_context(const host_interface_t *init, corehost_context_contract *context_contract)
+enum intialization_options_t
+{
+    none = 0x0,
+    wait_for_initialized = 0x1,
+};
+
+int corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, corehost_context_contract *context_contract)
 ```
 
 Initializes the host context. This calculates everything required to start CoreCLR (but does not actually do so).
-* `init` - struct defining how the host context should be initialized. If the host context is already initialized, this function will check if `init` is compatible with the active context.
+* `init_request` - struct containing information about the initialization request. If hostpolicy is not yet initialized, this is expected to be nullptr. If hostpolicy is already initialized, this should not be nullptr and this function will use the struct to check for compatibility with the way in which hostpolicy was previously initialized.
+* `options` - initialization options
+  * `wait_for_initialized` - wait until initialization through a different request is completed
 * `context_contract` - if initialization is successful, this is populated with the contract for operating on the initialized host context.
-
-``` C
-int corehost_close_context(corehost_context_contract *context_contract)
-```
-
-Closes the host context.
-* `context_contract` - contract of the context to close.
index 171a4c8..63c56c9 100644 (file)
@@ -50,21 +50,23 @@ namespace
     {
         return load_fxr_and_get_delegate(
             hostfxr_delegate_type::com_activation,
-            [](const pal::string_t& host_path, pal::string_t* app_path_out)
+            [app_path](const pal::string_t& host_path, pal::string_t* config_path_out)
             {
-                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"));
+                // Strip the comhost suffix to get the 'app' and config
+                size_t idx = host_path.rfind(_X(".comhost.dll"));
                 assert(idx != pal::string_t::npos);
+
+                pal::string_t app_path_local{ host_path };
                 app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".dll"));
+                *app_path = std::move(app_path_local);
 
-                *app_path_out = std::move(app_path_local);
+                pal::string_t config_path_local { host_path };
+                config_path_local.replace(config_path_local.begin() + idx, config_path_local.end(), _X(".runtimeconfig.json"));
+                *config_path_out = std::move(config_path_local);
 
                 return StatusCode::Success;
             },
-            delegate,
-            app_path
+            delegate
         );
     }
     
diff --git a/src/installer/corehost/cli/corehost_context_contract.h b/src/installer/corehost/cli/corehost_context_contract.h
new file mode 100644 (file)
index 0000000..789c290
--- /dev/null
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef __COREHOST_CONTEXT_CONTRACT_H__
+#define __COREHOST_CONTEXT_CONTRACT_H__
+
+#include "host_interface.h"
+#include <pal.h>
+
+enum intialization_options_t : int32_t
+{
+    none = 0x0,
+    wait_for_initialized = 0x1,  // Wait until initialization through a different request is completed
+};
+
+enum class coreclr_delegate_type
+{
+    invalid,
+    com_activation,
+    load_in_memory_assembly,
+    winrt_activation
+};
+
+#pragma pack(push, _HOST_INTERFACE_PACK)
+struct corehost_initialize_request_t
+{
+    size_t version;
+    strarr_t config_keys;
+    strarr_t config_values;
+};
+static_assert(offsetof(corehost_initialize_request_t, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_initialize_request_t, config_keys) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_initialize_request_t, config_values) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+
+struct corehost_context_contract
+{
+    size_t version;
+    int (__cdecl *get_property_value)(
+        const pal::char_t* key,
+        /*out*/ const pal::char_t** value);
+    int (__cdecl *set_property_value)(
+        const pal::char_t* key,
+        const pal::char_t* value);
+    int (__cdecl *get_properties)(
+        /*inout*/ size_t *count,
+        /*out*/ const pal::char_t** keys,
+        /*out*/ const pal::char_t** values);
+    int (__cdecl *load_runtime)();
+    int (__cdecl *run_app)(
+        const int argc,
+        const pal::char_t* argv[]);
+    int (__cdecl *get_runtime_delegate)(
+        coreclr_delegate_type type,
+        /*out*/ void** delegate);
+};
+static_assert(offsetof(corehost_context_contract, version) == 0 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, get_property_value) == 1 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, set_property_value) == 2 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, get_properties) == 3 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, load_runtime) == 4 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, run_app) == 5 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+static_assert(offsetof(corehost_context_contract, get_runtime_delegate) == 6 * sizeof(size_t), "Struct offset breaks backwards compatibility");
+#pragma pack(pop)
+
+#endif // __COREHOST_CONTEXT_CONTRACT_H__
\ No newline at end of file
index f01169a..077c502 100644 (file)
@@ -31,12 +31,14 @@ set(SOURCES
     ./fx_resolver.cpp
     ./fx_resolver.messages.cpp
     ./framework_info.cpp
+    ./host_context.cpp
     ./hostpolicy_resolver.cpp
     ./sdk_info.cpp
     ./sdk_resolver.cpp
 )
 
 set(HEADERS
+    ../corehost_context_contract.h
     ../deps_format.h
     ../deps_entry.h
     ../host_startup_info.h
@@ -52,6 +54,7 @@ set(HEADERS
     ./fx_muxer.h
     ./fx_resolver.h
     ./framework_info.h
+    ./host_context.h
     ./hostpolicy_resolver.h
     ./sdk_info.h
     ./sdk_resolver.h
index 4a26793..077f01f 100644 (file)
@@ -128,4 +128,4 @@ const host_interface_t& corehost_init_t::get_host_init_data()
     hi.host_info_app_path = m_host_info_app_path.c_str();
 
     return hi;
-}
+}
\ No newline at end of file
index b9f197a..2428388 100644 (file)
@@ -2,7 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+#include <atomic>
 #include <cassert>
+#include <condition_variable>
 #include <mutex>
 #include <error_codes.h>
 #include <pal.h>
@@ -10,6 +12,7 @@
 #include <utils.h>
 
 #include <cpprest/json.h>
+#include <corehost_context_contract.h>
 #include "corehost_init.h"
 #include "deps_format.h"
 #include "framework_info.h"
 #include "roll_fwd_on_no_candidate_fx_option.h"
 
 using corehost_main_fn = int(*) (const int argc, const pal::char_t* argv[]);
-using corehost_get_delegate_fn = int(*)(coreclr_delegate_type type, 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);
 
+namespace
+{
+    // hostfxr tracks the context used to load hostpolicy and coreclr as the active host context. This is the first
+    // context that is successfully created and used to load the runtime. There can only be one active context.
+    // Secondary contexts can only be created once the first context has fully initialized and been used to load the
+    // runtime. Any calls to initialize another context while the runtime has not yet been loaded by the first context
+    // will block until the first context loads the runtime (or fails).
+    std::mutex g_context_lock;
+
+    // Tracks the active host context. This is the context that was used to load and initialize hostpolicy and coreclr.
+    // It will only be set once both hostpolicy and coreclr are loaded and initialized. Once set, it should not be changed.
+    // This will remain set even if the context is closed through hostfxr_close. Since the context represents the active
+    // CoreCLR runtime and the active runtime cannot be unloaded, the active context is never unset.
+    std::unique_ptr<const host_context_t> g_active_host_context;
+
+    // Tracks whether the first host context is initializing (from creation of the first context to loading the runtime).
+    // Initialization of other contexts should block if the first context is initializing (i.e. this is true).
+    // The condition variable is used to block on and signal changes to this state.
+    std::atomic<bool> g_context_initializing(false);
+    std::condition_variable g_context_initializing_cv;
+
+    void handle_initialize_failure_or_abort(const hostpolicy_contract_t *hostpolicy_contract = nullptr)
+    {
+        {
+            std::lock_guard<std::mutex> lock{ g_context_lock };
+            assert(g_context_initializing.load());
+            assert(g_active_host_context == nullptr);
+            g_context_initializing.store(false);
+        }
+
+        if (hostpolicy_contract != nullptr && hostpolicy_contract->unload != nullptr)
+            hostpolicy_contract->unload();
+
+        g_context_initializing_cv.notify_all();
+    }
+}
+
 template<typename T>
 int load_hostpolicy(
     const pal::string_t& lib_dir,
     pal::dll_t* h_host,
-    hostpolicy_contract &host_contract,
+    hostpolicy_contract_t &hostpolicy_contract,
     const char *main_entry_symbol,
     T* main_fn)
 {
     assert(main_entry_symbol != nullptr && main_fn != nullptr);
 
-    int rc = hostpolicy_resolver::load(lib_dir, h_host, host_contract);
+    int rc = hostpolicy_resolver::load(lib_dir, h_host, hostpolicy_contract);
     if (rc != StatusCode::Success)
     {
         trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, lib_dir.c_str());
@@ -60,22 +99,53 @@ static int execute_app(
     const int argc,
     const pal::char_t* argv[])
 {
-    pal::dll_t corehost;
-    hostpolicy_contract host_contract{};
+    {
+        std::unique_lock<std::mutex> lock{ g_context_lock };
+        g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); });
+
+        if (g_active_host_context != nullptr)
+        {
+            trace::error(_X("Hosting components are already initialized. Re-initialization to execute an app is not allowed."));
+            return StatusCode::HostInvalidState;
+        }
+
+        g_context_initializing.store(true);
+    }
+
+    pal::dll_t hostpolicy_dll;
+    hostpolicy_contract_t hostpolicy_contract{};
     corehost_main_fn host_main = nullptr;
 
-    int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_main", &host_main);
+    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main", &host_main);
     if (code != StatusCode::Success)
+    {
+        handle_initialize_failure_or_abort();
         return code;
+    }
+
+    // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy
+
+    {
+        // Track an empty 'active' context so that host context-based APIs can work properly when
+        // the runtime is loaded through non-host context-based APIs. Once set, the context is never
+        // unset. This means that if any error occurs after this point (e.g. with loading the runtime),
+        // the process will be in a corrupted state and loading the runtime again will not be allowed.
+        std::lock_guard<std::mutex> lock{ g_context_lock };
+        assert(g_active_host_context == nullptr);
+        g_active_host_context.reset(new host_context_t(host_context_type::empty, hostpolicy_contract, {}));
+        g_context_initializing.store(false);
+    }
+
+    g_context_initializing_cv.notify_all();
 
     {
-        propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
+        propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer);
 
         const host_interface_t& intf = init->get_host_init_data();
-        if ((code = host_contract.load(&intf)) == StatusCode::Success)
+        if ((code = hostpolicy_contract.load(&intf)) == StatusCode::Success)
         {
             code = host_main(argc, argv);
-            (void)host_contract.unload();
+            (void)hostpolicy_contract.unload();
         }
     }
 
@@ -91,22 +161,24 @@ static int execute_host_command(
     int32_t buffer_size,
     int32_t* required_buffer_size)
 {
-    pal::dll_t corehost;
-    hostpolicy_contract host_contract{};
+    pal::dll_t hostpolicy_dll;
+    hostpolicy_contract_t hostpolicy_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);
+    int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract, "corehost_main_with_output_buffer", &host_main);
     if (code != StatusCode::Success)
         return code;
 
+    // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy
+
     {
-        propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
+        propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer);
 
         const host_interface_t& intf = init->get_host_init_data();
-        if ((code = host_contract.load(&intf)) == StatusCode::Success)
+        if ((code = hostpolicy_contract.load(&intf)) == StatusCode::Success)
         {
             code = host_main(argc, argv, result_buffer, buffer_size, required_buffer_size);
-            (void)host_contract.unload();
+            (void)hostpolicy_contract.unload();
         }
     }
 
@@ -458,8 +530,8 @@ namespace
         const pal::string_t &app_candidate,
         const opt_map_t &opts,
         host_mode_t mode,
-        pal::string_t &impl_dir,
-        std::unique_ptr<corehost_init_t> &init)
+        /*out*/ pal::string_t &hostpolicy_dir,
+        /*out*/ std::unique_ptr<corehost_init_t> &init)
     {
         pal::string_t opts_fx_version = _X("--fx-version");
         pal::string_t opts_roll_forward = _X("--roll-forward");
@@ -480,7 +552,7 @@ namespace
 
         runtime_config_t::settings_t override_settings;
 
-        // `Roll forward` is set to Minor (2) (roll_forward_option::Minor) by default. 
+        // `Roll forward` is set to Minor (2) (roll_forward_option::Minor) by default.
         // For backward compatibility there are two settings:
         //  - rollForward (the new one) which has more possible values
         //  - rollForwardOnNoCandidateFx (the old one) with only 0-Off, 1-Minor, 2-Major
@@ -572,7 +644,7 @@ namespace
         trace::verbose(_X("Executing as a %s app as per config file [%s]"),
             (is_framework_dependent ? _X("framework-dependent") : _X("self-contained")), app_config.get_path().c_str());
 
-        if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &impl_dir))
+        if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, app_candidate, deps_file, probe_realpaths, &hostpolicy_dir))
         {
             return CoreHostLibMissingFailure;
         }
@@ -595,7 +667,7 @@ int fx_muxer_t::read_config_and_execute(
     int32_t buffer_size,
     int32_t* required_buffer_size)
 {
-    pal::string_t impl_dir;
+    pal::string_t hostpolicy_dir;
     std::unique_ptr<corehost_init_t> init;
     int rc = get_init_info_for_app(
         host_command,
@@ -603,18 +675,18 @@ int fx_muxer_t::read_config_and_execute(
         app_candidate,
         opts,
         mode,
-        impl_dir,
+        hostpolicy_dir,
         init);
     if (rc != StatusCode::Success)
         return rc;
 
     if (host_command.size() == 0)
     {
-        rc = execute_app(impl_dir, init.get(), new_argc, new_argv);
+        rc = execute_app(hostpolicy_dir, init.get(), new_argc, new_argv);
     }
     else
     {
-        rc = execute_host_command(impl_dir, init.get(), new_argc, new_argv, out_buffer, buffer_size, required_buffer_size);
+        rc = execute_host_command(hostpolicy_dir, init.get(), new_argc, new_argv, out_buffer, buffer_size, required_buffer_size);
     }
 
     return rc;
@@ -702,102 +774,322 @@ int fx_muxer_t::execute(
 
 namespace
 {
-    int get_delegate_from_runtime(
-        const pal::string_t& impl_dll_dir,
-        corehost_init_t* init,
-        coreclr_delegate_type type,
-        void** delegate)
+    int get_init_info_for_component(
+        const host_startup_info_t &host_info,
+        host_mode_t mode,
+        pal::string_t &runtime_config_path,
+        /*out*/ pal::string_t &hostpolicy_dir,
+        /*out*/ std::unique_ptr<corehost_init_t> &init)
     {
-        pal::dll_t corehost;
-        hostpolicy_contract host_contract{};
-        corehost_get_delegate_fn coreclr_delegate = nullptr;
+        // 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));
+
+        const runtime_config_t::settings_t override_settings;
+        int rc = read_config(*app, host_info.app_path, runtime_config_path, override_settings);
+        if (rc != StatusCode::Success)
+            return rc;
 
-        int code = load_hostpolicy(impl_dll_dir, &corehost, host_contract, "corehost_get_coreclr_delegate", &coreclr_delegate);
-        if (code != StatusCode::Success)
+        const runtime_config_t app_config = app->get_runtime_config();
+        if (!app_config.get_is_framework_dependent())
         {
-            trace::error(_X("This component must target .NET Core 3.0 or a higher version."));
-            return code;
+            trace::error(_X("Initialization for self-contained components is not supported"));
+            return StatusCode::InvalidConfigFile;
         }
 
-        {
-            propagate_error_writer_t propagate_error_writer_to_corehost(host_contract.set_error_writer);
+        rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions);
+        if (rc != StatusCode::Success)
+            return rc;
 
-            const host_interface_t& intf = init->get_host_init_data();
+        const std::vector<pal::string_t> probe_realpaths = get_probe_realpaths(fx_definitions, std::vector<pal::string_t>() /* specified_probing_paths */);
 
-            if ((code = host_contract.load(&intf)) == StatusCode::Success)
-            {
-                code = coreclr_delegate(type, delegate);
-            }
+        trace::verbose(_X("Libhost loading occurring for a framework-dependent component per config file [%s]"), app_config.get_path().c_str());
+
+        const pal::string_t deps_file;
+        if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &hostpolicy_dir))
+        {
+            return StatusCode::CoreHostLibMissingFailure;
         }
 
-        return code;
+        const pal::string_t additional_deps_serialized;
+        init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions));
+
+        return StatusCode::Success;
     }
 
-    int get_init_info_for_component(
+    int get_init_info_for_secondary_component(
         const host_startup_info_t &host_info,
         host_mode_t mode,
         pal::string_t &runtime_config_path,
-        pal::string_t &impl_dir,
-        std::unique_ptr<corehost_init_t> &init)
+        const host_context_t *existing_context,
+        /*out*/ std::unordered_map<pal::string_t, pal::string_t> &config_properties)
     {
         // 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));
-
-        runtime_config_t::settings_t override_settings;
-        int rc = read_config(*app, host_info.app_path, runtime_config_path, override_settings);
+        fx_definition_t app;
+        const runtime_config_t::settings_t override_settings;
+        int rc = read_config(app, host_info.app_path, runtime_config_path, 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)
+        const runtime_config_t app_config = app.get_runtime_config();
+        if (!app_config.get_is_framework_dependent())
         {
-            rc = fx_resolver_t::resolve_frameworks_for_app(host_info, override_settings, app_config, fx_definitions);
-            if (rc != StatusCode::Success)
-            {
-                return rc;
-            }
+            trace::error(_X("Initialization for self-contained components is not supported"));
+            return StatusCode::InvalidConfigFile;
         }
 
-        std::vector<pal::string_t> probe_realpaths = get_probe_realpaths(fx_definitions, std::vector<pal::string_t>() /* specified_probing_paths */);
+        // [TODO] Validate the current context is acceptable for this request (frameworks)
 
-        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());
+        app_config.combine_properties(config_properties);
+        return StatusCode::Success;
+    }
 
-        pal::string_t deps_file;
-        if (!hostpolicy_resolver::try_get_dir(mode, host_info.dotnet_root, fx_definitions, host_info.app_path, deps_file, probe_realpaths, &impl_dir))
+    int initialize_context(
+        const pal::string_t hostpolicy_dir,
+        corehost_init_t &init,
+        int32_t initialize_options,
+        /*out*/ std::unique_ptr<host_context_t> &context)
+    {
+        pal::dll_t hostpolicy_dll;
+        hostpolicy_contract_t hostpolicy_contract{};
+        int rc = hostpolicy_resolver::load(hostpolicy_dir, &hostpolicy_dll, hostpolicy_contract);
+        if (rc != StatusCode::Success)
         {
-            return StatusCode::CoreHostLibMissingFailure;
+            trace::error(_X("An error occurred while loading required library %s from [%s]"), LIBHOSTPOLICY_NAME, hostpolicy_dir.c_str());
+        }
+        else
+        {
+            rc = host_context_t::create(hostpolicy_contract, init, initialize_options, context);
         }
 
-        pal::string_t additional_deps_serialized;
-        init.reset(new corehost_init_t(pal::string_t{}, host_info, deps_file, additional_deps_serialized, probe_realpaths, mode, fx_definitions));
+        // Leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy
 
-        return StatusCode::Success;
+        if (rc != StatusCode::Success)
+            handle_initialize_failure_or_abort(&hostpolicy_contract);
+
+        return rc;
     }
 }
 
-int fx_muxer_t::load_runtime_and_get_delegate(
-    const host_startup_info_thost_info,
-    host_mode_t mode,
-    coreclr_delegate_type delegate_type,
-    void** delegate)
+int fx_muxer_t::initialize_for_app(
+    const host_startup_info_t &host_info,
+    int argc,
+    const pal::char_t* argv[],
+    hostfxr_handle *host_context_handle)
 {
-    assert(host_info.is_valid(mode));
+    {
+        std::unique_lock<std::mutex> lock{ g_context_lock };
+        g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); });
+
+        if (g_active_host_context != nullptr)
+        {
+            trace::error(_X("Hosting components are already initialized. Re-initialization for an app is not allowed."));
+            return StatusCode::HostInvalidState;
+        }
+
+        g_context_initializing.store(true);
+    }
 
-    pal::string_t runtime_config = _X("");
-    pal::string_t impl_dir;
+    host_mode_t mode = host_mode_t::apphost;
+    opt_map_t opts;
+    pal::string_t hostpolicy_dir;
     std::unique_ptr<corehost_init_t> init;
-    int rc = get_init_info_for_component(host_info, mode, runtime_config, impl_dir, init);
+    int rc = get_init_info_for_app(
+        pal::string_t{} /*host_command*/,
+        host_info,
+        host_info.app_path,
+        opts,
+        mode,
+        hostpolicy_dir,
+        init);
     if (rc != StatusCode::Success)
+    {
+        handle_initialize_failure_or_abort();
+        return rc;
+    }
+
+    std::unique_ptr<host_context_t> context;
+    rc = initialize_context(hostpolicy_dir, *init, intialization_options_t::none, context);
+    if (rc != StatusCode::Success)
+    {
+        trace::error(_X("Failed to initialize context for app: %s. Error code: 0x%x"), host_info.app_path.c_str(), rc);
+        return rc;
+    }
+
+    context->is_app = true;
+    for (int i = 0; i < argc; ++i)
+        context->argv.push_back(argv[i]);
+
+    trace::verbose(_X("Initialized context for app: %s"), host_info.app_path.c_str());
+    *host_context_handle = context.release();
+    return rc;
+}
+
+int fx_muxer_t::initialize_for_runtime_config(
+    const host_startup_info_t &host_info,
+    const pal::char_t *runtime_config_path,
+    hostfxr_handle *host_context_handle)
+{
+    int32_t initialization_options = intialization_options_t::none;
+    const host_context_t *existing_context;
+    {
+        std::unique_lock<std::mutex> lock{ g_context_lock };
+        g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); });
+
+        existing_context = g_active_host_context.get();
+        if (existing_context == nullptr)
+        {
+            g_context_initializing.store(true);
+        }
+        else if (existing_context->type == host_context_type::invalid)
+        {
+            return StatusCode::HostInvalidState;
+        }
+        else if (existing_context->type == host_context_type::empty)
+        {
+            initialization_options |= intialization_options_t::wait_for_initialized;
+        }
+    }
+
+    bool already_initialized = existing_context != nullptr;
+
+    int rc;
+    host_mode_t mode = host_mode_t::libhost;
+    pal::string_t runtime_config = runtime_config_path;
+    std::unique_ptr<host_context_t> context;
+    if (already_initialized)
+    {
+        std::unordered_map<pal::string_t, pal::string_t> config_properties;
+        rc = get_init_info_for_secondary_component(host_info, mode, runtime_config, existing_context, config_properties);
+        if (rc != StatusCode::Success)
+            return rc;
+
+        rc = host_context_t::create_secondary(existing_context->hostpolicy_contract, config_properties, initialization_options, context);
+    }
+    else
+    {
+        pal::string_t hostpolicy_dir;
+        std::unique_ptr<corehost_init_t> init;
+        rc = get_init_info_for_component(host_info, mode, runtime_config, hostpolicy_dir, init);
+        if (rc != StatusCode::Success)
+        {
+            handle_initialize_failure_or_abort();
+            return rc;
+        }
+
+        rc = initialize_context(hostpolicy_dir, *init, initialization_options, context);
+    }
+
+    if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized)
+    {
+        trace::error(_X("Failed to initialize context for config: %s. Error code: 0x%x"), runtime_config_path, rc);
         return rc;
+    }
+
+    context->is_app = false;
 
-    rc = get_delegate_from_runtime(impl_dir, init.get(), delegate_type, delegate);
+    trace::verbose(_X("Initialized %s for config: %s"), already_initialized ? _X("secondary context") : _X("context"), runtime_config_path);
+    *host_context_handle = context.release();
     return rc;
 }
 
+namespace
+{
+    int load_runtime(host_context_t *context)
+    {
+        assert(context->type == host_context_type::initialized || context->type == host_context_type::active);
+        if (context->type == host_context_type::active)
+            return StatusCode::Success;
+
+        const corehost_context_contract &contract = context->hostpolicy_context_contract;
+        int rc = contract.load_runtime();
+
+        // Mark the context as active or invalid
+        context->type = rc == StatusCode::Success ? host_context_type::active : host_context_type::invalid;
+
+        {
+            std::lock_guard<std::mutex> lock{ g_context_lock };
+            assert(g_active_host_context == nullptr);
+            g_active_host_context.reset(context);
+            g_context_initializing.store(false);
+        }
+
+        g_context_initializing_cv.notify_all();
+        return rc;
+    }
+}
+
+int fx_muxer_t::run_app(host_context_t *context)
+{
+    if (!context->is_app)
+        return StatusCode::InvalidArgFailure;
+
+    int argc = context->argv.size();
+    std::vector<const pal::char_t*> argv;
+    argv.reserve(argc);
+    for (const auto& str : context->argv)
+        argv.push_back(str.c_str());
+
+    const corehost_context_contract &contract = context->hostpolicy_context_contract;
+    {
+        propagate_error_writer_t propagate_error_writer_to_corehost(context->hostpolicy_contract.set_error_writer);
+
+        int rc = load_runtime(context);
+        if (rc != StatusCode::Success)
+            return rc;
+
+        return contract.run_app(argc, argv.data());
+    }
+}
+
+int fx_muxer_t::get_runtime_delegate(host_context_t *context, coreclr_delegate_type type, void **delegate)
+{
+    if (context->is_app)
+        return StatusCode::InvalidArgFailure;
+
+    const corehost_context_contract &contract = context->hostpolicy_context_contract;
+    {
+        propagate_error_writer_t propagate_error_writer_to_corehost(context->hostpolicy_contract.set_error_writer);
+
+        if (context->type != host_context_type::secondary)
+        {
+            int rc = load_runtime(context);
+            if (rc != StatusCode::Success)
+                return rc;
+        }
+
+        return contract.get_runtime_delegate(type, delegate);
+    }
+}
+
+const host_context_t* fx_muxer_t::get_active_host_context()
+{
+    std::lock_guard<std::mutex> lock{ g_context_lock };
+    return g_active_host_context.get();
+}
+
+int fx_muxer_t::close_host_context(host_context_t *context)
+{
+    if (context->type == host_context_type::initialized)
+    {
+        // The first context is being closed without being used to start the runtime
+        assert(g_active_host_context == nullptr);
+        handle_initialize_failure_or_abort(&context->hostpolicy_contract);
+    }
+
+    context->close();
+
+    // Do not delete the active context.
+    {
+        std::lock_guard<std::mutex> lock{ g_context_lock };
+        if (context != g_active_host_context.get())
+            delete context;
+    }
+
+    return StatusCode::Success;
+}
+
 int fx_muxer_t::handle_exec(
     const host_startup_info_t& host_info,
     const pal::string_t& app_candidate,
index 6c62c78..381a616 100644 (file)
@@ -4,22 +4,16 @@
 
 class corehost_init_t;
 class runtime_config_t;
-class fx_definition_t; 
+class fx_definition_t;
 struct fx_ver_t;
 struct host_startup_info_t;
 
+#include <corehost_context_contract.h>
+#include "error_codes.h"
 #include "fx_definition.h"
+#include "host_context.h"
 #include "host_interface.h"
 #include "host_startup_info.h"
-#include "error_codes.h"
-
-enum class coreclr_delegate_type
-{
-    invalid,
-    com_activation,
-    load_in_memory_assembly,
-    winrt_activation
-};
 
 class fx_muxer_t
 {
@@ -32,11 +26,22 @@ public:
         pal::char_t result_buffer[],
         int32_t buffer_size,
         int32_t* required_buffer_size);
-    static int load_runtime_and_get_delegate(
+    static int initialize_for_app(
         const host_startup_info_t& host_info,
-        host_mode_t mode,
+        int argc,
+        const pal::char_t* argv[],
+        hostfxr_handle *host_context_handle);
+    static int initialize_for_runtime_config(
+        const host_startup_info_t& host_info,
+        const pal::char_t * runtime_config_path,
+        hostfxr_handle *host_context_handle);
+    static int run_app(host_context_t *context);
+    static int get_runtime_delegate(
+        host_context_t *context,
         coreclr_delegate_type delegate_type,
         void** delegate);
+    static const host_context_t* get_active_host_context();
+    static int close_host_context(host_context_t *context);
 private:
     static int parse_args(
         const host_startup_info_t& host_info,
diff --git a/src/installer/corehost/cli/fxr/host_context.cpp b/src/installer/corehost/cli/fxr/host_context.cpp
new file mode 100644 (file)
index 0000000..8cf6319
--- /dev/null
@@ -0,0 +1,137 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "host_context.h"
+#include <trace.h>
+
+namespace
+{
+    const int32_t valid_host_context_marker = 0xabababab;
+    const int32_t closed_host_context_marker = 0xcdcdcdcd;
+
+    int create_context_common(
+        const hostpolicy_contract_t &hostpolicy_contract,
+        const host_interface_t *host_interface,
+        const corehost_initialize_request_t *init_request,
+        int32_t initialization_options,
+        bool already_loaded,
+        /*out*/ corehost_context_contract *hostpolicy_context_contract)
+    {
+        if (hostpolicy_contract.initialize == nullptr)
+        {
+            trace::error(_X("This component must target .NET Core 3.0 or a higher version."));
+            return StatusCode::HostApiUnsupportedVersion;
+        }
+
+        int rc = StatusCode::Success;
+        {
+            propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer);
+            if (!already_loaded)
+            {
+                assert (host_interface != nullptr);
+                rc = hostpolicy_contract.load(host_interface);
+            }
+
+            if (rc == StatusCode::Success)
+            {
+                rc = hostpolicy_contract.initialize(init_request, initialization_options, hostpolicy_context_contract);
+            }
+        }
+
+        return rc;
+    }
+}
+
+int host_context_t::create(
+    const hostpolicy_contract_t &hostpolicy_contract,
+    corehost_init_t &init,
+    int32_t initialization_options,
+    /*out*/ std::unique_ptr<host_context_t> &context)
+{
+    const host_interface_t &host_interface = init.get_host_init_data();
+    corehost_context_contract hostpolicy_context_contract;
+    int rc = create_context_common(hostpolicy_contract, &host_interface, nullptr, initialization_options, /*already_loaded*/ false, &hostpolicy_context_contract);
+    if (rc == StatusCode::Success)
+    {
+        std::unique_ptr<host_context_t> context_local(new host_context_t(host_context_type::initialized, hostpolicy_contract, hostpolicy_context_contract));
+        context = std::move(context_local);
+    }
+
+    return rc;
+}
+
+int host_context_t::create_secondary(
+    const hostpolicy_contract_t &hostpolicy_contract,
+    std::unordered_map<pal::string_t, pal::string_t> &config_properties,
+    int32_t initialization_options,
+    /*out*/ std::unique_ptr<host_context_t> &context)
+{
+    std::vector<const pal::char_t*> config_keys;
+    std::vector<const pal::char_t*> config_values;
+    for (auto &kv : config_properties)
+    {
+        config_keys.push_back(kv.first.c_str());
+        config_values.push_back(kv.second.c_str());
+    }
+
+    corehost_initialize_request_t init_request;
+    init_request.version = sizeof(corehost_initialize_request_t);
+    init_request.config_keys.len = config_keys.size();
+    init_request.config_keys.arr = config_keys.data();
+    init_request.config_values.len = config_values.size();
+    init_request.config_values.arr = config_values.data();
+
+    corehost_context_contract hostpolicy_context_contract;
+    int rc = create_context_common(hostpolicy_contract, nullptr, &init_request, initialization_options, /*already_loaded*/ true, &hostpolicy_context_contract);
+    if (rc == StatusCode::CoreHostAlreadyInitialized)
+    {
+        std::unique_ptr<host_context_t> context_local(new host_context_t(host_context_type::secondary, hostpolicy_contract, hostpolicy_context_contract));
+        context_local->config_properties = config_properties;
+        context = std::move(context_local);
+    }
+
+    assert(rc != StatusCode::Success);
+    return rc;
+}
+
+host_context_t* host_context_t::from_handle(const hostfxr_handle handle, bool allow_invalid_type)
+{
+    if (handle == nullptr)
+        return nullptr;
+
+    host_context_t *context = static_cast<host_context_t*>(handle);
+    int32_t marker = context->marker;
+    if (marker == valid_host_context_marker)
+    {
+        if (allow_invalid_type || context->type != host_context_type::invalid)
+            return context;
+
+        trace::error(_X("Host context is in an invalid state"));
+    }
+    else if (marker == closed_host_context_marker)
+    {
+        trace::error(_X("Host context has already been closed"));
+    }
+    else
+    {
+        trace::error(_X("Invalid host context handle marker: 0x%x"), marker);
+    }
+
+    return nullptr;
+}
+
+host_context_t::host_context_t(
+    host_context_type type,
+    const hostpolicy_contract_t &hostpolicy_contract,
+    const corehost_context_contract &hostpolicy_context_contract)
+    : marker { valid_host_context_marker }
+    , type { type }
+    , hostpolicy_contract { hostpolicy_contract }
+    , hostpolicy_context_contract { hostpolicy_context_contract }
+{ }
+
+void host_context_t::close()
+{
+    marker = closed_host_context_marker;
+}
\ No newline at end of file
diff --git a/src/installer/corehost/cli/fxr/host_context.h b/src/installer/corehost/cli/fxr/host_context.h
new file mode 100644 (file)
index 0000000..749e44b
--- /dev/null
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef __HOST_CONTEXT_H__
+#define __HOST_CONTEXT_H__
+
+#include <pal.h>
+
+#include <corehost_context_contract.h>
+#include "corehost_init.h"
+#include <hostfxr.h>
+#include "hostpolicy_resolver.h"
+
+enum class host_context_type
+{
+    empty,        // Not populated, cannot be used for context-based operations
+    initialized,  // Created, but not active (runtime not loaded)
+    active,       // Runtime loaded for this context
+    secondary,    // Created after runtime was loaded using another context
+    invalid,      // Failed on loading runtime
+};
+
+struct host_context_t
+{
+public: // static
+    static int create(
+        const hostpolicy_contract_t &hostpolicy_contract,
+        corehost_init_t &init,
+        int32_t initialization_options,
+        /*out*/ std::unique_ptr<host_context_t> &context);
+    static int create_secondary(
+        const hostpolicy_contract_t &hostpolicy_contract,
+        std::unordered_map<pal::string_t, pal::string_t> &config_properties,
+        int32_t initialization_options,
+        /*out*/ std::unique_ptr<host_context_t> &context);
+    static host_context_t* from_handle(const hostfxr_handle handle, bool allow_invalid_type = false);
+
+public:
+    int32_t marker; // used as an indication for validity
+
+    host_context_type type;
+    const hostpolicy_contract_t hostpolicy_contract;
+    const corehost_context_contract hostpolicy_context_contract;
+
+    // Whether or not the context was initialized for an app. argv will be empty for non-app contexts.
+    bool is_app;
+    std::vector<pal::string_t> argv;
+
+    // Config properties for secondary contexts
+    std::unordered_map<pal::string_t, pal::string_t> config_properties;
+
+    host_context_t(
+        host_context_type type,
+        const hostpolicy_contract_t &hostpolicy_contract,
+        const corehost_context_contract &hostpolicy_context_contract);
+
+    void close();
+};
+
+#endif // __HOST_CONTEXT_H__
\ No newline at end of file
index d836eb5..a42ed43 100644 (file)
 #include "sdk_info.h"
 #include "sdk_resolver.h"
 #include "hostfxr.h"
+#include "host_context.h"
 
-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)
+namespace
 {
-    trace::setup();
+    void trace_hostfxr_entry_point(const pal::char_t *entry_point)
+    {
+        trace::setup();
+        trace::info(_X("--- Invoked %s [commit hash: %s]"), entry_point, _STRINGIFY(REPO_COMMIT_HASH));
+    }
+}
 
-    trace::info(_X("--- Invoked hostfxr v2 [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH));
+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_hostfxr_entry_point(_X("hostfxr_main_startupinfo"));
 
     host_startup_info_t startup_info(host_path, dotnet_root, app_path);
 
@@ -27,9 +35,7 @@ SHARED_API int hostfxr_main_startupinfo(const int argc, const pal::char_t* argv[
 
 SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[])
 {
-    trace::setup();
-
-    trace::info(_X("--- Invoked hostfxr [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH));
+    trace_hostfxr_entry_point(_X("hostfxr_main"));
 
     host_startup_info_t startup_info;
     startup_info.parse(argc, argv);
@@ -51,13 +57,13 @@ SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[])
 //      sub-folders. Pass the directory of a dotnet executable to
 //      mimic how that executable would search in its own directory.
 //      It is also valid to pass nullptr or empty, in which case
-//      multi-level lookup can still search other locations if 
+//      multi-level lookup can still search other locations if
 //      it has not been disabled by the user's environment.
 //
 //    working_dir
 //      The directory where the search for global.json (which can
 //      control the resolved SDK version) starts and proceeds
-//      upwards. 
+//      upwards.
 //
 //    buffer
 //      The buffer where the resolved SDK path will be written.
@@ -86,9 +92,7 @@ SHARED_API int32_t hostfxr_resolve_sdk(
     pal::char_t buffer[],
     int32_t buffer_size)
 {
-    trace::setup();
-
-    trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_resolve_sdk"), _STRINGIFY(REPO_COMMIT_HASH));
+    trace_hostfxr_entry_point(_X("hostfxr_resolve_sdk"));
 
     if (buffer_size < 0 || (buffer_size > 0 && buffer == nullptr))
     {
@@ -157,18 +161,18 @@ typedef void (*hostfxr_resolve_sdk2_result_fn)(
 //      sub-folders. Pass the directory of a dotnet executable to
 //      mimic how that executable would search in its own directory.
 //      It is also valid to pass nullptr or empty, in which case
-//      multi-level lookup can still search other locations if 
+//      multi-level lookup can still search other locations if
 //      it has not been disabled by the user's environment.
 //
 //    working_dir
 //      The directory where the search for global.json (which can
 //      control the resolved SDK version) starts and proceeds
-//      upwards. 
+//      upwards.
 //
 //   flags
 //      Bitwise flags that influence resolution.
 //         disallow_prerelease (0x1)
-//           do not allow resolution to return a prerelease SDK version 
+//           do not allow resolution to return a prerelease SDK version
 //           unless  prerelease version was specified via global.json.
 //
 //   result
@@ -191,7 +195,7 @@ typedef void (*hostfxr_resolve_sdk2_result_fn)(
 // Return value:
 //   0 on success, otherwise failure
 //   0x8000809b - SDK could not be resolved (SdkResolverResolveFailure)
-// 
+//
 // String encoding:
 //   Windows     - UTF-16 (pal::char_t is 2 byte wchar_t)
 //   Unix        - UTF-8  (pal::char_t is 1 byte char)
@@ -202,9 +206,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2(
     int32_t flags,
     hostfxr_resolve_sdk2_result_fn result)
 {
-    trace::setup();
-
-    trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_resolve_sdk2"), _STRINGIFY(REPO_COMMIT_HASH));
+    trace_hostfxr_entry_point(_X("hostfxr_resolve_sdk2"));
 
     if (exe_dir == nullptr)
     {
@@ -220,7 +222,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2(
     pal::string_t global_json_path;
 
     bool success = sdk_resolver_t::resolve_sdk_dotnet_path(
-        exe_dir, 
+        exe_dir,
         working_dir,
         &resolved_sdk_dir,
         (flags & hostfxr_resolve_sdk2_flags_t::disallow_prerelease) != 0,
@@ -241,7 +243,7 @@ SHARED_API int32_t hostfxr_resolve_sdk2(
     }
 
     return success
-        ? StatusCode::Success 
+        ? StatusCode::Success
         : StatusCode::SdkResolverResolveFailure;
 }
 
@@ -276,9 +278,7 @@ SHARED_API int32_t hostfxr_get_available_sdks(
     const pal::char_t* exe_dir,
     hostfxr_get_available_sdks_result_fn result)
 {
-    trace::setup();
-
-    trace::info(_X("--- Invoked hostfxr [commit hash: %s] hostfxr_get_available_sdks"), _STRINGIFY(REPO_COMMIT_HASH));
+    trace_hostfxr_entry_point(_X("hostfxr_get_available_sdks"));
 
     if (exe_dir == nullptr)
     {
@@ -304,7 +304,7 @@ SHARED_API int32_t hostfxr_get_available_sdks(
 
         result(sdk_dirs.size(), &sdk_dirs[0]);
     }
-    
+
     return StatusCode::Success;
 }
 
@@ -350,9 +350,7 @@ SHARED_API int32_t hostfxr_get_available_sdks(
 //
 SHARED_API int32_t hostfxr_get_native_search_directories(const int argc, const pal::char_t* argv[], pal::char_t buffer[], int32_t buffer_size, int32_t* required_buffer_size)
 {
-    trace::setup();
-
-    trace::info(_X("--- Invoked hostfxr_get_native_search_directories [commit hash: %s] main"), _STRINGIFY(REPO_COMMIT_HASH));
+    trace_hostfxr_entry_point(_X("hostfxr_get_native_search_directories"));
 
     if (buffer_size < 0 || (buffer_size > 0 && buffer == nullptr) || required_buffer_size == nullptr)
     {
@@ -373,18 +371,18 @@ typedef void(*hostfxr_error_writer_fn)(const pal::char_t* message);
 // Sets a callback which is to be used to write errors to.
 //
 // Parameters:
-//     error_writer 
+//     error_writer
 //         A callback function which will be invoked every time an error is to be reported.
 //         Or nullptr to unregister previously registered callback and return to the default behavior.
 // Return value:
 //     The previously registered callback (which is now unregistered), or nullptr if no previous callback
 //     was registered
-// 
+//
 // The error writer is registered per-thread, so the registration is thread-local. On each thread
 // only one callback can be registered. Subsequent registrations overwrite the previous ones.
-// 
+//
 // By default no callback is registered in which case the errors are written to stderr.
-// 
+//
 // Each call to the error writer is sort of like writing a single line (the EOL character is omitted).
 // Multiple calls to the error writer may occure for one failure.
 //
@@ -397,55 +395,452 @@ SHARED_API hostfxr_error_writer_fn hostfxr_set_error_writer(hostfxr_error_writer
     return trace::set_error_writer(error_writer);
 }
 
-coreclr_delegate_type hostfxr_delegate_to_coreclr_delegate(hostfxr_delegate_type type)
+namespace
 {
-    switch (type)
+    int populate_startup_info(const hostfxr_initialize_parameters *parameters, host_startup_info_t &startup_info)
     {
-    case hostfxr_delegate_type::com_activation:
-        return coreclr_delegate_type::com_activation;
-    case hostfxr_delegate_type::load_in_memory_assembly:
-        return coreclr_delegate_type::load_in_memory_assembly;
-    case hostfxr_delegate_type::winrt_activation:
-        return coreclr_delegate_type::winrt_activation;
+        if (parameters != nullptr)
+        {
+            if (parameters->host_path != nullptr)
+                startup_info.host_path = parameters->host_path;
+
+            if (parameters->dotnet_root != nullptr)
+                startup_info.dotnet_root = parameters->dotnet_root;
+        }
+
+        if (startup_info.host_path.empty())
+        {
+            if (!pal::get_own_executable_path(&startup_info.host_path) || !pal::realpath(&startup_info.host_path))
+            {
+                trace::error(_X("Failed to resolve full path of the current host [%s]"), startup_info.host_path.c_str());
+                return StatusCode::CoreHostCurHostFindFailure;
+            }
+        }
+
+        if (startup_info.dotnet_root.empty())
+        {
+            pal::string_t mod_path;
+            if (!pal::get_own_module_path(&mod_path))
+                return StatusCode::CoreHostCurHostFindFailure;
+
+            startup_info.dotnet_root = get_dotnet_root_from_fxr_path(mod_path);
+            if (!pal::realpath(&startup_info.dotnet_root))
+            {
+                trace::error(_X("Failed to resolve full path of dotnet root [%s]"), startup_info.dotnet_root.c_str());
+                return StatusCode::CoreHostCurHostFindFailure;
+            }
+        }
+
+        return StatusCode::Success;
+    }
+}
+
+//
+// Initializes the hosting components for running an application
+//
+// Parameters:
+//    argc
+//      Number of argv arguments
+//    argv
+//      Arguments for the application. If argc is 0, this is ignored.
+//    app_path
+//      Path to the managed application. If this is nullptr, the first argument in argv is assumed to be
+//      the application path.
+//    parameters
+//      Optional. Additional parameters for initialization
+//    host_context_handle
+//      On success, this will be populated with an opaque value representing the initalized host context
+//
+// Return value:
+//    Success          - Hosting components were successfully initialized
+//    HostInvalidState - Hosting components are already initialized
+//
+// The app_path will be used to find the corresponding .runtimeconfig.json and .deps.json with which to
+// resolve frameworks and dependencies and prepare everything needed to load the runtime.
+//
+// This function does not load the runtime.
+//
+SHARED_API int32_t __cdecl hostfxr_initialize_for_app(
+    int argc,
+    const pal::char_t *argv[],
+    const pal::char_t *app_path,
+    const hostfxr_initialize_parameters * parameters,
+    /*out*/ hostfxr_handle * host_context_handle)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_initialize_for_app"));
+
+    if (host_context_handle == nullptr || (argv == nullptr && argc != 0) || (app_path == nullptr && argc == 0))
+        return StatusCode::InvalidArgFailure;
+
+    *host_context_handle = nullptr;
+
+    host_startup_info_t startup_info{};
+    int new_argc;
+    const pal::char_t **new_argv;
+    if (app_path != nullptr)
+    {
+        startup_info.app_path = app_path;
+        new_argc = argc;
+        new_argv = argv;
+    }
+    else
+    {
+        // Take the first argument as the app path
+        startup_info.app_path = argv[0];
+        new_argc = argc-1;
+        new_argv = argc > 0 ? &argv[1] : nullptr;
+    }
+
+    int rc = populate_startup_info(parameters, startup_info);
+    if (rc != StatusCode::Success)
+        return rc;
+
+    return fx_muxer_t::initialize_for_app(
+        startup_info,
+        new_argc,
+        new_argv,
+        host_context_handle);
+}
+
+//
+// Initializes the hosting components using a .runtimeconfig.json file
+//
+// Parameters:
+//    runtime_config_path
+//      Path to the .runtimeconfig.json file
+//    parameters
+//      Optional. Additional parameters for initialization
+//    host_context_handle
+//      On success, this will be populated with an opaque value representing the initalized host context
+//
+// Return value:
+//    Success                     - Hosting components were successfully initialized
+//    CoreHostAlreadyInitialized  - Config is compatible with already initialized hosting components
+// [TODO]
+//    CoreHostIncompatibleConfig  - Config is incompatible with already initialized hosting components
+//    CoreHostDifferentProperties - Config has runtime properties that differ from already initialized hosting components
+//
+// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed
+// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that
+// may be next to the .runtimeconfig.json).
+//
+// This function does not load the runtime.
+//
+// If called when the runtime has already been loaded, this function will check if the specified runtime
+// config is compatible with the existing runtime.
+//
+SHARED_API int32_t __cdecl hostfxr_initialize_for_runtime_config(
+    const pal::char_t *runtime_config_path,
+    const hostfxr_initialize_parameters *parameters,
+    /*out*/ hostfxr_handle *host_context_handle)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_initialize_for_runtime_config"));
+
+    if (runtime_config_path == nullptr || host_context_handle == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    *host_context_handle = nullptr;
+
+    host_startup_info_t startup_info{};
+    int rc = populate_startup_info(parameters, startup_info);
+    if (rc != StatusCode::Success)
+        return rc;
+
+    return fx_muxer_t::initialize_for_runtime_config(
+        startup_info,
+        runtime_config_path,
+        host_context_handle);
+}
+
+//
+// Load CoreCLR and run the application for an initialized host context
+//
+// Parameters:
+//     host_context_handle
+//       Handle to the initialized host context
+//
+// Return value:
+//     If the app was successfully run, the exit code of the application. Otherwise, the error code result.
+//
+// The host_context_handle must have been initialized using hostfxr_initialize_for_app.
+//
+// This function will not return until the managed application exits.
+//
+SHARED_API int32_t __cdecl hostfxr_run_app(const hostfxr_handle host_context_handle)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_run_app"));
+
+    host_context_t *context = host_context_t::from_handle(host_context_handle);
+    if (context == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    return fx_muxer_t::run_app(context);
+}
+
+namespace
+{
+    coreclr_delegate_type hostfxr_delegate_to_coreclr_delegate(hostfxr_delegate_type type)
+    {
+        switch (type)
+        {
+        case hostfxr_delegate_type::com_activation:
+            return coreclr_delegate_type::com_activation;
+        case hostfxr_delegate_type::load_in_memory_assembly:
+            return coreclr_delegate_type::load_in_memory_assembly;
+        case hostfxr_delegate_type::winrt_activation:
+            return coreclr_delegate_type::winrt_activation;
+        }
+        return coreclr_delegate_type::invalid;
     }
-    return coreclr_delegate_type::invalid;
 }
 
 //
 // Gets a typed delegate from the currently loaded CoreCLR or from a newly created one.
 //
 // Parameters:
-//     libhost_path
-//          Absolute path of the entry hosting library
-//     dotnet_root
-//     app_path
+//     host_context_handle
+//       Handle to the initialized host context
+//     type
+//       Type of runtime delegate requested
 //     delegate
-//          An out parameter that will be assigned the delegate.
+//       An out parameter that will be assigned the 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.
+// The host_context_handle must have been initialized using hostfxr_initialize_for_runtime_config.
 //
-SHARED_API int32_t hostfxr_get_runtime_delegate(
-    const pal::char_t* host_path,
-    const pal::char_t* dotnet_root,
-    const pal::char_t* app_path,
+SHARED_API int32_t __cdecl hostfxr_get_runtime_delegate(
+    const hostfxr_handle host_context_handle,
     hostfxr_delegate_type type,
-    void** delegate)
+    /*out*/ void **delegate)
 {
-    if (host_path == nullptr || dotnet_root == nullptr || delegate == nullptr)
+    trace_hostfxr_entry_point(_X("hostfxr_get_runtime_delegate"));
+
+    if (delegate == nullptr)
         return StatusCode::InvalidArgFailure;
 
-    trace::setup();
+    *delegate = nullptr;
 
-    trace::info(_X("--- Invoked hostfxr_get_runtime_delegate [commit hash: %s]"), _STRINGIFY(REPO_COMMIT_HASH));
+    host_context_t *context = host_context_t::from_handle(host_context_handle);
+    if (context == nullptr)
+        return StatusCode::InvalidArgFailure;
 
-    host_startup_info_t startup_info{ host_path, dotnet_root, app_path };
+    return fx_muxer_t::get_runtime_delegate(context, hostfxr_delegate_to_coreclr_delegate(type), delegate);
+}
 
-    return fx_muxer_t::load_runtime_and_get_delegate(
-        startup_info,
-        host_mode_t::libhost,
-        hostfxr_delegate_to_coreclr_delegate(type),
-        delegate);
+//
+// Gets the runtime property value for an initialized host context
+//
+// Parameters:
+//     host_context_handle
+//       Handle to the initialized host context
+//     name
+//       Runtime property name
+//     value
+//       Out parameter. Pointer to a buffer with the property value.
+//
+// Return value:
+//     The error code result.
+//
+// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only
+// guaranteed until any of the below occur:
+//   - a 'run' method is called for the host context
+//   - properties are changed via hostfxr_set_runtime_property_value
+//   - the host context is closed via 'hostfxr_close'
+//
+// If host_context_handle is nullptr and an active host context exists, this function will get the
+// property value for the active host context.
+//
+SHARED_API int32_t __cdecl hostfxr_get_runtime_property_value(
+    const hostfxr_handle host_context_handle,
+    const pal::char_t *name,
+    /*out*/ const pal::char_t **value)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_get_runtime_property_value"));
+
+    if (name == nullptr || value == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    const host_context_t *context;
+    if (host_context_handle == nullptr)
+    {
+        const host_context_t *context_maybe = fx_muxer_t::get_active_host_context();
+        if (context_maybe == nullptr || context_maybe->type != host_context_type::active)
+        {
+            trace::error(_X("Hosting components context has not been initialized. Cannot get runtime properties."));
+            return StatusCode::HostInvalidState;
+        }
+
+        context = context_maybe;
+    }
+    else
+    {
+        context = host_context_t::from_handle(host_context_handle);
+        if (context == nullptr)
+            return StatusCode::InvalidArgFailure;
+    }
+
+
+    if (context->type == host_context_type::secondary)
+    {
+        const std::unordered_map<pal::string_t, pal::string_t> &properties = context->config_properties;
+        auto iter = properties.find(name);
+        if (iter == properties.cend())
+            return StatusCode::HostPropertyNotFound;
+
+        *value = (*iter).second.c_str();
+        return StatusCode::Success;
+    }
+
+    assert(context->type == host_context_type::initialized || context->type == host_context_type::active);
+    const corehost_context_contract &contract = context->hostpolicy_context_contract;
+    return contract.get_property_value(name, value);
+}
+
+//
+// Sets the value of a runtime property for an initialized host context
+//
+// Parameters:
+//     host_context_handle
+//       Handle to the initialized host context
+//     name
+//       Runtime property name
+//     value
+//       Value to set
+//
+// Return value:
+//     The error code result.
+//
+// Setting properties is only supported for the first host context, before the runtime has been loaded.
+//
+// If the property already exists in the host context, it will be overwritten. If value is nullptr, the
+// property will be removed.
+//
+SHARED_API int32_t __cdecl hostfxr_set_runtime_property_value(
+    const hostfxr_handle host_context_handle,
+    const pal::char_t *name,
+    const pal::char_t *value)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_set_runtime_property_value"));
+
+    if (name == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    host_context_t *context = host_context_t::from_handle(host_context_handle);
+    if (context == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    if (context->type != host_context_type::initialized)
+    {
+        trace::error(_X("Setting properties is not allowed once runtime has been loaded."));
+        return StatusCode::InvalidArgFailure;
+    }
+
+    const corehost_context_contract &contract = context->hostpolicy_context_contract;
+    return contract.set_property_value(name, value);
+}
+
+//
+// Gets all the runtime properties for an initialized host context
+//
+// Parameters:
+//     host_context_handle
+//       Handle to the initialized host context
+//     count
+//       [in] Size of the keys and values buffers
+//       [out] Number of properties returned (size of keys/values buffers used). If the input value is too
+//             small or keys/values is nullptr, this is populated with the number of available properties
+//     keys
+//       Array of pointers to buffers with runtime property keys
+//     values
+//       Array of pointers to buffers with runtime property values
+//
+// Return value:
+//     The error code result.
+//
+// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only
+// guaranteed until any of the below occur:
+//   - a 'run' method is called for the host context
+//   - properties are changed via hostfxr_set_runtime_property_value
+//   - the host context is closed via 'hostfxr_close'
+//
+// If host_context_handle is nullptr and an active host context exists, this function will get the
+// properties for the active host context.
+//
+SHARED_API int32_t __cdecl hostfxr_get_runtime_properties(
+    const hostfxr_handle host_context_handle,
+    /*inout*/ size_t * count,
+    /*out*/ const pal::char_t **keys,
+    /*out*/ const pal::char_t **values)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_get_runtime_properties"));
+
+    if (count == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    const host_context_t *context;
+    if (host_context_handle == nullptr)
+    {
+        const host_context_t *context_maybe = fx_muxer_t::get_active_host_context();
+        if (context_maybe == nullptr || context_maybe->type != host_context_type::active)
+        {
+            trace::error(_X("Hosting components context has not been initialized. Cannot get runtime properties."));
+            return StatusCode::HostInvalidState;
+        }
+
+        context = context_maybe;
+    }
+    else
+    {
+        context = host_context_t::from_handle(host_context_handle);
+        if (context == nullptr)
+            return StatusCode::InvalidArgFailure;
+    }
+
+    if (context->type == host_context_type::secondary)
+    {
+        const std::unordered_map<pal::string_t, pal::string_t> &properties = context->config_properties;
+        size_t actualCount = properties.size();
+        size_t input_count = *count;
+        *count = actualCount;
+        if (input_count < actualCount || keys == nullptr || values == nullptr)
+            return StatusCode::HostApiBufferTooSmall;
+
+        int i = 0;
+        for (const auto& kv : properties)
+        {
+            keys[i] = kv.first.data();
+            values[i] = kv.second.data();
+            ++i;
+        }
+
+        return StatusCode::Success;
+    }
+
+    assert(context->type == host_context_type::initialized || context->type == host_context_type::active);
+    const corehost_context_contract &contract = context->hostpolicy_context_contract;
+    return contract.get_properties(count, keys, values);
 }
+
+//
+// Closes an initialized host context
+//
+// Parameters:
+//     host_context_handle
+//       Handle to the initialized host context
+//
+// Return value:
+//     The error code result.
+//
+SHARED_API int32_t __cdecl hostfxr_close(const hostfxr_handle host_context_handle)
+{
+    trace_hostfxr_entry_point(_X("hostfxr_close"));
+
+    // Allow contexts with a type of invalid as we still need to clean them up
+    host_context_t *context = host_context_t::from_handle(host_context_handle, /*allow_invalid_type*/ true);
+    if (context == nullptr)
+        return StatusCode::InvalidArgFailure;
+
+    return fx_muxer_t::close_host_context(context);
+}
\ No newline at end of file
index b159960..1feb3cb 100644 (file)
@@ -15,7 +15,8 @@ namespace
 {
     std::mutex g_hostpolicy_lock;
     pal::dll_t g_hostpolicy;
-    hostpolicy_contract g_hostpolicy_contract;
+    hostpolicy_contract_t g_hostpolicy_contract;
+    pal::string_t g_hostpolicy_dir;
 
     /**
     * Resolve the hostpolicy version from deps.
@@ -189,8 +190,8 @@ namespace
 
 int hostpolicy_resolver::load(
     const pal::string_t& lib_dir,
-    pal::dll_t* h_host,
-    hostpolicy_contract &host_contract)
+    pal::dll_t* dll,
+    hostpolicy_contract_t &hostpolicy_contract)
 {
     std::lock_guard<std::mutex> lock{ g_hostpolicy_lock };
     if (g_hostpolicy == nullptr)
@@ -202,6 +203,7 @@ int hostpolicy_resolver::load(
         }
 
         // Load library
+        // We expect to leak hostpolicy - just as we do not unload coreclr, we do not unload hostpolicy
         if (!pal::load_library(&host_path, &g_hostpolicy))
         {
             trace::info(_X("Load library of %s failed"), host_path.c_str());
@@ -215,15 +217,24 @@ int hostpolicy_resolver::load(
             return StatusCode::CoreHostEntryPointFailure;
 
         g_hostpolicy_contract.set_error_writer = reinterpret_cast<corehost_set_error_writer_fn>(pal::get_symbol(g_hostpolicy, "corehost_set_error_writer"));
+        g_hostpolicy_contract.initialize = reinterpret_cast<corehost_initialize_fn>(pal::get_symbol(g_hostpolicy, "corehost_initialize"));
 
-        // 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.
+        // It's possible to not have corehost_set_error_writer and corehost_initialize. These were
+        // introduced in 3.0, so 2.0 hostpolicy would not have the exports. In this case, we will
+        // not propagate the error writer and errors will still be reported to stderr. Callers are
+        // responsible for checking that the function pointers are not null before using them.
+
+        g_hostpolicy_dir = lib_dir;
+    }
+    else
+    {
+        if (!pal::are_paths_equal_with_normalized_casing(g_hostpolicy_dir, lib_dir))
+            trace::warning(_X("The library %s was already loaded from [%s]. Reusing the existing library for the request to load from [%s]"), LIBHOSTPOLICY_NAME, g_hostpolicy_dir.c_str(), lib_dir.c_str());
     }
 
     // Return global values
-    *h_host = g_hostpolicy;
-    host_contract = g_hostpolicy_contract;
+    *dll = g_hostpolicy;
+    hostpolicy_contract = g_hostpolicy_contract;
 
     return StatusCode::Success;
 }
index ec9156a..e290ad5 100644 (file)
@@ -2,16 +2,21 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+#ifndef __HOSTPOLICY_RESOLVER_H__
+#define __HOSTPOLICY_RESOLVER_H__
+
 #include <fx_definition.h>
 #include <host_interface.h>
 #include <error_codes.h>
+#include <corehost_context_contract.h>
 
 using corehost_load_fn = int(*) (const host_interface_t* init);
 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);
+using corehost_initialize_fn = int(*)(const corehost_initialize_request_t* init_request, int32_t options, corehost_context_contract* handle);
 
-struct hostpolicy_contract
+struct hostpolicy_contract_t
 {
     // Required API contracts
     corehost_load_fn load;
@@ -19,14 +24,15 @@ struct hostpolicy_contract
 
     // 3.0+ contracts
     corehost_set_error_writer_fn set_error_writer;
+    corehost_initialize_fn initialize;
 };
 
 namespace hostpolicy_resolver
 {
     int load(
         const pal::string_t& lib_dir,
-        pal::dll_t* h_host,
-        hostpolicy_contract &host_contract);
+        pal::dll_t* dll,
+        hostpolicy_contract_t &hostpolicy_contract);
     bool try_get_dir(
         host_mode_t mode,
         const pal::string_t& dotnet_root,
@@ -36,3 +42,5 @@ namespace hostpolicy_resolver
         const std::vector<pal::string_t>& probe_realpaths,
         pal::string_t* impl_dir);
 };
+
+#endif // __HOSTPOLICY_RESOLVER_H__
\ No newline at end of file
index b9a9d4c..c841b64 100644 (file)
@@ -139,14 +139,3 @@ bool fxr_resolver::try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_
     trace::verbose(_X("Found previously loaded library %s [%s]."), LIBFXR_NAME, out_fxr_path->c_str());
     return true;
 }
-
-pal::string_t fxr_resolver::dotnet_root_from_fxr_path(const pal::string_t &fxr_path)
-{
-    // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained)
-    if (coreclr_exists_in_dir(fxr_path))
-        return get_directory(fxr_path);
-
-    // Path to hostfxr is: <dotnet_root>/host/fxr/<version>/<hostfxr_file>
-    pal::string_t fxr_root = get_directory(get_directory(fxr_path));
-    return get_directory(get_directory(fxr_root));
-}
index ee34835..38b3522 100644 (file)
@@ -15,11 +15,10 @@ namespace fxr_resolver
 {
     bool try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path);
     bool try_get_existing_fxr(pal::dll_t *out_fxr, pal::string_t *out_fxr_path);
-    pal::string_t dotnet_root_from_fxr_path(const pal::string_t &fxr_path);
 }
 
-template<typename THostNameToAppNameCallback, typename TDelegate>
-int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCallback host_path_to_app_path, TDelegate* delegate, pal::string_t* out_app_path = nullptr)
+template<typename THostPathToConfigCallback, typename TDelegate>
+int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TDelegate* delegate)
 {
     pal::dll_t fxr;
 
@@ -34,7 +33,7 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCall
     pal::string_t fxr_path;
     if (fxr_resolver::try_get_existing_fxr(&fxr, &fxr_path))
     {
-        dotnet_root = fxr_resolver::dotnet_root_from_fxr_path(fxr_path);
+        dotnet_root = get_dotnet_root_from_fxr_path(fxr_path);
         trace::verbose(_X("The library %s was already loaded. Reusing the previously loaded library [%s]."), LIBFXR_NAME, fxr_path.c_str());
     }
     else
@@ -56,22 +55,41 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostNameToAppNameCall
 
     // Leak fxr
 
-    auto get_delegate_from_hostfxr = reinterpret_cast<hostfxr_get_delegate_fn>(pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"));
-    if (get_delegate_from_hostfxr == nullptr)
+    auto hostfxr_initialize_for_runtime_config = reinterpret_cast<hostfxr_initialize_for_runtime_config_fn>(pal::get_symbol(fxr, "hostfxr_initialize_for_runtime_config"));
+    auto hostfxr_get_runtime_delegate = reinterpret_cast<hostfxr_get_runtime_delegate_fn>(pal::get_symbol(fxr, "hostfxr_get_runtime_delegate"));
+    auto hostfxr_close = reinterpret_cast<hostfxr_close_fn>(pal::get_symbol(fxr, "hostfxr_close"));
+    if (hostfxr_initialize_for_runtime_config == nullptr || hostfxr_get_runtime_delegate == nullptr || hostfxr_close == nullptr)
         return StatusCode::CoreHostEntryPointFailure;
 
-    pal::string_t app_path;
-
-    pal::string_t* app_path_to_use = out_app_path != nullptr ? out_app_path : &app_path;
-
-    pal::hresult_t status = host_path_to_app_path(host_path, app_path_to_use);
+    pal::string_t config_path;
+    pal::hresult_t status = host_path_to_config_path(host_path, &config_path);
 
     if (status != StatusCode::Success)
     {
         return status;
     }
 
-    return get_delegate_from_hostfxr(host_path.c_str(), dotnet_root.c_str(), app_path_to_use->c_str(), type, reinterpret_cast<void**>(delegate));
+    hostfxr_initialize_parameters parameters {
+        sizeof(hostfxr_initialize_parameters),
+        host_path.c_str(),
+        dotnet_root.c_str()
+    };
+
+    hostfxr_handle context;
+    int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &context);
+    if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized)
+        return rc;
+
+    rc = hostfxr_get_runtime_delegate(context, type, reinterpret_cast<void**>(delegate));
+
+    int rcClose = hostfxr_close(context);
+    if (rcClose != StatusCode::Success)
+    {
+        assert(false && "Failed to close host context");
+        trace::verbose(_X("Failed to close host context: 0x%x"), rcClose);
+    }
+
+    return rc;
 }
 
 #endif //_COREHOST_CLI_FXR_RESOLVER_H_
index 759a322..bc4ab70 100644 (file)
@@ -23,13 +23,6 @@ enum class hostfxr_delegate_type
     winrt_activation
 };
 
-using hostfxr_get_delegate_fn = int32_t(*)(
-    const pal::char_t* host_path,
-    const pal::char_t* dotnet_root,
-    const pal::char_t* app_path,
-    hostfxr_delegate_type type,
-    void** delegate);
-
 using hostfxr_main_fn = int32_t(*)(const int argc, const pal::char_t* argv[]);
 using hostfxr_main_startupinfo_fn = int32_t(*)(
     const int argc,
@@ -40,4 +33,45 @@ using hostfxr_main_startupinfo_fn = int32_t(*)(
 using hostfxr_error_writer_fn = void(*)(const pal::char_t* message);
 using hostfxr_set_error_writer_fn = hostfxr_error_writer_fn(*)(hostfxr_error_writer_fn error_writer);
 
+using hostfxr_handle = void*;
+struct hostfxr_initialize_parameters
+{
+    size_t size;
+    const pal::char_t *host_path;
+    const pal::char_t *dotnet_root;
+};
+
+using hostfxr_initialize_for_app_fn = int32_t(__cdecl *)(
+    int argc,
+    const pal::char_t *argv[],
+    const pal::char_t *app_path,
+    const hostfxr_initialize_parameters *parameters,
+    /*out*/ hostfxr_handle *host_context_handle);
+using hostfxr_initialize_for_runtime_config_fn = int32_t(__cdecl *)(
+    const pal::char_t *runtime_config_path,
+    const hostfxr_initialize_parameters*parameters,
+    /*out*/ hostfxr_handle *host_context_handle);
+
+using hostfxr_get_runtime_property_value_fn = int32_t(__cdecl *)(
+    const hostfxr_handle host_context_handle,
+    const pal::char_t *name,
+    /*out*/ const pal::char_t **value);
+using hostfxr_set_runtime_property_value_fn = int32_t(__cdecl *)(
+    const hostfxr_handle host_context_handle,
+    const pal::char_t *name,
+    const pal::char_t *value);
+using hostfxr_get_runtime_properties_fn = int32_t(__cdecl *)(
+    const hostfxr_handle host_context_handle,
+    /*inout*/ size_t * count,
+    /*out*/ const pal::char_t **keys,
+    /*out*/ const pal::char_t **values);
+
+using hostfxr_run_app_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle);
+using hostfxr_get_runtime_delegate_fn = int32_t(__cdecl *)(
+    const hostfxr_handle host_context_handle,
+    hostfxr_delegate_type type,
+    /*out*/ void **delegate);
+
+using hostfxr_close_fn = int32_t(__cdecl *)(const hostfxr_handle host_context_handle);
+
 #endif //_COREHOST_CLI_HOSTFXR_H_
index 6baf0a3..ad279fa 100644 (file)
@@ -42,6 +42,7 @@ set(HEADERS
     ./deps_resolver.h
     ./hostpolicy_context.h
     ./hostpolicy_init.h
+    ../corehost_context_contract.h
     ../runtime_config.h
     ../json/casablanca/include/cpprest/json.h
     ../fxr/fx_ver.h
index 04f1d88..562f70b 100644 (file)
@@ -57,7 +57,7 @@ void setup_shared_store_paths(const pal::string_t& tfm, host_mode_t host_mode,co
 
 bool parse_arguments(
     const hostpolicy_init_t& init,
-    const int argc, const pal::char_t* argv[], 
+    const int argc, const pal::char_t* argv[],
     arguments_t& args)
 {
     pal::string_t managed_application_path;
@@ -116,7 +116,7 @@ bool init_arguments(
     args.additional_deps_serialized = additional_deps_serialized;
 
     args.managed_application = managed_application_path;
-    if (!pal::realpath(&args.managed_application))
+    if (!args.managed_application.empty() && !pal::realpath(&args.managed_application))
     {
         trace::error(_X("Failed to locate managed application [%s]"), args.managed_application.c_str());
         return false;
index 821f0d2..b287e51 100644 (file)
@@ -252,7 +252,7 @@ bool coreclr_property_bag_t::add(const pal::char_t *key, const pal::char_t *valu
     }
 }
 
-bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **value)
+bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **value) const
 {
     int idx = static_cast<int>(key);
     assert(0 <= idx && idx < static_cast<int>(common_property::Last));
@@ -260,7 +260,7 @@ bool coreclr_property_bag_t::try_get(common_property key, const pal::char_t **va
     return try_get(PropertyNameMapping[idx], value);
 }
 
-bool coreclr_property_bag_t::try_get(const pal::char_t *key, const pal::char_t **value)
+bool coreclr_property_bag_t::try_get(const pal::char_t *key, const pal::char_t **value) const
 {
     assert(key != nullptr && value != nullptr);
     auto iter = _properties.find(key);
@@ -280,6 +280,7 @@ void coreclr_property_bag_t::remove(const pal::char_t *key)
     if (iter == _properties.cend())
         return;
 
+    trace::verbose(_X("Removing property %s. Old value: '%s'."), key, (*iter).second.c_str());
     _properties.erase(iter);
 }
 
index 7227eac..9d4711f 100644 (file)
@@ -86,8 +86,8 @@ public:
     bool add(common_property key, const pal::char_t *value);
     bool add(const pal::char_t *key, const pal::char_t *value);
 
-    bool try_get(common_property key, const pal::char_t **value);
-    bool try_get(const pal::char_t *key, const pal::char_t **value);
+    bool try_get(common_property key, const pal::char_t **value) const;
+    bool try_get(const pal::char_t *key, const pal::char_t **value) const;
 
     void remove(const pal::char_t *key);
 
index ed25f96..42f272b 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+#include <atomic>
+#include <condition_variable>
 #include <mutex>
 #include <pal.h>
 #include "args.h"
 #include <error_codes.h>
 #include "breadcrumbs.h"
 #include <host_startup_info.h>
+#include <corehost_context_contract.h>
 #include "hostpolicy_context.h"
 
 namespace
 {
+    // Initialization information set through corehost_load. All other entry points assume this has already
+    // been set and use it to perform the requested operation. Note that this being initialized does not
+    // indicate that the runtime is loaded or that the runtime will be loaded (e.g. host commands).
     std::mutex g_init_lock;
     bool g_init_done;
     hostpolicy_init_t g_init;
 
-    std::shared_ptr<coreclr_t> g_coreclr;
+    // hostpolicy tracks the context used to load and initialize coreclr. This is the first context that
+    // is successfully created and used to load the runtime. There can only be one hostpolicy context.
+    std::mutex g_context_lock;
 
-    std::mutex g_lib_lock;
-    std::weak_ptr<coreclr_t> g_lib_coreclr;
+    // Tracks the hostpolicy context. This is the one and only hostpolicy context. It represents the information
+    // that hostpolicy will use or has already used to load and initialize coreclr. It will be set once a context
+    // is initialized and updated to hold coreclr once the runtime is loaded.
+    std::unique_ptr<hostpolicy_context_t> g_context;
 
-    int create_coreclr(const hostpolicy_context_t &context, host_mode_t mode, std::unique_ptr<coreclr_t> &coreclr)
-    {
-        // Verbose logging
-        if (trace::is_enabled())
-        {
-            context.coreclr_properties.log_properties();
-        }
-
-        std::vector<char> host_path;
-        pal::pal_clrstring(context.host_path, &host_path);
+    // Tracks whether the hostpolicy context is initializing (from start of creation of the first context
+    // to loading coreclr). It will be false before initialization starts and after it succeeds or fails.
+    // Attempts to get/create a context should block if the first context is initializing (i.e. this is true).
+    // The condition variable is used to block on and signal changes to this state.
+    std::atomic<bool> g_context_initializing(false);
+    std::condition_variable g_context_initializing_cv;
 
-        const char *app_domain_friendly_name = mode == host_mode_t::libhost ? "clr_libhost" : "clrhost";
-
-        // Create a CoreCLR instance
-        trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), context.clr_path.c_str(), context.clr_dir.c_str());
-        auto hr = coreclr_t::create(
-            context.clr_dir,
-            host_path.data(),
-            app_domain_friendly_name,
-            context.coreclr_properties,
-            coreclr);
-
-        if (!SUCCEEDED(hr))
+    int create_coreclr()
+    {
+        int rc;
         {
-            trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr);
-            return StatusCode::CoreClrInitFailure;
+            std::lock_guard<std::mutex> context_lock { g_context_lock };
+            if (g_context == nullptr)
+            {
+                trace::error(_X("Hostpolicy has not been initialized"));
+                return StatusCode::HostInvalidState;
+            }
+
+            if (g_context->coreclr != nullptr)
+            {
+                trace::error(_X("CoreClr has already been loaded"));
+                return StatusCode::HostInvalidState;
+            }
+
+            // Verbose logging
+            if (trace::is_enabled())
+                g_context->coreclr_properties.log_properties();
+
+            std::vector<char> host_path;
+            pal::pal_clrstring(g_context->host_path, &host_path);
+            const char *app_domain_friendly_name = g_context->host_mode == host_mode_t::libhost ? "clr_libhost" : "clrhost";
+
+            // Create a CoreCLR instance
+            trace::verbose(_X("CoreCLR path = '%s', CoreCLR dir = '%s'"), g_context->clr_path.c_str(), g_context->clr_dir.c_str());
+            auto hr = coreclr_t::create(
+                g_context->clr_dir,
+                host_path.data(),
+                app_domain_friendly_name,
+                g_context->coreclr_properties,
+                g_context->coreclr);
+
+            if (!SUCCEEDED(hr))
+            {
+                trace::error(_X("Failed to create CoreCLR, HRESULT: 0x%X"), hr);
+                rc = StatusCode::CoreClrInitFailure;
+            }
+            else
+            {
+                rc = StatusCode::Success;
+            }
+
+            g_context_initializing.store(false);
         }
 
-        return StatusCode::Success;
+        g_context_initializing_cv.notify_all();
+        return rc;
     }
 
     int create_hostpolicy_context(
         hostpolicy_init_t &hostpolicy_init,
         const arguments_t &args,
-        bool breadcrumbs_enabled,
-        std::shared_ptr<hostpolicy_context_t> &context)
+        bool breadcrumbs_enabled)
     {
+        {
+            std::unique_lock<std::mutex> lock{ g_context_lock };
+            g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); });
+
+            const hostpolicy_context_t *existing_context = g_context.get();
+            if (existing_context != nullptr)
+            {
+                trace::info(_X("Host context has already been initialized"));
+                assert(existing_context->coreclr != nullptr);
+                return StatusCode::CoreHostAlreadyInitialized;
+            }
+
+            g_context_initializing.store(true);
+        }
+
+        g_context_initializing_cv.notify_all();
 
         std::unique_ptr<hostpolicy_context_t> context_local(new hostpolicy_context_t());
         int rc = context_local->initialize(hostpolicy_init, args, breadcrumbs_enabled);
         if (rc != StatusCode::Success)
-            return rc;
-
-        context = std::move(context_local);
+        {
+            {
+                std::lock_guard<std::mutex> lock{ g_context_lock };
+                g_context_initializing.store(false);
+            }
 
-        return StatusCode::Success;
-    }
-}
+            g_context_initializing_cv.notify_all();
+            return rc;
+        }
 
-int get_or_create_coreclr(
-    hostpolicy_init_t &hostpolicy_init,
-    const arguments_t &args,
-    host_mode_t mode,
-    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
+        {
+            std::lock_guard<std::mutex> lock{ g_context_lock };
+            g_context.reset(context_local.release());
+        }
 
-        trace::info(_X("Using existing CoreClr instance"));
         return StatusCode::Success;
     }
 
+    const hostpolicy_context_t* get_hostpolicy_context(bool require_runtime)
     {
-        std::lock_guard<std::mutex> lock{ g_lib_lock };
-        coreclr = g_lib_coreclr.lock();
-        if (coreclr != nullptr)
+        std::lock_guard<std::mutex> lock{ g_context_lock };
+        const hostpolicy_context_t *existing_context = g_context.get();
+        if (existing_context == nullptr)
         {
-            trace::info(_X("Using existing CoreClr instance"));
-            return StatusCode::Success;
+            trace::error(_X("Hostpolicy context has not been created"));
+            return nullptr;
         }
 
-        hostpolicy_context_t context {};
-        int rc = context.initialize(hostpolicy_init, args, false /* enable_breadcrumbs */);
-        if (rc != StatusCode::Success)
-            return rc;
-
-        std::unique_ptr<coreclr_t> coreclr_local;
-        rc = create_coreclr(context, mode, coreclr_local);
-        if (rc != StatusCode::Success)
-            return rc;
+        if (require_runtime && existing_context->coreclr == nullptr)
+        {
+            trace::error(_X("Runtime has not been loaded and initialized"));
+            return nullptr;
+        }
 
-        assert(g_coreclr == nullptr);
-        g_coreclr = std::move(coreclr_local);
-        g_lib_coreclr = g_coreclr;
+        return existing_context;
     }
-
-    coreclr = g_coreclr;
-    return StatusCode::Success;
 }
 
 int run_host_command(
@@ -151,12 +190,13 @@ int run_host_command(
     return StatusCode::InvalidArgFailure;
 }
 
-int run_as_app(
-    const std::shared_ptr<coreclr_t> &coreclr,
+int run_app_for_context(
     const hostpolicy_context_t &context,
     int argc,
     const pal::char_t **argv)
 {
+    assert(context.coreclr != nullptr);
+
     // Initialize clr strings for arguments
     std::vector<std::vector<char>> argv_strs(argc);
     std::vector<const char*> argv_local(argc);
@@ -192,7 +232,7 @@ int run_as_app(
 
     // Execute the application
     unsigned int exit_code;
-    auto hr = g_coreclr->execute_assembly(
+    auto hr = context.coreclr->execute_assembly(
         argv_local.size(),
         argv_local.data(),
         managed_app.data(),
@@ -207,7 +247,7 @@ int run_as_app(
     trace::info(_X("Execute managed assembly exit code: 0x%X"), exit_code);
 
     // Shut down the CoreCLR
-    hr = g_coreclr->shutdown(reinterpret_cast<int*>(&exit_code));
+    hr = context.coreclr->shutdown(reinterpret_cast<int*>(&exit_code));
     if (!SUCCEEDED(hr))
     {
         trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
@@ -218,6 +258,15 @@ int run_as_app(
     // The breadcrumb destructor will join to the background thread to finish writing
 }
 
+int run_app(const int argc, const pal::char_t *argv[])
+{
+    const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ true);
+    if (context == nullptr)
+        return StatusCode::HostInvalidState;
+
+    return run_app_for_context(*context, argc, argv);
+}
+
 void trace_hostpolicy_entrypoint_invocation(const pal::string_t& entryPointName)
 {
     trace::info(_X("--- Invoked hostpolicy [commit hash: %s] [%s,%s,%s][%s] %s = {"),
@@ -264,7 +313,7 @@ SHARED_API int corehost_load(host_interface_t* init)
 }
 
 int corehost_init(
-    hostpolicy_init_t &hostpolicy_init,
+    const hostpolicy_init_t &hostpolicy_init,
     const int argc,
     const pal::char_t* argv[],
     const pal::string_t& location,
@@ -320,26 +369,16 @@ SHARED_API int corehost_main(const int argc, const pal::char_t* argv[])
     if (rc != StatusCode::Success)
         return rc;
 
-    std::shared_ptr<hostpolicy_context_t> context;
-    rc = create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */, context);
+    assert(g_context == nullptr);
+    rc = create_hostpolicy_context(g_init, args, true /* breadcrumbs_enabled */);
     if (rc != StatusCode::Success)
         return rc;
 
-    std::unique_ptr<coreclr_t> coreclr;
-    rc = create_coreclr(*context, g_init.host_mode, coreclr);
+    rc = create_coreclr();
     if (rc != StatusCode::Success)
         return rc;
 
-    assert(g_coreclr == nullptr);
-    g_coreclr = std::move(coreclr);
-
-    {
-        std::lock_guard<std::mutex> lock{ g_lib_lock };
-        g_lib_coreclr = g_coreclr;
-    }
-
-    rc = run_as_app(g_coreclr, *context, args.app_argc, args.app_argv);
-    return rc;
+    return run_app(args.app_argc, args.app_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)
@@ -382,7 +421,7 @@ SHARED_API int corehost_main_with_output_buffer(const int argc, const pal::char_
     return rc;
 }
 
-int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_t& location, arguments_t& args)
+int corehost_libhost_init(const hostpolicy_init_t &hostpolicy_init, const pal::string_t& location, arguments_t& args)
 {
     // Host info should always be valid in the delegate scenario
     assert(hostpolicy_init.host_info.is_valid(host_mode_t::libhost));
@@ -392,8 +431,16 @@ int corehost_libhost_init(hostpolicy_init_t &hostpolicy_init, const pal::string_
 
 namespace
 {
-    int get_coreclr_delegate(const std::shared_ptr<coreclr_t> &coreclr, coreclr_delegate_type type, void** delegate)
+    int get_delegate(coreclr_delegate_type type, void **delegate)
     {
+        if (delegate == nullptr)
+            return StatusCode::InvalidArgFailure;
+
+        const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ true);
+        if (context == nullptr)
+            return StatusCode::HostInvalidState;
+
+        coreclr_t *coreclr = context->coreclr.get();
         switch (type)
         {
         case coreclr_delegate_type::com_activation:
@@ -418,26 +465,208 @@ namespace
             return StatusCode::LibHostInvalidArgs;
         }
     }
+
+    int get_property(const pal::char_t *key, const pal::char_t **value)
+    {
+        if (key == nullptr)
+            return StatusCode::InvalidArgFailure;
+
+        const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ false);
+        if (context == nullptr)
+            return StatusCode::HostInvalidState;
+
+        if (!context->coreclr_properties.try_get(key, value))
+            return StatusCode::HostPropertyNotFound;
+
+        return StatusCode::Success;
+    }
+
+    int set_property(const pal::char_t *key, const pal::char_t *value)
+    {
+        if (key == nullptr)
+            return StatusCode::InvalidArgFailure;
+
+        std::lock_guard<std::mutex> lock{ g_context_lock };
+        if (g_context == nullptr || g_context->coreclr != nullptr)
+        {
+            trace::error(_X("Setting properties is only allowed before runtime has been loaded and initialized"));
+            return HostInvalidState;
+        }
+
+        if (value != nullptr)
+        {
+            g_context->coreclr_properties.add(key, value);
+        }
+        else
+        {
+            g_context->coreclr_properties.remove(key);
+        }
+
+        return StatusCode::Success;
+    }
+
+    int get_properties(size_t * count, const pal::char_t **keys, const pal::char_t **values)
+    {
+        if (count == nullptr)
+            return StatusCode::InvalidArgFailure;
+
+        const hostpolicy_context_t *context = get_hostpolicy_context(/*require_runtime*/ false);
+        if (context == nullptr)
+            return StatusCode::HostInvalidState;
+
+        size_t actualCount = context->coreclr_properties.count();
+        size_t input_count = *count;
+        *count = actualCount;
+        if (input_count < actualCount || keys == nullptr || values == nullptr)
+            return StatusCode::HostApiBufferTooSmall;
+
+        int index = 0;
+        std::function<void (const pal::string_t &,const pal::string_t &)> callback = [&] (const pal::string_t& key, const pal::string_t& value)
+        {
+            keys[index] = key.data();
+            values[index] = value.data();
+            ++index;
+        };
+        context->coreclr_properties.enumerate(callback);
+
+        return StatusCode::Success;
+    }
 }
 
-SHARED_API int corehost_get_coreclr_delegate(coreclr_delegate_type type, void** delegate)
+// Initializes hostpolicy. Calculates everything required to start the runtime and creates a context to track
+// that information
+//
+// Parameters:
+//    init_request
+//      struct containing information about the initialization request. If hostpolicy is not yet initialized,
+//      this is expected to be nullptr. If hostpolicy is already initialized, this should not be nullptr and
+//      this function will use the struct to check for compatibility with the way in which hostpolicy was
+//      previously initialized.
+//    options
+//      initialization options
+//    context_contract
+//      [out] if initialization is successful, populated with a contract for performing operations on hostpolicy
+//
+// Return value:
+//    Success                     - Initialization was succesful
+//    CoreHostAlreadyInitialized  - Request is compatible with already initialized hostpolicy
+// [TODO]
+//    CoreHostDifferentProperties - Request has runtime properties that differ from already initialized hostpolicy
+//
+// This function does not load the runtime
+//
+// If a previous request to initialize hostpolicy was made, but the runtime was not yet loaded, this function will
+// block until the runtime is loaded.
+//
+// This function assumes corehost_load has already been called. It uses the init information set through that
+// call - not the struct passed into this function - to create a context.
+//
+SHARED_API int __cdecl corehost_initialize(const corehost_initialize_request_t *init_request, int32_t options, /*out*/ corehost_context_contract *context_contract)
 {
-    arguments_t args;
+    if (context_contract == nullptr)
+        return StatusCode::InvalidArgFailure;
 
-    int rc = corehost_libhost_init(g_init, _X("corehost_get_coreclr_delegate"), args);
-    if (rc != StatusCode::Success)
-        return rc;
+    bool wait_for_initialized = (options & intialization_options_t::wait_for_initialized) != 0;
 
-    std::shared_ptr<coreclr_t> coreclr;
-    rc = get_or_create_coreclr(g_init, args, g_init.host_mode, coreclr);
+    {
+        std::unique_lock<std::mutex> lock { g_context_lock };
+        bool already_initializing = g_context_initializing.load();
+        bool already_initialized = g_context.get() != nullptr;
+
+        if (wait_for_initialized)
+        {
+            trace::verbose(_X("Initialization option to wait for initialize request is set"));
+            if (init_request == nullptr)
+            {
+                trace::error(_X("Initialization request is expected to be non-null when waiting for initialize request option is set"));
+                return StatusCode::InvalidArgFailure;
+            }
+
+            // If we are not already initializing or done initializing, wait until another context initialization has started
+            if (!already_initialized && !already_initializing)
+            {
+                trace::info(_X("Waiting for another request to initialize hostpolicy"));
+                g_context_initializing_cv.wait(lock, [&] { return g_context_initializing.load(); });
+            }
+        }
+        else
+        {
+            if (init_request != nullptr && !already_initialized && !already_initializing)
+            {
+                trace::error(_X("Initialization request is expected to be null for the first initialization request"));
+                return StatusCode::InvalidArgFailure;
+            }
+
+            if (init_request == nullptr && (already_initializing || already_initialized))
+            {
+                trace::error(_X("Initialization request is expected to be non-null for requests other than the first one"));
+                return StatusCode::InvalidArgFailure;
+            }
+        }
+    }
+
+    // Trace entry point information and initialize args using previously set init information.
+    // This function does not modify any global state.
+    arguments_t args;
+    int rc = corehost_libhost_init(g_init, _X("corehost_initialize"), args);
     if (rc != StatusCode::Success)
         return rc;
 
-    return get_coreclr_delegate(coreclr, type, delegate);
+    if (wait_for_initialized)
+    {
+        // Wait for context initialization to complete
+        std::unique_lock<std::mutex> lock{ g_context_lock };
+        g_context_initializing_cv.wait(lock, [] { return !g_context_initializing.load(); });
+
+        const hostpolicy_context_t *existing_context = g_context.get();
+        if (existing_context == nullptr || existing_context->coreclr == nullptr)
+        {
+            trace::info(_X("Option to wait for initialize request was set, but that request did not result in initialization"));
+            return StatusCode::HostInvalidState;
+        }
+
+        rc = StatusCode::CoreHostAlreadyInitialized;
+    }
+    else
+    {
+        rc = create_hostpolicy_context(g_init, args, g_init.host_mode != host_mode_t::libhost);
+        if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized)
+            return rc;
+    }
+
+    if (rc == StatusCode::CoreHostAlreadyInitialized)
+    {
+        // [TODO] Compare the current context with this request (properties)
+    }
+
+    context_contract->version = sizeof(corehost_context_contract);
+    context_contract->get_property_value = get_property;
+    context_contract->set_property_value = set_property;
+    context_contract->get_properties = get_properties;
+    context_contract->load_runtime = create_coreclr;
+    context_contract->run_app = run_app;
+    context_contract->get_runtime_delegate = get_delegate;
+
+    return rc;
 }
 
 SHARED_API int corehost_unload()
 {
+    {
+        std::lock_guard<std::mutex> lock{ g_context_lock };
+        if (g_context != nullptr && g_context->coreclr != nullptr)
+            return StatusCode::Success;
+
+        // Allow re-initializing if runtime has not been loaded
+        g_context.reset();
+        g_context_initializing.store(false);
+    }
+
+    g_context_initializing_cv.notify_all();
+
+    std::lock_guard<std::mutex> init_lock{ g_init_lock };
+    g_init_done = false;
+
     return StatusCode::Success;
 }
 
index 8385dbb..2bb320f 100644 (file)
@@ -20,6 +20,7 @@ namespace
 int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs)
 {
     application = args.managed_application;
+    host_mode = hostpolicy_init.host_mode;
     host_path = args.host_path;
     breadcrumbs_enabled = enable_breadcrumbs;
 
index 2508f02..26840fb 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "args.h"
 #include "coreclr.h"
+#include <corehost_context_contract.h>
 #include "hostpolicy_init.h"
 
 struct hostpolicy_context_t
@@ -17,6 +18,7 @@ public:
     pal::string_t application;
     pal::string_t clr_dir;
     pal::string_t clr_path;
+    host_mode_t host_mode;
     pal::string_t host_path;
 
     bool breadcrumbs_enabled;
@@ -24,6 +26,8 @@ public:
 
     coreclr_property_bag_t coreclr_properties;
 
+    std::unique_ptr<coreclr_t> coreclr;
+
     int initialize(hostpolicy_init_t &hostpolicy_init, const arguments_t &args, bool enable_breadcrumbs);
 };
 
index d2a5728..c4f7002 100644 (file)
@@ -25,13 +25,19 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m
 {
     return load_fxr_and_get_delegate(
         hostfxr_delegate_type::load_in_memory_assembly,
-        [handle](const pal::string_t& host_path, pal::string_t* app_path_out)
+        [handle](const pal::string_t& host_path, pal::string_t* config_path_out)
         {
-            if (!pal::get_module_path(handle, app_path_out))
+            pal::string_t mod_path;
+            if (!pal::get_module_path(handle, &mod_path))
             {
                 trace::error(_X("Failed to resolve full path of the current mixed-mode module [%s]"), host_path.c_str());
                 return StatusCode::LibHostCurExeFindFailure;
             }
+
+            pal::string_t config_path_local { strip_file_ext(mod_path) };
+            config_path_local.append(_X(".runtimeconfig.json"));
+            *config_path_out = std::move(config_path_local);
+
             return StatusCode::Success;
         },
         delegate
index 3384a68..dc8b567 100644 (file)
@@ -3,12 +3,29 @@
 // See the LICENSE file in the project root for more information.
 
 #include "mockcoreclr.h"
+#include <chrono>
 #include <iostream>
+#include <thread>
 #include "trace.h"
 
-#define MockLog(string) std::cout << "mock " << string << std::endl;
-#define MockLogArg(arg) std::cout << "mock " << #arg << ":" << arg << std::endl;
-#define MockLogEntry(dict, key, value) std::cout << "mock " << dict << "[" << key << "] = " << value << std::endl;
+#define MockLog(string)\
+{\
+    std::stringstream ss;\
+    ss << "mock " << string << std::endl;\
+    std::cout << ss.str();\
+}
+#define MockLogArg(arg)\
+{\
+    std::stringstream ss;\
+    ss << "mock " << #arg << ":" << arg << std::endl;\
+    std::cout << ss.str();\
+}
+#define MockLogEntry(dict, key, value)\
+{\
+    std::stringstream ss;\
+    ss << "mock " << dict << "[" << key << "] = " << value << std::endl;\
+    std::cout << ss.str();\
+}
 
 SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_initialize(
     const char* exePath,
@@ -81,6 +98,15 @@ SHARED_API pal::hresult_t STDMETHODCALLTYPE coreclr_execute_assembly(
         MockLogEntry("argv", i, argv[i]);
     }
 
+    pal::string_t path;
+    if (pal::getenv(_X("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY"), &path))
+    {
+        while (pal::file_exists(path))
+        {
+            std::this_thread::sleep_for(std::chrono::milliseconds(100));
+        }
+    }
+
     if (exitCode != nullptr)
     {
         *exitCode = 0;
index ab6abb6..68508be 100644 (file)
@@ -18,6 +18,7 @@ endif()
 include_directories(${CMAKE_CURRENT_LIST_DIR}/../../nethost)
 
 set(SOURCES
+    ./host_context_test.cpp
     ./nativehost.cpp
 )
 
diff --git a/src/installer/corehost/cli/test/nativehost/host_context_test.cpp b/src/installer/corehost/cli/test/nativehost/host_context_test.cpp
new file mode 100644 (file)
index 0000000..093eac7
--- /dev/null
@@ -0,0 +1,385 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include <iostream>
+#include <pal.h>
+#include <error_codes.h>
+#include <future>
+#include <hostfxr.h>
+#include "host_context_test.h"
+
+namespace
+{
+    const pal::char_t *app_log_prefix = _X("[APP] ");
+    const pal::char_t *config_log_prefix = _X("[CONFIG] ");
+    const pal::char_t *secondary_log_prefix = _X("[SECONDARY] ");
+
+    class hostfxr_exports
+    {
+    public:
+        hostfxr_initialize_for_app_fn init_app;
+        hostfxr_run_app_fn run_app;
+
+        hostfxr_initialize_for_runtime_config_fn init_config;
+        hostfxr_get_runtime_delegate_fn get_delegate;
+
+        hostfxr_get_runtime_property_value_fn get_prop_value;
+        hostfxr_set_runtime_property_value_fn set_prop_value;
+        hostfxr_get_runtime_properties_fn get_properties;
+
+        hostfxr_close_fn close;
+
+    public:
+        hostfxr_exports(const pal::string_t &hostfxr_path)
+        {
+            if (!pal::load_library(&hostfxr_path, &_dll))
+            {
+                std::cout << "Load library of hostfxr failed" << std::endl;
+                throw StatusCode::CoreHostLibLoadFailure;
+            }
+
+            init_app = (hostfxr_initialize_for_app_fn)pal::get_symbol(_dll, "hostfxr_initialize_for_app");
+            run_app = (hostfxr_run_app_fn)pal::get_symbol(_dll, "hostfxr_run_app");
+
+            init_config = (hostfxr_initialize_for_runtime_config_fn)pal::get_symbol(_dll, "hostfxr_initialize_for_runtime_config");
+            get_delegate = (hostfxr_get_runtime_delegate_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_delegate");
+
+            get_prop_value = (hostfxr_get_runtime_property_value_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_property_value");
+            set_prop_value = (hostfxr_set_runtime_property_value_fn)pal::get_symbol(_dll, "hostfxr_set_runtime_property_value");
+            get_properties = (hostfxr_get_runtime_properties_fn)pal::get_symbol(_dll, "hostfxr_get_runtime_properties");
+
+            close = (hostfxr_close_fn)pal::get_symbol(_dll, "hostfxr_close");
+
+            if (init_app == nullptr || run_app == nullptr
+                || init_config == nullptr || get_delegate == nullptr
+                || get_prop_value == nullptr || set_prop_value == nullptr
+                || get_properties == nullptr || close == nullptr)
+            {
+                std::cout << "Failed to get hostfxr entry points" << std::endl;
+                throw StatusCode::CoreHostEntryPointFailure;
+            }
+        }
+
+        ~hostfxr_exports()
+        {
+            pal::unload_library(_dll);
+        }
+
+    private:
+        pal::dll_t _dll;
+    };
+
+    void get_property_value(
+        const hostfxr_exports &hostfxr,
+        hostfxr_handle handle,
+        int property_count,
+        const pal::char_t *property_keys[],
+        const pal::char_t *log_prefix,
+        pal::stringstream_t &test_output)
+    {
+        for (int i = 0; i < property_count; ++i)
+        {
+            const pal::char_t *key = property_keys[i];
+            const pal::char_t *value;
+            int rc = hostfxr.get_prop_value(handle, key, &value);
+            if (rc == StatusCode::Success)
+            {
+                test_output << log_prefix << _X("hostfxr_get_runtime_property_value succeeded for property: ")
+                    << key << _X("=") << value << std::endl;
+            }
+            else
+            {
+                test_output << log_prefix << _X("hostfxr_get_runtime_property_value failed for property: ") << key
+                    << _X(" - ") << std::hex << std::showbase << rc << std::endl;
+            }
+        }
+    }
+
+    void set_property_value(
+        const hostfxr_exports &hostfxr,
+        hostfxr_handle handle,
+        int property_count,
+        const pal::char_t *property_keys[],
+        bool remove,
+        const pal::char_t *log_prefix,
+        pal::stringstream_t &test_output)
+    {
+        for (int i = 0; i < property_count; ++i)
+        {
+            const pal::char_t *key = property_keys[i];
+            const pal::char_t *value = remove ? nullptr : _X("VALUE_FROM_HOST");
+            int rc = hostfxr.set_prop_value(handle, key, value);
+            if (rc == StatusCode::Success)
+            {
+                test_output << log_prefix << _X("hostfxr_set_runtime_property_value succeeded for property: ") << key << std::endl;
+            }
+            else
+            {
+                test_output << log_prefix << _X("hostfxr_set_runtime_property_value failed for property: ") << key
+                    << _X(" - ") << std::hex << std::showbase << rc << std::endl;
+            }
+        }
+    }
+
+    void get_properties(
+        const hostfxr_exports &hostfxr,
+        hostfxr_handle handle,
+        const pal::char_t *log_prefix,
+        pal::stringstream_t &test_output)
+    {
+        size_t count = 0;
+        std::vector<const pal::char_t*> keys;
+        std::vector<const pal::char_t*> values;
+        int rc = hostfxr.get_properties(handle, &count, nullptr, nullptr);
+        if (static_cast<StatusCode>(rc) == StatusCode::HostApiBufferTooSmall)
+        {
+            keys.resize(count);
+            values.resize(count);
+               rc = hostfxr.get_properties(handle, &count, keys.data(), values.data());
+        }
+
+        if (rc != StatusCode::Success)
+        {
+            test_output << log_prefix << _X("hostfxr_get_runtime_properties failed - ")
+                << std::hex << std::showbase << rc << std::endl;
+            return;
+        }
+
+        test_output << log_prefix << _X("hostfxr_get_runtime_properties succeeded.") << std::endl;
+        for (size_t i = 0; i < keys.size(); ++i)
+        {
+            test_output << log_prefix << _X("hostfxr_get_runtime_properties: ")
+                << keys[i] << _X("=") << values[i] << std::endl;
+        }
+    }
+
+    void inspect_modify_properties(
+        host_context_test::check_properties scenario,
+        const hostfxr_exports &hostfxr,
+        hostfxr_handle handle,
+        int key_count,
+        const pal::char_t *keys[],
+        const pal::char_t *log_prefix,
+        pal::stringstream_t &test_output)
+    {
+        switch (scenario)
+        {
+            case host_context_test::check_properties::get:
+                get_property_value(hostfxr, handle, key_count, keys, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::set:
+                set_property_value(hostfxr, handle, key_count, keys, false /*remove*/, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::remove:
+                set_property_value(hostfxr, handle, key_count, keys, true /*remove*/, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::get_all:
+                get_properties(hostfxr, handle, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::get_active:
+                get_property_value(hostfxr, nullptr, key_count, keys, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::get_all_active:
+                get_properties(hostfxr, nullptr, log_prefix, test_output);
+                break;
+            case host_context_test::check_properties::none:
+            default:
+                break;
+        }
+    }
+
+    bool config_test(
+        const hostfxr_exports &hostfxr,
+        host_context_test::check_properties check_properties,
+        const pal::char_t *config_path,
+        int argc,
+        const pal::char_t *argv[],
+        const pal::char_t *log_prefix,
+        pal::stringstream_t &test_output)
+    {
+        hostfxr_handle handle;
+        int rc = hostfxr.init_config(config_path, nullptr, &handle);
+        if (rc != StatusCode::Success && rc != StatusCode::CoreHostAlreadyInitialized)
+        {
+            test_output << log_prefix << _X("hostfxr_initialize_for_runtime_config failed: ") << std::hex << std::showbase << rc << std::endl;
+            return false;
+        }
+
+        inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, log_prefix, test_output);
+
+        void *delegate;
+        rc = hostfxr.get_delegate(handle, hostfxr_delegate_type::com_activation, &delegate);
+        if (rc != StatusCode::Success)
+            test_output << log_prefix << _X("hostfxr_get_runtime_delegate failed: ") << std::hex << std::showbase << rc << std::endl;
+
+        int rcClose = hostfxr.close(handle);
+        if (rcClose != StatusCode::Success)
+            test_output << log_prefix << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc  << std::endl;
+
+        return rc == StatusCode::Success && rcClose == StatusCode::Success;
+    }
+}
+
+host_context_test::check_properties host_context_test::check_properties_from_string(const pal::char_t *str)
+{
+    if (pal::strcmp(str, _X("get")) == 0)
+    {
+        return host_context_test::check_properties::get;
+    }
+    else if (pal::strcmp(str, _X("set")) == 0)
+    {
+        return host_context_test::check_properties::set;
+    }
+    else if (pal::strcmp(str, _X("remove")) == 0)
+    {
+        return host_context_test::check_properties::remove;
+    }
+    else if (pal::strcmp(str, _X("get_all")) == 0)
+    {
+        return host_context_test::check_properties::get_all;
+    }
+    else if (pal::strcmp(str, _X("get_active")) == 0)
+    {
+        return host_context_test::check_properties::get_active;
+    }
+    else if (pal::strcmp(str, _X("get_all_active")) == 0)
+    {
+        return host_context_test::check_properties::get_all_active;
+    }
+
+    return host_context_test::check_properties::none;
+}
+
+bool host_context_test::app(
+    check_properties check_properties,
+    const pal::string_t &hostfxr_path,
+    const pal::char_t *app_path,
+    int argc,
+    const pal::char_t *argv[],
+    pal::stringstream_t &test_output)
+{
+    hostfxr_exports hostfxr { hostfxr_path };
+
+    hostfxr_handle handle;
+    int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle);
+    if (rc != StatusCode::Success)
+    {
+        test_output << _X("hostfxr_initialize_for_app failed: ") << std::hex << std::showbase << rc << std::endl;
+        return false;
+    }
+
+    inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix, test_output);
+
+    rc = hostfxr.run_app(handle);
+    if (rc != StatusCode::Success)
+        test_output << _X("hostfxr_run_app failed: ") << std::hex << std::showbase << rc << std::endl;
+
+    int rcClose = hostfxr.close(handle);
+    if (rcClose != StatusCode::Success)
+        test_output << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc  << std::endl;
+
+    return rc == StatusCode::Success && rcClose == StatusCode::Success;
+}
+
+bool host_context_test::config(
+    check_properties check_properties,
+    const pal::string_t &hostfxr_path,
+    const pal::char_t *config_path,
+    int argc,
+    const pal::char_t *argv[],
+    pal::stringstream_t &test_output)
+{
+    hostfxr_exports hostfxr { hostfxr_path };
+
+    return config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix, test_output);
+}
+
+bool host_context_test::config_multiple(
+    check_properties check_properties,
+    const pal::string_t &hostfxr_path,
+    const pal::char_t *config_path,
+    const pal::char_t *secondary_config_path,
+    int argc,
+    const pal::char_t *argv[],
+    pal::stringstream_t &test_output)
+{
+    hostfxr_exports hostfxr { hostfxr_path };
+
+    if (!config_test(hostfxr, check_properties, config_path, argc, argv, config_log_prefix, test_output))
+        return false;
+
+    return config_test(hostfxr, check_properties, secondary_config_path, argc, argv, secondary_log_prefix, test_output);
+}
+
+namespace
+{
+    class block_mock_execute_assembly
+    {
+    public:
+        block_mock_execute_assembly()
+        {
+            if (pal::getenv(_X("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY"), &_path))
+                pal::touch_file(_path);
+        }
+
+        ~block_mock_execute_assembly()
+        {
+            unblock();
+        }
+
+        void unblock()
+        {
+            if (_path.empty())
+                return;
+
+            pal::remove(_path.c_str());
+            _path.clear();
+        }
+
+    private:
+        pal::string_t _path;
+    };
+}
+
+bool host_context_test::mixed(
+    check_properties check_properties,
+    const pal::string_t &hostfxr_path,
+    const pal::char_t *app_path,
+    const pal::char_t *config_path,
+    int argc,
+    const pal::char_t *argv[],
+    pal::stringstream_t &test_output)
+{
+    hostfxr_exports hostfxr { hostfxr_path };
+
+    hostfxr_handle handle;
+    int rc = hostfxr.init_app(argc, argv, app_path, nullptr, &handle);
+    if (rc != StatusCode::Success)
+    {
+        test_output << _X("hostfxr_initialize_for_app failed: ") << std::hex << std::showbase << rc << std::endl;
+        return false;
+    }
+
+    inspect_modify_properties(check_properties, hostfxr, handle, argc, argv, app_log_prefix, test_output);
+
+    block_mock_execute_assembly block_mock;
+
+    pal::stringstream_t run_app_output;
+    auto run_app = [&]{
+        int rc = hostfxr.run_app(handle);
+        if (rc != StatusCode::Success)
+            run_app_output << _X("hostfxr_run_app failed: ") << std::hex << std::showbase << rc << std::endl;
+
+        int rcClose = hostfxr.close(handle);
+        if (rcClose != StatusCode::Success)
+            run_app_output << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc  << std::endl;
+    };
+    std::thread app_start = std::thread(run_app);
+
+    bool success = config_test(hostfxr, check_properties, config_path, argc, argv, secondary_log_prefix, test_output);
+    block_mock.unblock();
+    app_start.join();
+    test_output << run_app_output.str();
+    return success;
+}
\ No newline at end of file
diff --git a/src/installer/corehost/cli/test/nativehost/host_context_test.h b/src/installer/corehost/cli/test/nativehost/host_context_test.h
new file mode 100644 (file)
index 0000000..070cb57
--- /dev/null
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include <iostream>
+#include <pal.h>
+#include <error_codes.h>
+#include <hostfxr.h>
+
+namespace host_context_test
+{
+    enum check_properties
+    {
+        none,
+        get,
+        set,
+        remove,
+        get_all,
+        get_active,
+        get_all_active
+    };
+
+    check_properties check_properties_from_string(const pal::char_t *str);
+
+    bool app(
+        check_properties scenario,
+        const pal::string_t &hostfxr_path,
+        const pal::char_t *app_path,
+        int argc,
+        const pal::char_t *argv[],
+        pal::stringstream_t &test_output);
+    bool config(
+        check_properties scenario,
+        const pal::string_t &hostfxr_path,
+        const pal::char_t *config_path,
+        int argc,
+        const pal::char_t *argv[],
+        pal::stringstream_t &test_output);
+    bool config_multiple(
+        check_properties scenario,
+        const pal::string_t &hostfxr_path,
+        const pal::char_t *config_path,
+        const pal::char_t *secondary_config_path,
+        int argc,
+        const pal::char_t *argv[],
+        pal::stringstream_t &test_output);
+    bool mixed(
+        check_properties scenario,
+        const pal::string_t &hostfxr_path,
+        const pal::char_t *app_path,
+        const pal::char_t *config_path,
+        int argc,
+        const pal::char_t *argv[],
+        pal::stringstream_t &test_output);
+}
\ No newline at end of file
index e81367e..bb21fcb 100644 (file)
@@ -7,6 +7,8 @@
 #include <error_codes.h>
 #include <nethost.h>
 #include "comhost_test.h"
+#include <hostfxr.h>
+#include "host_context_test.h"
 
 namespace
 {
@@ -80,6 +82,73 @@ int main(const int argc, const pal::char_t *argv[])
             return EXIT_FAILURE;
         }
     }
+    else if (pal::strcmp(command, _X("host_context")) == 0)
+    {
+        // args: ... <scenario> <check_properties> <hostfxr_path> <app_or_config_path> [<remaining_args>]
+        const int min_argc = 6;
+        if (argc < min_argc)
+        {
+            std::cerr << "Invalid arguments" << std::endl;
+            return -1;
+        }
+
+        const pal::char_t *scenario = argv[2];
+        const pal::char_t *check_properties_str = argv[3];
+        const pal::string_t hostfxr_path = argv[4];
+        const pal::char_t *app_or_config_path = argv[5];
+
+        // Remaining args used as property names to get/set as well as arguments for the app
+        int remaining_argc = argc - min_argc;
+        const pal::char_t **remaining_argv = nullptr;
+        if (argc > min_argc)
+            remaining_argv = &argv[min_argc];
+
+        auto check_properties = host_context_test::check_properties_from_string(check_properties_str);
+
+        pal::stringstream_t test_output;
+        bool success = false;
+        if (pal::strcmp(scenario, _X("app")) == 0)
+        {
+            success = host_context_test::app(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv, test_output);
+        }
+        else if (pal::strcmp(scenario, _X("config")) == 0)
+        {
+            success = host_context_test::config(check_properties, hostfxr_path, app_or_config_path, remaining_argc, remaining_argv, test_output);
+        }
+        else if (pal::strcmp(scenario, _X("config_multiple")) == 0)
+        {
+            // args: ... <scenario> <check_properties> <hostfxr_path> <config_path> <secondary_config_path>
+            if (argc < min_argc + 1)
+            {
+                std::cerr << "Invalid arguments" << std::endl;
+                return -1;
+            }
+
+            const pal::char_t *secondary_config_path = argv[6];
+            --remaining_argc;
+            ++remaining_argv;
+
+            success = host_context_test::config_multiple(check_properties, hostfxr_path, app_or_config_path, secondary_config_path, remaining_argc, remaining_argv, test_output);
+        }
+        else if (pal::strcmp(scenario, _X("mixed")) == 0)
+        {
+            // args: ... <scenario> <check_properties> <hostfxr_path> <app_path> <config_path>
+            if (argc < min_argc + 1)
+            {
+                std::cerr << "Invalid arguments" << std::endl;
+                return -1;
+            }
+
+            const pal::char_t *config_path = argv[6];
+            --remaining_argc;
+            ++remaining_argv;
+
+            success = host_context_test::mixed(check_properties, hostfxr_path, app_or_config_path, config_path, remaining_argc, remaining_argv, test_output);
+        }
+
+        std::cout << tostr(test_output.str()).data() << std::endl;
+        return success ? EXIT_SUCCESS : EXIT_FAILURE;
+    }
 #if defined(_WIN32)
     else if (pal::strcmp(command, _X("comhost")) == 0)
     {
index d57f04d..4c5a2f7 100644 (file)
@@ -35,21 +35,23 @@ namespace
     {
         return load_fxr_and_get_delegate(
             hostfxr_delegate_type::winrt_activation,
-            [](const pal::string_t& host_path, pal::string_t* app_path_out)
+            [app_path](const pal::string_t& host_path, pal::string_t* config_path_out)
             {
-                pal::string_t app_path_local{ host_path };
-
-                // Change the extension to get the 'app'
-                size_t idx = app_path_local.rfind(_X(".dll"));
+                // Change the extension to get the 'app' and config
+                size_t idx = host_path.rfind(_X(".dll"));
                 assert(idx != pal::string_t::npos);
+
+                pal::string_t app_path_local{ host_path };
                 app_path_local.replace(app_path_local.begin() + idx, app_path_local.end(), _X(".winmd"));
+                *app_path = std::move(app_path_local);
 
-                *app_path_out = std::move(app_path_local);
+                pal::string_t config_path_local { host_path };
+                config_path_local.replace(config_path_local.begin() + idx, config_path_local.end(), _X(".runtimeconfig.json"));
+                *config_path_out = std::move(config_path_local);
 
                 return StatusCode::Success;
             },
-            delegate,
-            app_path
+            delegate
         );
     }
 }
index 8417160..2ea3fbc 100644 (file)
@@ -614,7 +614,12 @@ bool pal::get_own_executable_path(pal::string_t* recv)
 
 bool pal::get_own_module_path(string_t* recv)
 {
-    return false;
+    Dl_info info;
+    if (dladdr((void *)&pal::get_own_module_path, &info) == 0)
+        return false;
+
+    recv->assign(info.dli_fname);
+    return true;
 }
 
 bool pal::get_module_path(dll_t module, string_t* recv)
index beddb4d..c937c06 100644 (file)
@@ -270,7 +270,7 @@ bool skip_utf8_bom(pal::istream_t* stream)
     unsigned char bytes[3];
     stream->read(reinterpret_cast<char*>(bytes), 3);
     if ((stream->gcount() < 3) ||
-            (bytes[1] != 0xBB) || 
+            (bytes[1] != 0xBB) ||
             (bytes[2] != 0xBF))
     {
         // Reset to 0 if returning false.
@@ -451,3 +451,15 @@ void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& na
 
     trace::verbose(_X("Runtime config is cfg=%s dev=%s"), json_path.c_str(), dev_json_path.c_str());
 }
+
+pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path)
+{
+    // If coreclr exists next to hostfxr, assume everything is local (e.g. self-contained)
+    pal::string_t fxr_dir = get_directory(fxr_path);
+    if (coreclr_exists_in_dir(fxr_dir))
+        return fxr_dir;
+
+    // Path to hostfxr is: <dotnet_root>/host/fxr/<version>/<hostfxr_file>
+    pal::string_t fxr_root = get_directory(fxr_dir);
+    return get_directory(get_directory(fxr_root));
+}
index c555a06..0d12cef 100644 (file)
@@ -65,6 +65,7 @@ bool try_stou(const pal::string_t& str, unsigned* num);
 pal::string_t get_dotnet_root_env_var_name();
 pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal::string_t& app);
 void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg);
+pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path);
 
 // Helper class to make it easy to propagate error writer to the hostpolicy
 class propagate_error_writer_t
index e86e651..1ef23bc 100644 (file)
@@ -6,7 +6,11 @@
 #define __ERROR_CODES_H__
 enum StatusCode
 {
+    // Success
     Success                     = 0,
+    CoreHostAlreadyInitialized  = 0x00000001,
+
+    // Failure
     InvalidArgFailure           = 0x80008081,
     CoreHostLibLoadFailure      = 0x80008082,
     CoreHostLibMissingFailure   = 0x80008083,
@@ -40,5 +44,8 @@ enum StatusCode
     BundleExtractionFailure     = 0x8000809f,
     BundleExtractionIOError     = 0x800080a0,
     LibHostDuplicateProperty    = 0x800080a1,
+    HostApiUnsupportedVersion   = 0x800080a2,
+    HostInvalidState            = 0x800080a3,
+    HostPropertyNotFound        = 0x800080a4,
 };
 #endif // __ERROR_CODES_H__
diff --git a/src/installer/test/HostActivationTests/NativeHosting/HostContext.cs b/src/installer/test/HostActivationTests/NativeHosting/HostContext.cs
new file mode 100644 (file)
index 0000000..f8a5c24
--- /dev/null
@@ -0,0 +1,447 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.DotNet.Cli.Build.Framework;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Xunit;
+
+namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
+{
+    public class HostContext : IClassFixture<HostContext.SharedTestState>
+    {
+        public class Scenario
+        {
+            public const string App = "app";
+            public const string Config = "config";
+            public const string ConfigMultiple = "config_multiple";
+            public const string Mixed = "mixed";
+        }
+
+        public class CheckProperties
+        {
+            public const string None = "none";
+            public const string Get = "get";
+            public const string Set = "set";
+            public const string Remove = "remove";
+            public const string GetAll = "get_all";
+            public const string GetActive = "get_active";
+            public const string GetAllActive = "get_all_active";
+        }
+
+        public class LogPrefix
+        {
+            public const string App = "[APP] ";
+            public const string Config = "[CONFIG] ";
+            public const string Secondary = "[SECONDARY] ";
+        }
+
+        private const string HostContextArg = "host_context";
+        private const string PropertyValueFromHost = "VALUE_FROM_HOST";
+
+        private const int InvalidArgFailure = unchecked((int)0x80008081);
+        private const int HostInvalidState = unchecked((int)0x800080a3);
+        private const int HostPropertyNotFound = unchecked((int)0x800080a4);
+
+        private readonly SharedTestState sharedState;
+
+        public HostContext(SharedTestState sharedTestState)
+        {
+            sharedState = sharedTestState;
+        }
+
+        [Theory]
+        [InlineData(CheckProperties.None)]
+        [InlineData(CheckProperties.Get)]
+        [InlineData(CheckProperties.Set)]
+        [InlineData(CheckProperties.Remove)]
+        [InlineData(CheckProperties.GetAll)]
+        public void RunApp(string checkProperties)
+        {
+            string newPropertyName = "HOST_TEST_PROPERTY";
+            string[] args =
+            {
+                HostContextArg,
+                Scenario.App,
+                checkProperties,
+                sharedState.HostFxrPath,
+                sharedState.AppPath,
+                sharedState.AppPropertyName,
+                newPropertyName
+            };
+            CommandResult result = Command.Create(sharedState.NativeHostPath, args)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot)
+                .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot)
+                .Execute();
+
+            result.Should().Pass()
+                .And.InitializeContextForApp(sharedState.AppPath)
+                .And.ExecuteAssemblyMock(sharedState.AppPath);
+
+            switch (checkProperties)
+            {
+                case CheckProperties.None:
+                    int appArgIndex = 5;
+                    result.Should()
+                        .HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.HaveStdOutContaining($"mock argc:{args.Length - appArgIndex}");
+                    for (int i = appArgIndex; i < args.Length; ++i)
+                    {
+                        result.Should().HaveStdOutContaining($"mock argv[{i - appArgIndex}] = {args[i]}");
+                    }
+                    break;
+                case CheckProperties.Get:
+                    result.Should()
+                        .GetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.App, newPropertyName, HostPropertyNotFound)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.NotHavePropertyMock(newPropertyName);
+                    break;
+                case CheckProperties.Set:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.App, newPropertyName)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, PropertyValueFromHost)
+                        .And.HavePropertyMock(newPropertyName, PropertyValueFromHost);
+                    break;
+                case CheckProperties.Remove:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.App, newPropertyName)
+                        .And.NotHavePropertyMock(sharedState.AppPropertyName)
+                        .And.NotHavePropertyMock(newPropertyName);
+                    break;
+                case CheckProperties.GetAll:
+                    result.Should()
+                        .GetRuntimePropertiesIncludes(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue);
+                    break;
+                default:
+                    throw new Exception($"Unknown option: {checkProperties}");
+            }
+        }
+
+        [Theory]
+        [InlineData(CheckProperties.None)]
+        [InlineData(CheckProperties.Get)]
+        [InlineData(CheckProperties.Set)]
+        [InlineData(CheckProperties.Remove)]
+        [InlineData(CheckProperties.GetAll)]
+        public void GetDelegate(string checkProperties)
+        {
+            string newPropertyName = "HOST_TEST_PROPERTY";
+            string[] args =
+            {
+                HostContextArg,
+                Scenario.Config,
+                checkProperties,
+                sharedState.HostFxrPath,
+                sharedState.RuntimeConfigPath,
+                sharedState.ConfigPropertyName,
+                newPropertyName
+            };
+            CommandResult result = Command.Create(sharedState.NativeHostPath, args)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot)
+                .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot)
+                .Execute();
+
+            result.Should().Pass()
+                .And.InitializeContextForConfig(sharedState.RuntimeConfigPath)
+                .And.CreateDelegateMock();
+
+            switch (checkProperties)
+            {
+                case CheckProperties.None:
+                    result.Should()
+                        .HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                case CheckProperties.Get:
+                    result.Should()
+                        .GetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Config, newPropertyName, HostPropertyNotFound)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                case CheckProperties.Set:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.Config, newPropertyName)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost)
+                        .And.HavePropertyMock(newPropertyName, PropertyValueFromHost);
+                    break;
+                case CheckProperties.Remove:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.Config, newPropertyName)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName)
+                        .And.NotHavePropertyMock(newPropertyName);
+                    break;
+                case CheckProperties.GetAll:
+                    result.Should()
+                        .GetRuntimePropertiesIncludes(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                default:
+                    throw new Exception($"Unknown option: {checkProperties}");
+            }
+        }
+
+        [Theory]
+        [InlineData(CheckProperties.None)]
+        [InlineData(CheckProperties.Get)]
+        [InlineData(CheckProperties.Set)]
+        [InlineData(CheckProperties.Remove)]
+        [InlineData(CheckProperties.GetAll)]
+        [InlineData(CheckProperties.GetActive)]
+        [InlineData(CheckProperties.GetAllActive)]
+        public void GetDelegate_Multiple(string checkProperties)
+        {
+            string[] args =
+            {
+                HostContextArg,
+                Scenario.ConfigMultiple,
+                checkProperties,
+                sharedState.HostFxrPath,
+                sharedState.RuntimeConfigPath,
+                sharedState.SecondaryRuntimeConfigPath,
+                sharedState.ConfigPropertyName,
+                sharedState.SecondaryConfigPropertyName
+            };
+            CommandResult result = Command.Create(sharedState.NativeHostPath, args)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot)
+                .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot)
+                .Execute();
+
+            result.Should().Pass()
+                .And.InitializeContextForConfig(sharedState.RuntimeConfigPath)
+                .And.InitializeSecondaryContext(sharedState.SecondaryRuntimeConfigPath)
+                .And.CreateDelegateMock();
+
+            switch (checkProperties)
+            {
+                case CheckProperties.None:
+                    result.Should()
+                        .HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                case CheckProperties.Get:
+                    result.Should()
+                        .GetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName, HostPropertyNotFound)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, HostPropertyNotFound)
+                        .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, sharedState.SecondaryConfigPropertyValue)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName);
+                    break;
+                case CheckProperties.Set:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, InvalidArgFailure)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost)
+                        .And.HavePropertyMock(sharedState.SecondaryConfigPropertyName, PropertyValueFromHost);
+                    break;
+                case CheckProperties.Remove:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, InvalidArgFailure)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName)
+                        .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName);
+                    break;
+                case CheckProperties.GetAll:
+                    result.Should()
+                        .GetRuntimePropertiesIncludes(LogPrefix.Config, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, sharedState.SecondaryConfigPropertyValue)
+                        .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.ConfigPropertyName)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                case CheckProperties.GetActive:
+                    result.Should()
+                        .FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.ConfigPropertyName, HostInvalidState)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Config, sharedState.SecondaryConfigPropertyName, HostInvalidState)
+                        .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName, HostPropertyNotFound)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.SecondaryConfigPropertyName);
+                    break;
+                case CheckProperties.GetAllActive:
+                    result.Should()
+                        .FailToGetRuntimeProperties(LogPrefix.Config, HostInvalidState)
+                        .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.ConfigPropertyName,sharedState.ConfigPropertyValue)
+                        .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.SecondaryConfigPropertyName)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue);
+                    break;
+                default:
+                    throw new Exception($"Unknown option: {checkProperties}");
+            }
+        }
+
+        [Theory]
+        [InlineData(CheckProperties.None)]
+        [InlineData(CheckProperties.Get)]
+        [InlineData(CheckProperties.Set)]
+        [InlineData(CheckProperties.Remove)]
+        [InlineData(CheckProperties.GetAll)]
+        [InlineData(CheckProperties.GetActive)]
+        [InlineData(CheckProperties.GetAllActive)]
+        public void RunApp_GetDelegate(string checkProperties)
+        {
+            string[] args =
+            {
+                HostContextArg,
+                Scenario.Mixed,
+                checkProperties,
+                sharedState.HostFxrPath,
+                sharedState.AppPath,
+                sharedState.RuntimeConfigPath,
+                sharedState.AppPropertyName,
+                sharedState.ConfigPropertyName
+            };
+            CommandResult result = Command.Create(sharedState.NativeHostPath, args)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable("COREHOST_TRACE", "1")
+                .EnvironmentVariable("DOTNET_ROOT", sharedState.DotNetRoot)
+                .EnvironmentVariable("DOTNET_ROOT(x86)", sharedState.DotNetRoot)
+                .EnvironmentVariable("TEST_BLOCK_MOCK_EXECUTE_ASSEMBLY", $"{sharedState.AppPath}.block")
+                .Execute();
+
+            result.Should().Pass()
+                .And.InitializeContextForApp(sharedState.AppPath)
+                .And.ExecuteAssemblyMock(sharedState.AppPath)
+                .And.InitializeSecondaryContext(sharedState.RuntimeConfigPath)
+                .And.CreateDelegateMock();
+
+            switch(checkProperties)
+            {
+                case CheckProperties.None:
+                    result.Should()
+                        .HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue);
+                    break;
+                case CheckProperties.Get:
+                    result.Should()
+                        .GetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName, HostPropertyNotFound)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, HostPropertyNotFound)
+                        .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName);
+                    break;
+                case CheckProperties.Set:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, InvalidArgFailure)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, PropertyValueFromHost)
+                        .And.HavePropertyMock(sharedState.ConfigPropertyName, PropertyValueFromHost);
+                    break;
+                case CheckProperties.Remove:
+                    result.Should()
+                        .SetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName)
+                        .And.SetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, InvalidArgFailure)
+                        .And.FailToSetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, InvalidArgFailure)
+                        .And.NotHavePropertyMock(sharedState.AppPropertyName)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName);
+                    break;
+                case CheckProperties.GetAll:
+                    result.Should()
+                        .GetRuntimePropertiesIncludes(LogPrefix.App, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.ConfigPropertyName, sharedState.ConfigPropertyValue)
+                        .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.AppPropertyName)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName);
+                    break;
+                case CheckProperties.GetActive:
+                    result.Should()
+                        .FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.AppPropertyName, HostInvalidState)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.App, sharedState.ConfigPropertyName, HostInvalidState)
+                        .And.GetRuntimePropertyValue(LogPrefix.Secondary, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.FailToGetRuntimePropertyValue(LogPrefix.Secondary, sharedState.ConfigPropertyName, HostPropertyNotFound)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName);
+                    break;
+                case CheckProperties.GetAllActive:
+                    result.Should()
+                        .FailToGetRuntimeProperties(LogPrefix.App, HostInvalidState)
+                        .And.GetRuntimePropertiesIncludes(LogPrefix.Secondary, sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.GetRuntimePropertiesExcludes(LogPrefix.Secondary, sharedState.ConfigPropertyName)
+                        .And.HavePropertyMock(sharedState.AppPropertyName, sharedState.AppPropertyValue)
+                        .And.NotHavePropertyMock(sharedState.ConfigPropertyName);
+                    break;
+                default:
+                    throw new Exception($"Unknown option: {checkProperties}");
+            }
+        }
+
+        public class SharedTestState : SharedTestStateBase
+        {
+            public string HostFxrPath { get; }
+            public string DotNetRoot { get; }
+
+            public string AppPath { get; }
+            public string RuntimeConfigPath { get; }
+            public string SecondaryRuntimeConfigPath { get; }
+
+            public string AppPropertyName => "APP_TEST_PROPERTY";
+            public string AppPropertyValue => "VALUE_FROM_APP";
+
+            public string ConfigPropertyName => "CONFIG_TEST_PROPERTY";
+            public string ConfigPropertyValue => "VALUE_FROM_CONFIG";
+
+            public string SecondaryConfigPropertyName => "SECONDARY_CONFIG_TEST_PROPERTY";
+            public string SecondaryConfigPropertyValue => "VALUE_FROM_SECONDARY_CONFIG";
+
+            public SharedTestState()
+            {
+                var dotNet = new DotNetBuilder(BaseDirectory, Path.Combine(TestArtifact.TestArtifactsPath, "sharedFrameworkPublish"), "mockRuntime")
+                    .AddMicrosoftNETCoreAppFrameworkMockCoreClr(RepoDirectories.MicrosoftNETCoreAppVersion)
+                    .Build();
+                DotNetRoot = dotNet.BinPath;
+
+                HostFxrPath = Path.Combine(
+                    dotNet.GreatestVersionHostFxrPath,
+                    RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr"));
+
+                string appDir = Path.Combine(BaseDirectory, "app");
+                Directory.CreateDirectory(appDir);
+                AppPath = Path.Combine(appDir, "App.dll");
+                File.WriteAllText(AppPath, string.Empty);
+
+                RuntimeConfig.FromFile(Path.Combine(appDir, "App.runtimeconfig.json"))
+                    .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion))
+                    .WithProperty(AppPropertyName, AppPropertyValue)
+                    .Save();
+
+                string configDir = Path.Combine(BaseDirectory, "config");
+                Directory.CreateDirectory(configDir);
+                RuntimeConfigPath = Path.Combine(configDir, "Component.runtimeconfig.json");
+                RuntimeConfig.FromFile(RuntimeConfigPath)
+                    .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion))
+                    .WithProperty(ConfigPropertyName, ConfigPropertyValue)
+                    .Save();
+
+                string secondaryDir = Path.Combine(BaseDirectory, "secondary");
+                Directory.CreateDirectory(secondaryDir);
+                SecondaryRuntimeConfigPath = Path.Combine(secondaryDir, "Secondary.runtimeconfig.json");
+                RuntimeConfig.FromFile(SecondaryRuntimeConfigPath)
+                    .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion))
+                    .WithProperty(SecondaryConfigPropertyName, SecondaryConfigPropertyValue)
+                    .Save();
+            }
+        }
+    }
+}
diff --git a/src/installer/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs b/src/installer/test/HostActivationTests/NativeHosting/HostContextResultExtensions.cs
new file mode 100644 (file)
index 0000000..de3c0c5
--- /dev/null
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using FluentAssertions;
+
+namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
+{
+    internal static class HostContextResultExtensions
+    {
+        public static AndConstraint<CommandResultAssertions> ExecuteAssemblyMock(this CommandResultAssertions assertion, string appPath)
+        {
+            return assertion.HaveStdOutContaining("mock coreclr_initialize() called")
+                .And.HaveStdOutContaining("mock coreclr_execute_assembly() called")
+                .And.HaveStdOutContaining($"mock managedAssemblyPath:{appPath}")
+                .And.HaveStdOutContaining("mock coreclr_shutdown_2() called");
+        }
+
+        public static AndConstraint<CommandResultAssertions> CreateDelegateMock(this CommandResultAssertions assertion)
+        {
+            return assertion.HaveStdOutContaining("mock coreclr_initialize() called")
+                .And.HaveStdOutContaining("mock coreclr_create_delegate() called");
+        }
+
+        public static AndConstraint<CommandResultAssertions> HavePropertyMock(this CommandResultAssertions assertion, string name, string value)
+        {
+            return assertion.HaveStdOutContaining($"mock property[{name}] = {value}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> NotHavePropertyMock(this CommandResultAssertions assertion, string name)
+        {
+            return assertion.NotHaveStdOutContaining($"mock property[{name}]");
+        }
+
+        public static AndConstraint<CommandResultAssertions> InitializeContextForApp(this CommandResultAssertions assertion, string path)
+        {
+            return assertion.HaveStdErrContaining($"Initialized context for app: {path}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> InitializeContextForConfig(this CommandResultAssertions assertion, string path)
+        {
+            return assertion.HaveStdErrContaining($"Initialized context for config: {path}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> InitializeSecondaryContext(this CommandResultAssertions assertion, string path)
+        {
+            return assertion.HaveStdErrContaining($"Initialized secondary context for config: {path}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> GetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, string value)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_property_value succeeded for property: {name}={value}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> FailToGetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, int errorCode)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_property_value failed for property: {name} - 0x{errorCode.ToString("x")}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> SetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_set_runtime_property_value succeeded for property: {name}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> FailToSetRuntimePropertyValue(this CommandResultAssertions assertion, string prefix, string name, int errorCode)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_set_runtime_property_value failed for property: {name} - 0x{errorCode.ToString("x")}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> GetRuntimePropertiesIncludes(this CommandResultAssertions assertion, string prefix, string name, string value)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties succeeded")
+                .And.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties: {name}={value}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> GetRuntimePropertiesExcludes(this CommandResultAssertions assertion, string prefix, string name)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties succeeded")
+                .And.NotHaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties: {name}");
+        }
+
+        public static AndConstraint<CommandResultAssertions> FailToGetRuntimeProperties(this CommandResultAssertions assertion, string prefix, int errorCode)
+        {
+            return assertion.HaveStdOutContaining($"{prefix}hostfxr_get_runtime_properties failed - 0x{errorCode.ToString("x")}");
+        }
+    }
+}
index 1a27e31..dfc8611 100644 (file)
@@ -154,12 +154,6 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
 
             public SharedTestState()
             {
-                // Copy nethost next to native host
-                string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost");
-                File.Copy(
-                    Path.Combine(RepoDirectories.CorehostPackages, nethostName),
-                    Path.Combine(Path.GetDirectoryName(NativeHostPath), nethostName));
-
                 InvalidInstallRoot = Path.Combine(BaseDirectory, "invalid");
                 Directory.CreateDirectory(InvalidInstallRoot);
 
index 4c55b89..ad788ac 100644 (file)
@@ -24,6 +24,15 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
             // Copy over native host
             RepoDirectories = new RepoDirectoriesProvider();
             File.Copy(Path.Combine(RepoDirectories.Artifacts, "corehost_test", nativeHostName), NativeHostPath);
+
+            // Copy nethost next to native host
+            // This is done even for tests not directly using nethost because nativehost consumes nethost in the more
+            // user-friendly way of linking against nethost (instead of dlopen/LoadLibrary and dlsym/GetProcAddress).
+            // On Windows, we can delay load through a linker option, but on other platforms load is required on start.
+            string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost");
+            File.Copy(
+                Path.Combine(RepoDirectories.CorehostPackages, nethostName),
+                Path.Combine(Path.GetDirectoryName(NativeHostPath), nethostName));
         }
 
         public void Dispose()
index 3a6f58a..f87cf6d 100644 (file)
@@ -63,7 +63,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
 
         public AndConstraint<CommandResultAssertions> NotHaveStdOutContaining(string pattern)
         {
-            Execute.Assertion.ForCondition(!_commandResult.StdErr.Contains(pattern))
+            Execute.Assertion.ForCondition(!_commandResult.StdOut.Contains(pattern))
                 .FailWith("The command output contained a result it should not have contained: {0}{1}", pattern, GetDiagnosticsInfo());
             return new AndConstraint<CommandResultAssertions>(this);
         }