From 483fc90e356cc2d4f45fc5ad5e86dbbd92ef22d9 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Wed, 2 Jun 2021 15:40:38 -0700 Subject: [PATCH] Remove some unnecessary string copies in hosting layer (#53631) --- src/native/corehost/deps_format.cpp | 53 ++++--- src/native/corehost/fxr/command_line.cpp | 20 +-- src/native/corehost/fxr/command_line.h | 2 +- src/native/corehost/fxr/fx_muxer.cpp | 6 +- src/native/corehost/fxr/fx_resolver.cpp | 33 +++-- src/native/corehost/hostmisc/pal.h | 2 +- src/native/corehost/hostmisc/pal.unix.cpp | 2 +- src/native/corehost/hostmisc/pal.windows.cpp | 2 +- src/native/corehost/hostpolicy/deps_resolver.cpp | 157 +++++++++++---------- src/native/corehost/runtime_config.cpp | 4 +- src/native/corehost/test/nativehost/nativehost.cpp | 4 +- 11 files changed, 156 insertions(+), 129 deletions(-) diff --git a/src/native/corehost/deps_format.cpp b/src/native/corehost/deps_format.cpp index e60ccd5..f0beb6e 100644 --- a/src/native/corehost/deps_format.cpp +++ b/src/native/corehost/deps_format.cpp @@ -70,7 +70,9 @@ void deps_json_t::reconcile_libraries_with_targets( for (size_t i = 0; i < deps_entry_t::s_known_asset_types.size(); ++i) { bool rid_specific = false; - for (const auto& asset : get_assets_fn(library.name.GetString(), i, &rid_specific)) + const vec_asset_t& assets = get_assets_fn(lib_name, i, &rid_specific); + m_deps_entries[i].reserve(assets.size()); + for (const auto& asset : assets) { auto asset_name = asset.name; if (ends_with(asset_name, _X(".ni"), false)) @@ -139,7 +141,7 @@ pal::string_t deps_json_t::get_current_rid(const rid_fallback_graph_t& rid_fallb bool deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets, const rid_fallback_graph_t& rid_fallback_graph) { pal::string_t host_rid = get_current_rid(rid_fallback_graph); - + for (auto& package : portable_assets->libs) { for (size_t asset_type_index = 0; asset_type_index < deps_entry_t::asset_types::count; asset_type_index++) @@ -235,13 +237,16 @@ bool deps_json_t::process_runtime_targets(const json_parser_t::value_t& json, co const auto& rid = file.value[_X("rid")].GetString(); - trace::info(_X("Adding runtimeTargets %s asset %s rid=%s assemblyVersion=%s fileVersion=%s from %s"), - deps_entry_t::s_known_asset_types[asset_type_index], - asset.relative_path.c_str(), - rid, - asset.assembly_version.as_str().c_str(), - asset.file_version.as_str().c_str(), - package.name.GetString()); + if (trace::is_enabled()) + { + trace::info(_X("Adding runtimeTargets %s asset %s rid=%s assemblyVersion=%s fileVersion=%s from %s"), + deps_entry_t::s_known_asset_types[asset_type_index], + asset.relative_path.c_str(), + rid, + asset.assembly_version.as_str().c_str(), + asset.file_version.as_str().c_str(), + package.name.GetString()); + } assets.libs[package.name.GetString()][asset_type_index].rid_assets[rid].push_back(asset); } @@ -270,7 +275,10 @@ bool deps_json_t::process_targets(const json_parser_t::value_t& json, const pal: continue; } - for (const auto& file : iter->value.GetObject()) + const auto& files = iter->value.GetObject(); + vec_asset_t& asset_files = assets.libs[package.name.GetString()][i]; + asset_files.reserve(files.MemberCount()); + for (const auto& file : files) { version_t assembly_version, file_version; @@ -289,14 +297,17 @@ bool deps_json_t::process_targets(const json_parser_t::value_t& json, const pal: pal::string_t file_name{file.name.GetString()}; deps_asset_t asset(get_filename_without_ext(file_name), file_name, assembly_version, file_version); - trace::info(_X("Adding %s asset %s assemblyVersion=%s fileVersion=%s from %s"), - deps_entry_t::s_known_asset_types[i], - asset.relative_path.c_str(), - asset.assembly_version.as_str().c_str(), - asset.file_version.as_str().c_str(), - package.name.GetString()); + if (trace::is_enabled()) + { + trace::info(_X("Adding %s asset %s assemblyVersion=%s fileVersion=%s from %s"), + deps_entry_t::s_known_asset_types[i], + asset.relative_path.c_str(), + asset.assembly_version.as_str().c_str(), + asset.file_version.as_str().c_str(), + package.name.GetString()); + } - assets.libs[package.name.GetString()][i].push_back(asset); + asset_files.push_back(asset); } } } @@ -374,7 +385,9 @@ bool deps_json_t::load_self_contained(const pal::string_t& deps_path, const json for (const auto& rid : json[_X("runtimes")].GetObject()) { auto& vec = m_rid_fallback_graph[rid.name.GetString()]; - for (const auto& fallback : rid.value.GetArray()) + const auto& fallback_array = rid.value.GetArray(); + vec.reserve(fallback_array.Size()); + for (const auto& fallback : fallback_array) { vec.push_back(fallback.GetString()); } @@ -403,7 +416,7 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve pal::string_t pv = name; pv.push_back(_X('/')); pv.append(ver); - + auto iter = m_rid_assets.libs.find(pv); if (iter != m_rid_assets.libs.end()) { @@ -415,7 +428,7 @@ bool deps_json_t::has_package(const pal::string_t& name, const pal::string_t& ve } } } - + return m_assets.libs.count(pv); } diff --git a/src/native/corehost/fxr/command_line.cpp b/src/native/corehost/fxr/command_line.cpp index 1146d40..082f81d 100644 --- a/src/native/corehost/fxr/command_line.cpp +++ b/src/native/corehost/fxr/command_line.cpp @@ -14,9 +14,9 @@ namespace { struct host_option { - const pal::string_t option; - const pal::string_t argument; - const pal::string_t description; + const pal::char_t* option; + const pal::char_t* argument; + const pal::char_t* description; }; const host_option KnownHostOptions[] = @@ -85,7 +85,7 @@ namespace int arg_i = *num_args; while (arg_i < argc) { - pal::string_t arg = argv[arg_i]; + const pal::char_t* arg = argv[arg_i]; pal::string_t arg_lower = pal::to_lower(arg); const auto &iter = std::find_if(known_opts.cbegin(), known_opts.cend(), [&](const known_options &opt) { return arg_lower == get_host_option(opt).option; }); @@ -101,7 +101,7 @@ namespace return false; } - trace::verbose(_X("Parsed known arg %s = %s"), arg.c_str(), argv[arg_i + 1]); + trace::verbose(_X("Parsed known arg %s = %s"), arg, argv[arg_i + 1]); (*opts)[*iter].push_back(argv[arg_i + 1]); // Increment for both the option and its value. @@ -134,7 +134,7 @@ namespace for (const auto& opt : known_opts) { const host_option &arg = get_host_option(opt); - trace::error(_X(" %-37s %s"), (arg.option + _X(" ") + arg.argument).c_str(), arg.description.c_str()); + trace::error(_X(" %s %-*s %s"), arg.option, 36 - pal::strlen(arg.option), arg.argument, arg.description); } return StatusCode::InvalidArgFailure; } @@ -211,7 +211,7 @@ pal::string_t command_line::get_option_value( return default_value; } -const pal::string_t& command_line::get_option_name(known_options opt) +const pal::char_t* command_line::get_option_name(known_options opt) { return get_host_option(opt).option; } @@ -325,7 +325,7 @@ void command_line::print_muxer_usage(bool is_sdk_present) for (const auto& opt : known_opts) { const host_option &arg = get_host_option(opt); - trace::println(_X(" %-30s %s"), (arg.option + _X(" ") + arg.argument).c_str(), arg.description.c_str()); + trace::println(_X(" %s %-*s %s"), arg.option, 29 - pal::strlen(arg.option), arg.argument, arg.description); } trace::println(_X(" --list-runtimes Display the installed runtimes")); trace::println(_X(" --list-sdks Display the installed SDKs")); @@ -334,7 +334,7 @@ void command_line::print_muxer_usage(bool is_sdk_present) { trace::println(); trace::println(_X("Common Options:")); - trace::println(_X(" -h|--help Displays this help.")); - trace::println(_X(" --info Display .NET information.")); + trace::println(_X(" -h|--help Displays this help.")); + trace::println(_X(" --info Display .NET information.")); } } diff --git a/src/native/corehost/fxr/command_line.h b/src/native/corehost/fxr/command_line.h index 7dc96a0..118206b 100644 --- a/src/native/corehost/fxr/command_line.h +++ b/src/native/corehost/fxr/command_line.h @@ -33,7 +33,7 @@ typedef std::unordered_map, known_opti namespace command_line { - const pal::string_t& get_option_name(known_options opt); + const pal::char_t* get_option_name(known_options opt); pal::string_t get_option_value( const opt_map_t& opts, known_options opt, diff --git a/src/native/corehost/fxr/fx_muxer.cpp b/src/native/corehost/fxr/fx_muxer.cpp index 35673fd..6a20206 100644 --- a/src/native/corehost/fxr/fx_muxer.cpp +++ b/src/native/corehost/fxr/fx_muxer.cpp @@ -404,7 +404,7 @@ namespace auto val = roll_forward_option_from_string(roll_forward); if (val == roll_forward_option::__Last) { - trace::error(_X("Invalid value for command line argument '%s'"), command_line::get_option_name(known_options::roll_forward).c_str()); + trace::error(_X("Invalid value for command line argument '%s'"), command_line::get_option_name(known_options::roll_forward)); return StatusCode::InvalidArgFailure; } @@ -417,8 +417,8 @@ namespace if (override_settings.has_roll_forward) { trace::error(_X("It's invalid to use both '%s' and '%s' command line options."), - command_line::get_option_name(known_options::roll_forward).c_str(), - command_line::get_option_name(known_options::roll_forward_on_no_candidate_fx).c_str()); + command_line::get_option_name(known_options::roll_forward), + command_line::get_option_name(known_options::roll_forward_on_no_candidate_fx)); return StatusCode::InvalidArgFailure; } diff --git a/src/native/corehost/fxr/fx_resolver.cpp b/src/native/corehost/fxr/fx_resolver.cpp index 2da9af4..5a5fef7 100644 --- a/src/native/corehost/fxr/fx_resolver.cpp +++ b/src/native/corehost/fxr/fx_resolver.cpp @@ -54,13 +54,16 @@ namespace } } - if (best_match_version == fx_ver_t()) + if (trace::is_enabled()) { - trace::verbose(_X("No match greater than or equal to [%s] found."), fx_ref.get_fx_version().c_str()); - } - else - { - trace::verbose(_X("Found version [%s]"), best_match_version.as_str().c_str()); + if (best_match_version == fx_ver_t()) + { + trace::verbose(_X("No match greater than or equal to [%s] found."), fx_ref.get_fx_version().c_str()); + } + else + { + trace::verbose(_X("Found version [%s]"), best_match_version.as_str().c_str()); + } } } @@ -89,14 +92,20 @@ namespace apply_patch_from_version = fx_ref.get_fx_version_number(); } - trace::verbose( - _X("Applying patch roll forward from [%s] on %s"), - apply_patch_from_version.as_str().c_str(), - release_only ? _X("release only") : _X("release/pre-release")); + if (trace::is_enabled()) + { + trace::verbose( + _X("Applying patch roll forward from [%s] on %s"), + apply_patch_from_version.as_str().c_str(), + release_only ? _X("release only") : _X("release/pre-release")); + } for (const auto& ver : version_list) { - trace::verbose(_X("Inspecting version... [%s]"), ver.as_str().c_str()); + if (trace::is_enabled()) + { + trace::verbose(_X("Inspecting version... [%s]"), ver.as_str().c_str()); + } if ((!release_only || !ver.is_prerelease()) && (fx_ref.get_apply_patches() || ver.get_patch() == apply_patch_from_version.get_patch()) && @@ -170,7 +179,7 @@ namespace best_match = fx_ref.get_fx_version_number(); trace::verbose(_X("Framework reference didn't resolve to any available version.")); } - else + else if (trace::is_enabled()) { trace::verbose(_X("Framework reference resolved to version '%s'."), best_match.as_str().c_str()); } diff --git a/src/native/corehost/hostmisc/pal.h b/src/native/corehost/hostmisc/pal.h index 21bb1cf..27daf76 100644 --- a/src/native/corehost/hostmisc/pal.h +++ b/src/native/corehost/hostmisc/pal.h @@ -252,7 +252,7 @@ namespace pal string_t get_timestamp(); bool getcwd(string_t* recv); - string_t to_lower(const string_t& in); + string_t to_lower(const char_t* in); inline void file_flush(FILE *f) { std::fflush(f); } diff --git a/src/native/corehost/hostmisc/pal.unix.cpp b/src/native/corehost/hostmisc/pal.unix.cpp index 3aa4d5d..2db2dd8 100644 --- a/src/native/corehost/hostmisc/pal.unix.cpp +++ b/src/native/corehost/hostmisc/pal.unix.cpp @@ -50,7 +50,7 @@ #error "Don't know how to obtain max path on this platform" #endif -pal::string_t pal::to_lower(const pal::string_t& in) +pal::string_t pal::to_lower(const pal::char_t* in) { pal::string_t ret = in; std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index 7b53fec..8a9e35a 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -40,7 +40,7 @@ bool GetModuleHandleFromAddress(void *addr, HMODULE *hModule) return (res != FALSE); } -pal::string_t pal::to_lower(const pal::string_t& in) +pal::string_t pal::to_lower(const pal::char_t* in) { pal::string_t ret = in; std::transform(ret.begin(), ret.end(), ret.begin(), ::towlower); diff --git a/src/native/corehost/hostpolicy/deps_resolver.cpp b/src/native/corehost/hostpolicy/deps_resolver.cpp index 5af59fc..aae9e87 100644 --- a/src/native/corehost/hostpolicy/deps_resolver.cpp +++ b/src/native/corehost/hostpolicy/deps_resolver.cpp @@ -12,78 +12,77 @@ #include #include -const pal::string_t MissingAssemblyMessage = _X( - "%s:\n" - " An assembly specified in the application dependencies manifest (%s) was not found:\n" - " package: '%s', version: '%s'\n" - " path: '%s'"); - -const pal::string_t ManifestListMessage = _X( - " This assembly was expected to be in the local runtime store as the application was published using the following target manifest files:\n" - " %s"); - -const pal::string_t DuplicateAssemblyWithDifferentExtensionMessage = _X( - "Error:\n" - " An assembly specified in the application dependencies manifest (%s) has already been found but with a different file extension:\n" - " package: '%s', version: '%s'\n" - " path: '%s'\n" - " previously found assembly: '%s'"); - namespace { -// ----------------------------------------------------------------------------- -// A uniqifying append helper that doesn't let two "paths" to be identical in -// the "output" string. -// -void add_unique_path( - deps_entry_t::asset_types asset_type, - const pal::string_t& path, - std::unordered_set* existing, - pal::string_t* serviced, - pal::string_t* non_serviced, - const pal::string_t& svc_dir) -{ - // To optimize startup time, we avoid calling realpath here. - // Because of this, there might be duplicates in the output - // whenever path is eiter non-normalized or a symbolic link. - if (existing->count(path)) + const pal::char_t* MissingAssemblyMessage = _X( + "%s:\n" + " An assembly specified in the application dependencies manifest (%s) was not found:\n" + " package: '%s', version: '%s'\n" + " path: '%s'"); + + const pal::char_t* ManifestListMessage = _X( + " This assembly was expected to be in the local runtime store as the application was published using the following target manifest files:\n" + " %s"); + + const pal::char_t* DuplicateAssemblyWithDifferentExtensionMessage = _X( + "Error:\n" + " An assembly specified in the application dependencies manifest (%s) has already been found but with a different file extension:\n" + " package: '%s', version: '%s'\n" + " path: '%s'\n" + " previously found assembly: '%s'"); + + // ----------------------------------------------------------------------------- + // A uniqifying append helper that doesn't let two "paths" to be identical in + // the "output" string. + // + void add_unique_path( + deps_entry_t::asset_types asset_type, + const pal::string_t& path, + std::unordered_set* existing, + pal::string_t* serviced, + pal::string_t* non_serviced, + const pal::string_t& svc_dir) { - return; - } - - trace::verbose(_X("Adding to %s path: %s"), deps_entry_t::s_known_asset_types[asset_type], path.c_str()); + // To optimize startup time, we avoid calling realpath here. + // Because of this, there might be duplicates in the output + // whenever path is eiter non-normalized or a symbolic link. + if (existing->count(path)) + { + return; + } - if (starts_with(path, svc_dir, false)) - { - serviced->append(path); - serviced->push_back(PATH_SEPARATOR); - } - else - { - non_serviced->append(path); - non_serviced->push_back(PATH_SEPARATOR); - } + trace::verbose(_X("Adding to %s path: %s"), deps_entry_t::s_known_asset_types[asset_type], path.c_str()); - existing->insert(path); -} + if (starts_with(path, svc_dir, false)) + { + serviced->append(path); + serviced->push_back(PATH_SEPARATOR); + } + else + { + non_serviced->append(path); + non_serviced->push_back(PATH_SEPARATOR); + } -// Return the filename from deps path; a deps path always uses a '/' for the separator. -pal::string_t get_deps_filename(const pal::string_t& path) -{ - if (path.empty()) - { - return path; + existing->insert(path); } - auto name_pos = path.find_last_of('/'); - if (name_pos == pal::string_t::npos) + // Return the filename from deps path; a deps path always uses a '/' for the separator. + pal::string_t get_deps_filename(const pal::string_t& path) { - return path; - } + if (path.empty()) + { + return path; + } - return path.substr(name_pos + 1); -} + auto name_pos = path.find_last_of('/'); + if (name_pos == pal::string_t::npos) + { + return path; + } + return path.substr(name_pos + 1); + } } // end of anonymous namespace // ----------------------------------------------------------------------------- @@ -97,10 +96,13 @@ void deps_resolver_t::add_tpa_asset( name_to_resolved_asset_map_t::iterator existing = items->find(resolved_asset.asset.name); if (existing == items->end()) { - trace::verbose(_X("Adding tpa entry: %s, AssemblyVersion: %s, FileVersion: %s"), - resolved_asset.resolved_path.c_str(), - resolved_asset.asset.assembly_version.as_str().c_str(), - resolved_asset.asset.file_version.as_str().c_str()); + if (trace::is_enabled()) + { + trace::verbose(_X("Adding tpa entry: %s, AssemblyVersion: %s, FileVersion: %s"), + resolved_asset.resolved_path.c_str(), + resolved_asset.asset.assembly_version.as_str().c_str(), + resolved_asset.asset.file_version.as_str().c_str()); + } items->emplace(resolved_asset.asset.name, resolved_asset); } @@ -312,7 +314,8 @@ bool deps_resolver_t::probe_deps_entry(const deps_entry_t& entry, const pal::str trace::verbose(_X(" Skipping... not runtime asset")); continue; } - pal::string_t probe_dir = config.probe_dir; + + const pal::string_t& probe_dir = config.probe_dir; uint32_t search_options = deps_entry_t::search_options::none; if (needs_file_existence_checks()) { @@ -391,32 +394,32 @@ bool report_missing_assembly_in_manifest(const deps_entry_t& entry, bool continu // Treat missing resource assemblies as informational. continueResolving = true; - trace::info(MissingAssemblyMessage.c_str(), _X("Info"), + trace::info(MissingAssemblyMessage, _X("Info"), entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str()); if (showManifestListMessage) { - trace::info(ManifestListMessage.c_str(), entry.runtime_store_manifest_list.c_str()); + trace::info(ManifestListMessage, entry.runtime_store_manifest_list.c_str()); } } else if (continueResolving) { - trace::warning(MissingAssemblyMessage.c_str(), _X("Warning"), + trace::warning(MissingAssemblyMessage, _X("Warning"), entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str()); if (showManifestListMessage) { - trace::warning(ManifestListMessage.c_str(), entry.runtime_store_manifest_list.c_str()); + trace::warning(ManifestListMessage, entry.runtime_store_manifest_list.c_str()); } } else { - trace::error(MissingAssemblyMessage.c_str(), _X("Error"), + trace::error(MissingAssemblyMessage, _X("Error"), entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str()); if (showManifestListMessage) { - trace::error(ManifestListMessage.c_str(), entry.runtime_store_manifest_list.c_str()); + trace::error(ManifestListMessage, entry.runtime_store_manifest_list.c_str()); } } @@ -476,7 +479,7 @@ bool deps_resolver_t::resolve_tpa_list( if (get_deps_filename(entry.asset.relative_path) != get_filename(existing->second.resolved_path)) { trace::error( - DuplicateAssemblyWithDifferentExtensionMessage.c_str(), + DuplicateAssemblyWithDifferentExtensionMessage, entry.deps_file.c_str(), entry.library_name.c_str(), entry.library_version.c_str(), @@ -599,7 +602,7 @@ bool deps_resolver_t::resolve_tpa_list( } } - // Convert the paths into a string and return it + // Convert the paths into a string and return it for (const auto& item : items) { output->append(item.second.resolved_path); @@ -664,14 +667,14 @@ void deps_resolver_t::resolve_additional_deps(const arguments_t& args, const dep { if (pal::file_exists(additional_deps_path)) { - trace::verbose(_X("Using specified additional deps.json: '%s'"), + trace::verbose(_X("Using specified additional deps.json: '%s'"), additional_deps_path.c_str()); m_additional_deps_files.push_back(additional_deps_path); } else { - trace::warning(_X("Warning: Specified additional deps.json does not exist: '%s'"), + trace::warning(_X("Warning: Specified additional deps.json does not exist: '%s'"), additional_deps_path.c_str()); } } @@ -815,7 +818,7 @@ bool deps_resolver_t::resolve_probe_dirs( return true; } - trace::verbose(_X("Processing native/culture for deps entry [%s, %s, %s]"), + trace::verbose(_X("Processing native/culture for deps entry [%s, %s, %s]"), entry.library_name.c_str(), entry.library_version.c_str(), entry.asset.relative_path.c_str()); bool found_in_bundle = false; diff --git a/src/native/corehost/runtime_config.cpp b/src/native/corehost/runtime_config.cpp index 218394e..986c44c 100644 --- a/src/native/corehost/runtime_config.cpp +++ b/src/native/corehost/runtime_config.cpp @@ -89,7 +89,9 @@ bool runtime_config_t::parse_opts(const json_parser_t::value_t& opts) const auto& properties = opts_obj.FindMember(_X("configProperties")); if (properties != opts_obj.MemberEnd()) { - for (const auto& property : properties->value.GetObject()) + const auto& properties_obj = properties->value.GetObject(); + m_properties.reserve(properties_obj.MemberCount()); + for (const auto& property : properties_obj) { if (property.value.IsString()) { diff --git a/src/native/corehost/test/nativehost/nativehost.cpp b/src/native/corehost/test/nativehost/nativehost.cpp index 8987cb8..b323f95 100644 --- a/src/native/corehost/test/nativehost/nativehost.cpp +++ b/src/native/corehost/test/nativehost/nativehost.cpp @@ -40,7 +40,7 @@ int main(const int argc, const pal::char_t *argv[]) // args: ... [] [] [] [] bool explicit_load = false; if (argc >= 3) - explicit_load = pal::strcmp(pal::to_lower(pal::string_t{argv[2]}).c_str(), _X("true")) == 0; + explicit_load = pal::strcmp(pal::to_lower(argv[2]).c_str(), _X("true")) == 0; const pal::char_t *assembly_path = nullptr; if (argc >= 4 && pal::strcmp(argv[3], _X("nullptr")) != 0) @@ -117,7 +117,7 @@ int main(const int argc, const pal::char_t *argv[]) if (static_cast(res) == StatusCode::Success) { std::cout << "get_hostfxr_path succeeded" << std::endl; - std::cout << "hostfxr_path: " << tostr(pal::to_lower(fxr_path)).data() << std::endl; + std::cout << "hostfxr_path: " << tostr(pal::to_lower(fxr_path.c_str())).data() << std::endl; return EXIT_SUCCESS; } else -- 2.7.4