目录
- 引言
- main
-
- exe_start
-
- 初始化特定平台的静态转储(createdump)功能
- 获取当前宿主程序的绝对路径。
- [初始化 hostfxr 解析器](#初始化 hostfxr 解析器)
- [调用 hostfxr 入口函数](#调用 hostfxr 入口函数)
- hostfxr
-
- resolve_main_(bundle_)startupinfo
- fx_muxer_t::execute
- read_config_and_execute
- [execute_app / execute_host_command](#execute_app / execute_host_command)
- hostpolicy
-
- [corehost_main / corehost_main_with_output_buffer](#corehost_main / corehost_main_with_output_buffer)
- run_host_command
- run_app
引言
最近闲着没事我开始仔细观察.net8 winform 应用经过ide编译后的obj目录,这里面竟然有一个apphost.exe,我这个以前没怎么深入了解过.net底层的菜鸟感到很奇怪,因为我觉得他会和cpp应用的中间产物差不多,为啥会有个exe,并且大小和demo.exe(假设自己的应用是demo)大小一样,反编译后的代码也一样,我直接把他替换bin文件夹里的demo.exe,也能正常跑起来,所以这俩文件应该是一样的。后来我又建了个demo2的项目,把demo2.exe和demo.exe进行二进制文件对比发现只有一个不同之处就是一块含有主程序集文件名的区域,我在hexeditor把demo.exe的"demo.dll"修改成"demo2.dll"然后替换demo2.exe,正常运行。这么看起来.net项目的exe只是一个启动器的作用,到这我大概知道为什么叫apphost的名字了,经过一翻查阅和反编译,我大概了解了,但是我想更清楚一些,果断下载dotnet/runtime源码从入口开始浏览。
虽然自己囫囵吞枣看一遍不会有很大提升,但是我想自己去探索,既然看了总得留下点什么,只是在脑子里以后可能会忘,所以想记录一下这个过程,我见识有限所以这可能不专业,也可能有错误,欢迎读者批评指正一起交流。

main
位置:runtime/src/native/corehost/corehost.cpp
一切的入口main函数,不是c#代码里的Main方法哦。
他干啥了呢?
- 初始化用于调试和诊断运行时自身的日志追踪系统以及输出程序启动信息。
- 调用
exe_start函数。 - 处理错误缓存 / 输出,返回退出码。
exe_start
这里会有几个条件编译,关于
FEATURE_APPHOST:apphost 是 .NET Core 3.0 之后引入的应用宿主,对应我们发布 .NET 应用时生成的同名可执行文件,它会和应用程序集、运行时依赖打包在一起,让应用可以脱离全局安装的 .NET 运行时独立运行(即自包含部署)。
初始化特定平台的静态转储(createdump)功能
用于崩溃调试。
获取当前宿主程序的绝对路径。
通过 FEATURE_APPHOST 宏区分两种部署模式的宿主逻辑
1. FEATURE_APPHOST(自包含部署 / 单文件部署的 AppHost)
对应我们发布 .NET 应用时生成的 YourApp.exe,负责启动自身绑定的应用。
is_exe_enabled_for_execution:检查 AppHost 可执行文件是否绑定了有效的应用程序集。
bundle_marker_t::is_bundle():判断当前 AppHost 是否是单文件应用(单文件打包,所有依赖都嵌入在可执行文件中)。
cpp
//AppHost 编译时会嵌入应用名称,这里验证嵌入的应用是否有效,无效则返回绑定失败状态码。
pal::string_t embedded_app_name;
if (!is_exe_enabled_for_execution(&embedded_app_name))
{
return StatusCode::AppHostExeNotBoundFailure;
}
//将嵌入的应用名称中的 / 替换为当前系统的路径分隔符(DIR_SEPARATOR),
//避免路径拼接错误。
if (_X('/') != DIR_SEPARATOR)
{
replace_char(&embedded_app_name, _X('/'), DIR_SEPARATOR);
}
//如果嵌入的应用名称包含路径分隔符(即应用在子目录中),
//则需要 hostfxr 的 startupinfo 接口,旧版接口不支持相对路径应用。
//hostfxr后面会说
auto pos_path_char = embedded_app_name.find(DIR_SEPARATOR);
if (pos_path_char != pal::string_t::npos)
{
requires_hostfxr_startupinfo_interface = true;
}
//拼接应用程序的完整路径
app_path.assign(get_directory(host_path));
append_path(&app_path, embedded_app_name.c_str());
//单文件应用判断/普通 AppHost 应用路径验证
...
//后续用于 hostfxr 解析框架依赖
app_root.assign(get_directory(app_path));
2. 非 FEATURE_APPHOST(普通 dotnet 宿主,即 dotnet.exe/dotnet)
对应全局安装的 dotnet 命令,负责执行框架依赖的应用或 dotnet 命令。
cpp
//提取宿主程序名称(去除扩展名,如 dotnet.exe -> dotnet)
pal::string_t own_name = strip_executable_ext(get_filename(host_path));
//安全校验:防止 dotnet 被重命名后执行(避免伪造签名)
if (pal::strcasecmp(own_name.c_str(), CURHOST_TYPE) != 0)
{
trace::error(_X("Error: cannot execute %s when renamed to %s."), CURHOST_TYPE, own_name.c_str());
return StatusCode::CoreHostEntryPointFailure;
}
if (argc <= 1)//无参数校验:输出 dotnet 命令用法
{
trace::println();
trace::println(_X("Usage: dotnet [path-to-application]"));
trace::println(_X("Usage: dotnet [commands]"));
// ... 省略用法输出 ...
return StatusCode::InvalidArgFailure;
}
//确定应用程序路径(默认 dotnet.dll,或用户指定的应用.dll)
app_root.assign(host_path);
app_path.assign(get_directory(app_root));
append_path(&app_path, own_name.c_str());
app_path.append(_X(".dll"));
初始化 hostfxr 解析器
hostfxr_resolver_t:.NET 宿主中的hostfxr 解析器类,构造时传入应用根目录 app_root。
核心作用:
在应用根目录、全局 .NET 安装目录中查找 hostfxr 库(如 hostfxr.dll/libhostfxr.so)。
加载 hostfxr 库,并解析其导出的入口函数(如 hostfxr_main_startupinfo、hostfxr_main_bundle_startupinfo)
cpp
hostfxr_resolver_t fxr{app_root};
int rc = fxr.status_code();
if (rc != StatusCode::Success)
return rc;
调用 hostfxr 入口函数
1. 单文件应用(FEATURE_APPHOST && bundle_marker_t::is_bundle ())
cpp
auto hostfxr_main_bundle_startupinfo = fxr.resolve_main_bundle_startupinfo();
if (hostfxr_main_bundle_startupinfo != nullptr)
{
// 准备参数:宿主路径、.NET 根目录、应用路径、单文件包头部偏移
const pal::char_t* host_path_cstr = host_path.c_str();
const pal::char_t* dotnet_root_cstr =
fxr.dotnet_root().empty() ? nullptr : fxr.dotnet_root().c_str();
const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str();
int64_t bundle_header_offset = bundle_marker_t::header_offset();
// 输出日志
trace::info(_X("Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo"),
fxr.fxr_path().c_str());
// ... 省略参数日志 ...
// 传递错误写入器给 hostfxr
auto set_error_writer = fxr.resolve_set_error_writer();
propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer);
// 调用单文件应用专属入口函数,启动应用
rc = hostfxr_main_bundle_startupinfo(argc, argv, host_path_cstr,
dotnet_root_cstr, app_path_cstr, bundle_header_offset);
}
else
{
// 错误处理:hostfxr 不支持单文件应用(版本过旧)
trace::error(_X("The required library %s does not support single-file apps."),
fxr.fxr_path().c_str());
need_newer_framework_error(fxr.dotnet_root(), host_path);
rc = StatusCode::FrameworkMissingFailure;
}
hostfxr_main_bundle_startupinfo:单文件应用专属的 hostfxr 入口函数,支持从打包后的单文件中提取并加载应用和依赖。
bundle_header_offset:单文件包的头部偏移量,用于定位包内的应用资源。
2. 普通应用(非单文件 /dotnet 命令)
cpp
auto hostfxr_main_startupinfo = fxr.resolve_main_startupinfo();
if (hostfxr_main_startupinfo != nullptr)
{
// 准备参数:宿主路径、.NET 根目录、应用路径
const pal::char_t* host_path_cstr = host_path.c_str();
const pal::char_t* dotnet_root_cstr = fxr.dotnet_root().empty() ? nullptr : fxr.dotnet_root().c_str();
const pal::char_t* app_path_cstr = app_path.empty() ? nullptr : app_path.c_str();
// 输出日志
trace::info(_X("Invoking fx resolver [%s] hostfxr_main_startupinfo"), fxr.fxr_path().c_str());
// ... 省略参数日志 ...
// 传递错误写入器给 hostfxr
auto set_error_writer = fxr.resolve_set_error_writer();
propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer);
// 调用新版入口函数,启动应用/执行 dotnet 命令
rc = hostfxr_main_startupinfo(argc, argv, host_path_cstr, dotnet_root_cstr, app_path_cstr);
// 兼容处理:旧版 hostfxr 不支持错误写入器,补充提示信息
if (trace::get_error_writer() != nullptr && rc == static_cast<int>(StatusCode::FrameworkMissingFailure) && set_error_writer == nullptr)
{
need_newer_framework_error(fxr.dotnet_root(), host_path);
}
}
hostfxr_main_startupinfo:新版 hostfxr 的入口函数,支持 DOTNET_ROOT 配置、相对路径应用等高级功能。
若新版入口函数不存在(hostfxr 版本过旧),则进入兼容模式,调用旧版 hostfxr_main_v1 入口函数:
hostfxr
resolve_main_(bundle_)startupinfo
前面的hostfxr_resolver_t 是对hostfxr库的封装,比如resolve_main_startupinfo实际是返回库里的hostfxr_main_startupinfo函数
cpp
hostfxr_main_startupinfo_fn hostfxr_resolver_t::resolve_main_startupinfo()
{
assert(m_hostfxr_dll != nullptr);
return reinterpret_cast<hostfxr_main_startupinfo_fn>(pal::get_symbol(m_hostfxr_dll, "hostfxr_main_startupinfo"));
}
hostfxr真正的源码在corehost/fxr目录下。
fx_muxer_t::execute
hostfxr_main_bundle_startupinfo:专用于单文件应用(bundle),先处理单文件包的解压 / 资源提取,再组装启动信息。
hostfxr_main_startupinfo:用于普通应用(非单文件 / 框架依赖),直接组装启动信息。
最终两者都会调用 fx_muxer_t::execute,
它的核心职责是 识别宿主运行模式、解析命令行参数、根据参数类型分发到不同的处理逻辑(CLI 命令处理 / 应用执行处理),是连接 hostfxr 顶层入口和实际业务逻辑(执行应用 / 处理 dotnet 命令)的关键分发器。
看具体代码前先看几个定义,
host_mode_t:宿主运行模式枚举,常见值包括 HostMode::Execute(执行 .NET 应用)、HostMode::Cli(处理 dotnet CLI 命令)、HostMode::AppHost(AppHost 启动应用)等。
·opt_map_t·:命令行选项映射表(键值对),用于存储解析后的命令行选项(如 --additionalprobingpath、--depsfile 等)。
new_argoff:解析命令行参数后,「应用参数 / 剩余参数」的起始偏移量(用于剥离命令行选项,只保留传递给应用的参数)。
app_candidate:解析得到的「候选应用路径」(如 YourApp.dll)。
cpp
int fx_muxer_t::execute(
const pal::string_t host_command,
const int argc,
const pal::char_t* argv[],
const host_startup_info_t& host_info,
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size){
// Detect invocation mode
host_mode_t mode = detect_operating_mode(host_info);
int new_argoff;
pal::string_t app_candidate;
opt_map_t opts;
int result = command_line::parse_args_for_mode(mode, host_info, argc, argv, &new_argoff, app_candidate, opts);
if (static_cast<StatusCode>(result) == AppArgNotRunnable) {
//识别并执行 dotnet 内置 CLI 命令
if (host_command.empty()){
return handle_cli(host_info, argc, argv, app_candidate);
}
else{
return result;
}
}
if (!result){
// Transform dotnet [exec] [--additionalprobingpath path] [--depsfile file] [dll] [args]
// -> dotnet [dll] [args]
result = handle_exec_host_command(
host_command,
host_info,
app_candidate,
opts,
argc,
argv,
new_argoff,
mode,
false /*is_sdk_command*/,
result_buffer,
buffer_size,
required_buffer_size);
}
return result;
}
int fx_muxer_t::handle_exec_host_command(
const pal::string_t& host_command,
const host_startup_info_t& host_info,
const pal::string_t& app_candidate,
const opt_map_t& opts,
int argc,
const pal::char_t* argv[],
int argoff,
host_mode_t mode,
const bool is_sdk_command,
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size){
const pal::char_t** new_argv = argv;
int new_argc = argc;
std::vector<const pal::char_t*> vec_argv;
if (argoff != 1){ //调整命令行参数格式
vec_argv.reserve(argc - argoff + 1); // +1 for dotnet
vec_argv.push_back(argv[0]);
vec_argv.insert(vec_argv.end(), argv + argoff, argv + argc);
new_argv = vec_argv.data();
new_argc = (int32_t)vec_argv.size();
}
trace::info(_X("Using dotnet root path [%s]"), host_info.dotnet_root.c_str());
// Transform dotnet [exec] [--additionalprobingpath path] [--depsfile file] [dll] [args] -> dotnet [dll] [args]
return read_config_and_execute(
host_command,
host_info,
app_candidate,
opts,
new_argc,
new_argv,
mode,
is_sdk_command,
result_buffer,
buffer_size,
required_buffer_size);
}
举个例子帮助理解:
原始参数(argc=5):argv[0] = dotnet.exe、argv[1] = exec、argv[2] = --depsfile、argv[3] = YourApp.dll、argv[4] = arg1
解析后 argoff=3(应用路径从 argv[3] 开始)
新参数数组 vec_argv:[dotnet.exe, YourApp.dll, arg1],new_argc=3
效果:剥离了 exec 和 --depsfile 两个宿主选项,只保留应用执行所需的核心参数。
read_config_and_execute
cpp
int read_config_and_execute(
const pal::string_t& host_command,
const host_startup_info_t& host_info,
const pal::string_t& app_candidate,
const opt_map_t& opts,
int new_argc,
const pal::char_t** new_argv,
host_mode_t mode,
const bool is_sdk_command,
pal::char_t out_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size){
pal::string_t hostpolicy_dir;
std::unique_ptr<corehost_init_t> init;
int rc = get_init_info_for_app(
host_command,
host_info,
app_candidate,
opts,
mode,
is_sdk_command,
hostpolicy_dir,
init);
if (rc != StatusCode::Success) return rc;
if (host_command.size() == 0){
rc = execute_app(hostpolicy_dir, init.get(), new_argc, new_argv);
}
else{
rc = execute_host_command(hostpolicy_dir, init.get(),
new_argc, new_argv, out_buffer, buffer_size, required_buffer_size);
}
return rc;
}
通过 get_init_info_for_app 函数,读取应用配置文件、解析依赖、定位 hostpolicy 库(.NET 应用策略执行库)和 CoreCLR 运行时,组装出完整的启动初始化信息。
根据 host_command 是否为空,区分默认应用执行(host_command 为空,如 dotnet YourApp.dll、AppHost 启动应用)和 自定义宿主命令执行(host_command 非空,自定义宿主场景)。
分别调用 execute_app(默认应用启动,加载 CoreCLR 并运行应用)或 execute_host_command(自定义宿主命令处理),最终返回执行状态码。
继续来看execute_app
execute_app / execute_host_command
cpp
static int execute_app(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[]){
//宿主上下文安全校验(防止重复初始化)
{
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);
}
//加载 hostpolicy 库
pal::dll_t hostpolicy_dll;
hostpolicy_contract_t hostpolicy_contract{};
corehost_main_fn host_main = nullptr;
int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract);
if (code == StatusCode::Success){
host_main = hostpolicy_contract.corehost_main;
if (host_main == nullptr){
code = StatusCode::CoreHostEntryPointFailure;
}
}
...//省略 错误检查
//标记 .NET 运行时已初始化,并初始化应用框架信息
{
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_active_host_context->initialize_frameworks(*init);
g_context_initializing.store(false);
}
g_context_initializing_cv.notify_all();
{
//将 hostfxr 的错误日志写入器传递给 hostpolicy,保证整个启动流程的日志输出统一
propagate_error_writer_t propagate_error_writer_to_corehost(hostpolicy_contract.set_error_writer);
//获取应用启动的核心初始化数据(如应用根目录、运行时路径、配置信息),传递给 hostpolicy。
const host_interface_t& intf = init->get_host_init_data();
//由 hostpolicy 负责加载 CoreCLR 运行时库,初始化 .NET 运行环境(如默认应用域、GC 配置、程序集加载器)。
if ((code = hostpolicy_contract.load(&intf)) == StatusCode::Success){
//调用 hostpolicy 入口函数,最终会定位到 .NET 应用的 Program.Main 方法
code = host_main(argc, argv);
//仅调用 hostpolicy 的卸载接口标记状态,不实际释放
(void)hostpolicy_contract.unload();
}
}
return code;
}
再来看 execute_host_command
cpp
static int execute_host_command(
const pal::string_t& impl_dll_dir,
corehost_init_t* init,
const int argc,
const pal::char_t* argv[],
pal::char_t result_buffer[],
int32_t buffer_size,
int32_t* required_buffer_size)
{
pal::dll_t hostpolicy_dll;
hostpolicy_contract_t hostpolicy_contract{};
//带输出缓冲区的入口函数类型, 支持将执行结果写入外部缓冲区。
corehost_main_with_output_buffer_fn host_main = nullptr;
int code = load_hostpolicy(impl_dll_dir, &hostpolicy_dll, hostpolicy_contract);
if (code == StatusCode::Success) {
host_main = hostpolicy_contract.corehost_main_with_output_buffer;
if (host_main == nullptr)
{
code = StatusCode::CoreHostEntryPointFailure;
}
}
if (code != StatusCode::Success) return code;
{
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 = hostpolicy_contract.load(&intf)) == StatusCode::Success)
{
code = host_main(argc, argv, result_buffer, buffer_size, required_buffer_size);
(void)hostpolicy_contract.unload();
}
}
return code;
}
基本和execute_app一致,host_main后面几个参数解释:
result_buffer:自定义宿主传入的输出缓冲区,用于存储命令执行结果(如字符串形式的返回信息)。
buffer_size:输出缓冲区的大小,告知 hostpolicy 缓冲区的可用容量。
required_buffer_size:输出参数,若 result_buffer 容量不足,hostpolicy 会通过该参数返回所需的缓冲区大小,方便自定义宿主重新分配足够内存。
hostpolicy
corehost_main / corehost_main_with_output_buffer
初始化 hostpolicy 核心数据、创建 CoreCLR 运行时、执行托管应用入口
cpp
SHARED_API int HOSTPOLICY_CALLTYPE corehost_main(const int argc, const pal::char_t* argv[])
{
int rc = corehost_main_init(g_init, argc, argv, _X("corehost_main"));
if (rc != StatusCode::Success)
return rc;
arguments_t args;
assert(g_context == nullptr);
rc = create_hostpolicy_context(g_init, argc, argv, true /* breadcrumbs_enabled */, &args);
if (rc != StatusCode::Success)
return rc;
rc = create_coreclr();
if (rc != StatusCode::Success)
return rc;
return run_app(args.app_argc, args.app_argv);
}
SHARED_API int HOSTPOLICY_CALLTYPE 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)
{
int rc = corehost_main_init(g_init, argc, argv, _X("corehost_main_with_output_buffer"));
if (rc != StatusCode::Success) return rc;
//仅识别并处理 get-native-search-directories 命令,
//该命令的作用是获取 .NET 应用原生依赖(如 *.dll/*.so)的搜索目录列表,
//供自定义宿主排查原生依赖加载问题
if (g_init.host_command == _X("get-native-search-directories")){
arguments_t args;
if (!parse_arguments(g_init, argc, argv, args))
return StatusCode::LibHostInvalidArgs;
//将结果存储到 output_string 中
pal::string_t output_string;
rc = run_host_command(g_init, args, &output_string);
if (rc != StatusCode::Success) return rc;
//缓冲区大小适配与结果写入
int32_t len = static_cast<int32_t>(output_string.length());
if (len + 1 > buffer_size){
rc = StatusCode::HostApiBufferTooSmall;
*required_buffer_size = len + 1;
trace::info(_X("get-native-search-directories failed with buffer too small"), output_string.c_str());
}
else{
output_string.copy(buffer, len);
buffer[len] = '\0';
*required_buffer_size = 0;
trace::info(_X("get-native-search-directories success: %s"), output_string.c_str());
}
}
else{
trace::error(_X("Unknown command: %s"), g_init.host_command.c_str());
rc = StatusCode::LibHostInvalidArgs;
}
return rc;
}
run_host_command
run_host_command 是 get-native-search-directories 命令的专属实现函数,不依赖CoreClr。
context.coreclr_properties:上下文的属性集合,存储了 CoreCLR 运行时及应用的关键配置属性。
cpp
int run_host_command(
hostpolicy_init_t &hostpolicy_init,
const arguments_t &args,
pal::string_t* out_host_command_result = nullptr)
{
assert(out_host_command_result != nullptr);
//根据 hostpolicy_init 和 args,加载应用配置并填充上下文属性(如 NativeDllSearchDirectories)
hostpolicy_context_t context {};
int rc = context.initialize(hostpolicy_init, args, false /* enable_breadcrumbs */);
if (rc != StatusCode::Success) return rc;
//命令匹配
if (pal::strcasecmp(hostpolicy_init.host_command.c_str(), _X("get-native-search-directories")) == 0){
const pal::char_t *value;
//尝试从属性集合中获取「原生 DLL 搜索目录」属性值,该值是拼接好的目录列表
if (!context.coreclr_properties.try_get(common_property::NativeDllSearchDirectories, &value)){
trace::error(_X("get-native-search-directories failed to find NATIVE_DLL_SEARCH_DIRECTORIES property"));
return StatusCode::HostApiFailed;
}
assert(out_host_command_result != nullptr);
//结果赋值
out_host_command_result->assign(value);
return StatusCode::Success;
}
return StatusCode::InvalidArgFailure;
}
run_app
cpp
int HOSTPOLICY_CALLTYPE run_app(const int argc, const pal::char_t *argv[])
{
//确保上下文已正确初始化且绑定了 CoreCLR 运行时,避免无效执行
const std::shared_ptr<hostpolicy_context_t> context = get_hostpolicy_context(/*require_runtime*/ true);
if (context == nullptr)
return StatusCode::HostInvalidState;
return run_app_for_context(*context, argc, argv);
}
run_app_for_context
完成命令行参数的跨字符串编码转换、调用 CoreCLR 原生 API 执行托管程序集、最终关闭 CoreCLR 并返回托管应用的退出码。
context.coreclr:CoreCLR 运行时实例指针,封装了 CoreCLR 的所有原生 API(如 execute_assembly、shutdown)。
pal::pal_clrstring/pal::clr_palstring:跨平台字符串编码转换函数,用于在宿主原生字符串(如 UTF-16/UTF-8)和CoreCLR 托管字符串(UTF-8/UTF-16)之间转换,保证跨平台兼容性。
cpp
int run_app_for_context(
const hostpolicy_context_t &context,
int argc,
const pal::char_t **argv)
{
assert(context.coreclr != nullptr);
//将宿主环境的原生字符串(pal::char_t*,跨平台适配的字符类型)转换
//为 CoreCLR 能识别的 char* 字符串(通常是 UTF-8 编码)
std::vector<std::vector<char>> argv_strs(argc);
std::vector<const char*> argv_local(argc);
for (int i = 0; i < argc; i++){
pal::pal_clrstring(argv[i], &argv_strs[i]);
argv_local[i] = argv_strs[i].data();
}
//记录启动日志
if (trace::is_enabled()) {
pal::string_t arg_str;
for (size_t i = 0; i < argv_local.size(); i++){
pal::string_t cur;
pal::clr_palstring(argv_local[i], &cur);
arg_str.append(cur);
arg_str.append(_X(","));
}
trace::info(_X("Launch host: %s, app: %s, argc: %d, args: %s"), context.host_path.c_str(),
context.application.c_str(), argc, arg_str.c_str());
}
//托管程序集路径编码转换
std::vector<char> managed_app;
pal::pal_clrstring(context.application, &managed_app);
std::shared_ptr<breadcrumb_writer_t> writer;
if (!context.breadcrumbs.empty()){
writer = breadcrumb_writer_t::begin_write(context.breadcrumbs);
assert(context.breadcrumbs.empty());
}
trace::flush();
unsigned int exit_code;
//调用 CoreCLR API 执行托管程序集
auto hr = context.coreclr->execute_assembly(
(int32_t)argv_local.size(),
argv_local.data(),
managed_app.data(),
&exit_code);
if (!SUCCEEDED(hr)){
trace::error(_X("Failed to execute managed app, HRESULT: 0x%X"), hr);
return StatusCode::CoreClrExeFailure;
}
trace::info(_X("Execute managed assembly exit code: 0x%X"), exit_code);
//关闭 CoreCLR 运行时
hr = context.coreclr->shutdown(reinterpret_cast<int*>(&exit_code));
if (!SUCCEEDED(hr)){
trace::warning(_X("Failed to shut down CoreCLR, HRESULT: 0x%X"), hr);
}
if (writer){
writer->end_write();
}
return exit_code;
}
这就是.NET 应用启动的最终落地函数。