diff options
author | Elinor Fung <47805090+elinor-fung@users.noreply.github.com> | 2019-05-02 21:36:50 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-02 21:36:50 +0300 |
commit | c6f5b9098002f91dc8ee28c653ddfc8b4c9ae03d (patch) | |
tree | 3c57f7de54802a1811c9a885307c792168c7d49f /src/installer/corehost/cli/fxr/hostfxr.cpp | |
parent | 864191b29b8fa27ad238d3c251a128d450a2a313 (diff) |
Add host context-based entry points for native hosting (dotnet/core-setup#5859)
- 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
Diffstat (limited to 'src/installer/corehost/cli/fxr/hostfxr.cpp')
-rw-r--r-- | src/installer/corehost/cli/fxr/hostfxr.cpp | 517 |
1 files changed, 456 insertions, 61 deletions
diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index d836eb50f75..a42ed43b239 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -13,12 +13,20 @@ #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 |