资讯动态

.NET Runtime 主机运行时契约(Host Runtime Contract)与运行时信息传递机制详解

发布时间:2026/9/18 3:58:47 来源:尧图企业网站定制
.NET Runtime 主机运行时契约Host Runtime Contract与运行时信息传递机制详解【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime导读在 .NET 的托管宿主host体系中宿主与运行时runtime之间通过一组运行时属性runtime properties——即键值字符串——来传递应用信息。从 .NET 8 开始宿主通过一个名为HOST_RUNTIME_CONTRACT的属性向运行时传递一份结构化契约从根本上改变了传统 key-value 字符串传递方式的成本模型。本文以 dotnet/runtime 仓库中 host-runtime-information.md 为核心结合宿主层hostpolicy与运行时层hostinformation.cpp的源码实现系统讲解这份契约的结构、全部 well-known 运行时属性的语义与版本演进帮助读者理解apphost/hostpolicy到 CoreCLR 之间的信息通道是如何设计与落地的。一、背景运行时属性传递的固有代价宿主在启动运行时如调用coreclr_initialize时需要把大量信息传给运行时包括用户在 runtimeconfig.json 或 MSBuild 属性中指定的运行时配置项以及宿主自身产生的一组 well-known 属性。所有这些信息都表现为 key-value 字符串。这种全字符串化的机制存在三个明显缺陷这也是 host-runtime-information.md 明确指出的设计动因表达能力受限任何信息都必须先编码成字符串结构化数据或非字符串数据例如函数指针、结构体难以直接传递开销非平凡属性流经宿主层、运行时和托管库时每个名称和值都会被复制多份启动即全量计算属性在启动时预计算并全部设置无论应用是否真正需要某个属性都必须为全部属性付出成本。为解决上述问题.NET 8 引入了一种更灵活、成本更低的通信方式——宿主运行时契约host runtime contract。二、HOST_RUNTIME_CONTRACT从字符串到结构化契约2.1 属性定义契约本身仍然通过属性传递属性名为HOST_RUNTIME_CONTRACT其值是一个指向host_runtime_contract结构体的指针的十六进制字符串表示。结构体定义位于 host_runtime_contract.h。通过这一层间接宿主不再需要把每个属性都预先展开成字符串传入运行时而是把查询能力交给运行时实现按需获取pay-for-play。host_runtime_contract结构体完整定义如下摘自 host_runtime_contract.hstruct host_runtime_contract { size_t size; // 结构体大小用于版本兼容 void* context; // 契约上下文传给各函数 // 获取运行时属性值。返回包含结尾 null 的长度未找到返回 -1。 size_t(HOST_CONTRACT_CALLTYPE* get_runtime_property)( const char* key, /*out*/ char* value_buffer, size_t value_buffer_size, void* contract_context); // 探测应用 bundle 中的 path命中时输出其 offset、size 与压缩后大小 bool(HOST_CONTRACT_CALLTYPE* bundle_probe)( const char* path, /*out*/ int64_t* offset, /*out*/ int64_t* size, /*out*/ int64_t* compressedSize); // 查询指定 p/invokelibrary_name、entry_point_name是否被重定向 const void* (HOST_CONTRACT_CALLTYPE* pinvoke_override)( const char* library_name, const char* entry_point_name); // 在宿主中探测 path命中时输出数据起始指针与大小供外部程序集探测扩展使用 bool(HOST_CONTRACT_CALLTYPE* external_assembly_probe)( const char* path, /*out*/ void **data_start, /*out*/ int64_t* size); // 为指定程序集由 native code context 描述获取原生代码数据R2R 信息 bool(HOST_CONTRACT_CALLTYPE* get_native_code_data)( const struct host_runtime_contract_native_code_context* context, /*out*/ struct host_runtime_contract_native_code_data* data); };关键设计点版本化结构体size字段记录结构体大小运行时按size判断哪些回调可用见下文HostInformation::HasExternalProbe的实现这是跨版本扩展契约的兼容手段调用约定Windows 上回调使用__stdcall其他平台无修饰见 host_runtime_contract.h 中HOST_CONTRACT_CALLTYPE宏回调生命周期头文件注释明确规定设置在契约上的所有回调在进程存活期内都必须保持有效契约中还配套定义了host_runtime_contract_native_code_context携带assembly_path与owner_composite_name和host_runtime_contract_native_code_data携带 R2R header 指针、镜像大小与基址两个辅助结构用于跨宿主获取组件程序集的原生 ReadyToRun 代码数据。2.2 宿主侧hostpolicy 如何构造并注入契约在框架依赖的托管应用中真正与 CoreCLR 对接的是hostpolicy库。在 hostpolicy_context.cpp 中可以看到契约的构造与注入逻辑{ host_contract { sizeof(host_runtime_contract), this }; if (bundle::info_t::is_single_file_bundle()) { host_contract.bundle_probe bundle_probe; #if defined(NATIVE_LIBS_EMBEDDED) host_contract.pinvoke_override pinvoke_override; #endif } host_contract.get_runtime_property get_runtime_property; pal::char_t ptr_to_string_buffer[STRING_LENGTH(0xffffffffffffffff) 1]; pal::snwprintf(ptr_to_string_buffer, ARRAY_SIZE(ptr_to_string_buffer), _X(0x%zx), (size_t)(host_contract)); if (!coreclr_properties.add(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT), ptr_to_string_buffer)) { log_duplicate_property_error(_STRINGIFY(HOST_PROPERTY_RUNTIME_CONTRACT)); return StatusCode::LibHostDuplicateProperty; } }要点解读context字段被设为this即hostpolicy_context_t*各回调通过它访问宿主上下文仅当应用是单文件single-filebundle 时才挂载bundle_probe与pinvoke_override回调契约指针以十六进制字符串形式写入HOST_RUNTIME_CONTRACT属性再随其他属性一起传给coreclr_initialize。get_runtime_property的实现hostpolicy_context.cpp展示了两类属性来源按需计算的属性如ENTRY_ASSEMBLY_NAME由应用路径去掉扩展名得出、ARGV0仅当宿主记录了调用名时返回否则返回 -1、BUNDLE_EXTRACTION_PATH仅单文件 bundle 且确有解压目录时返回来自运行时初始化的属性即启动时预置在coreclr_properties中的全部属性通过try_get按 key 查找并转换为 UTF-8 输出。约定返回 -1 表示属性不存在返回长度含结尾 null用于让调用方扩容缓冲区重试。2.3 运行时侧CoreCLR 如何消费契约在 CoreCLR 中契约由HostInformation类维护实现位于 hostinformation.cppHostInformation::SetContract在初始化时把宿主传入的契约整体复制到静态变量s_hostContractHostInformation::GetProperty(name, value)封装了get_runtime_property调用先以MAX_PATH 1的缓冲尝试若返回长度更大则按需扩容再取一次两次失败即判定属性不存在hostinformation.cppHostInformation::HasExternalProbe使用offsetof(host_runtime_contract, external_assembly_probe) sizeof(...)与s_hostContract.size比较通过结构体 size 判断外部探测回调是否在宿主版本中可用——这正是size字段发挥版本兼容作用的实际案例hostinformation.cpp。2.4 coreclr_initialize 入口处的优先级规则属性在coreclr_initialize入口处由 exports.cpp 中的ConvertConfigPropertiesToUnicode统一处理。这里有非常明确的优先级规则扫描属性列表时识别出BUNDLE_PROBE、PINVOKE_OVERRIDE、HOST_RUNTIME_CONTRACT三个特殊属性BUNDLE_PROBE与PINVOKE_OVERRIDE的值被解析为函数指针u16_strtoui64后强转契约中的对应回调优先于单独属性只有在契约回调未设置为 null时才会回退使用BUNDLE_PROBE/PINVOKE_OVERRIDE属性的值。这一点同时为 .NET 9 中宿主不再设置BUNDLE_PROBE/PINVOKE_OVERRIDE属性的演进提供了平滑的兼容路径——老宿主通过属性传新宿主通过契约传运行时按优先级统一消费。2.5 其他宿主实现corerun 与浏览器/WASI 宿主契约机制并非apphost独有。CoreCLR 自带的corerun宿主同样实现了完整的契约回调corerun.cppget_runtime_property支持按需返回ENTRY_ASSEMBLY_NAME并支持用户通过-p/--property传入的自定义属性corerun.cpp实现了external_assembly_probe浏览器目标下委托给BrowserHost_ExternalAssemblyProbe否则在s_core_libs_path/s_core_root_path中按文件名探测实现了get_native_code_data在 Windows/macOS 上加载程序集旁的平台原生库并读取其RTR_HEADER导出组装 R2R 头指针、镜像基址与大小corerun.cpp。此外browserhost.cpp 与 wasihost.cpp 也引用并实现该契约说明这一机制覆盖了dotnet、corerun、浏览器托管、WASI 等多种宿主形态。三、Well-known 运行时属性全解析除了契约本身宿主还会通过get_runtime_property提供一组 well-known 属性。在契约引入之前这些属性都必须在coreclr_initialize时作为字符串一次性传入。属性名常量集中定义在 host_runtime_contract.h。3.1 路径分隔符约定所有包含路径列表的属性都使用平台相关的分隔符Windows 上为;Unix 上为:。这一点同时影响STARTUP_HOOKS、TRUSTED_PLATFORM_ASSEMBLIES、NATIVE_DLL_SEARCH_DIRECTORIES、PLATFORM_RESOURCE_ROOTS、APP_PATHS、PROBING_DIRECTORIES等属性的解析。3.2 应用信息App information属性语义APP_CONTEXT_BASE_DIRECTORY应用所在目录对应托管 APIAppContext.BaseDirectoryRUNTIME_IDENTIFIER应用的运行时标识符RID对应RuntimeInformation.RuntimeIdentifierARGV0调用应用宿主的名称对应原生进程的argv[0]。apphost会提供该属性使Environment.GetCommandLineArgs()的第一个元素为宿主名而dotnet、corerun这类 muxer 式宿主不提供该属性从而保留托管应用路径作为第一个元素ENTRY_ASSEMBLY_NAME入口程序集名称。尽管未在文档主表中单列它由get_runtime_property按需计算在 hostpolicy 中由application路径去掉扩展名得到在 corerun 中由entry_assembly_fullpath拆出文件名再截去扩展名corerun.cpp3.3 Deps 文件属性语义APP_CONTEXT_DEPS_FILES应用的deps.json文件路径供Microsoft.Extensions.DependencyModel使用FX_DEPS_FILE根共享框架Microsoft.NETCore.App的deps.json路径仅框架依赖应用同样供Microsoft.Extensions.DependencyModel使用3.4 启动钩子属性语义STARTUP_HOOKS包含 StartupHook 的程序集列表路径或名称在应用Main入口点之前按序执行多个条目用平台路径分隔符分隔StartupHook 机制本身在 host-startup-hook.md 中有完整规范类型必须名为StartupHook、无命名空间、通常为internal暴露public static void Initialize()条目可以是程序集绝对路径也可以是通过AssemblyLoadContext.Default按名称加载的程序集名名称不得含目录分隔符、空格、逗号不得以.dll结尾。DOTNET_STARTUP_HOOKS环境变量指定的钩子具有最高优先级hostpolicy 在构建STARTUP_HOOKS属性时会把配置来源的钩子追加在环境变量钩子之后hostpolicy_context.cpp。3.5 探测路径Probing paths属性语义使用场景TRUSTED_PLATFORM_ASSEMBLIES平台与应用程序集的文件路径列表托管程序集默认探测NATIVE_DLL_SEARCH_DIRECTORIES搜索非托管原生库的目录列表非托管原生程序集探测PLATFORM_RESOURCE_ROOTS搜索附属satellite资源程序集的目录列表卫星资源程序集探测APP_PATHS搜索托管程序集的目录列表默认不设置PROBING_DIRECTORIES对应共享存储路径与附加探测路径host-probing.md 所述探测路径SYSTEM_CORELIB_DIRECTORY包含System.Private.CoreLib.dll的绝对路径见下探测机制本身host-probing.md宿主按优先级顺序遍历探测路径列表对每个路径拼接deps.json中库的相对路径与资产相对路径命中磁盘上的真实文件即结束探测。SYSTEM_CORELIB_DIRECTORY的语义值得专门说明host-runtime-information.md设置后运行时将从该目录加载System.Private.CoreLib而不再在coreclr旁边寻找这是为System.Private.CoreLib.dll未与coreclr同目录放置的宿主设计的例如单文件打包场景若宿主提供了程序集探测扩展external_assembly_probe有效的探测结果优先于该属性否则一旦设置该属性就不做回退搜索——指定目录中加载失败即启动失败宿主只能指定目录而非文件路径因为运行时约定 corelib 永远叫System.Private.CoreLib.dll。3.6 单文件Single-file相关属性属性语义版本状态BUNDLE_EXTRACTION_PATH单文件 bundle 解压出的文件的解压目录路径运行时用它搜索与捆绑托管程序集关联的原生库.NET 10 新增BUNDLE_PROBE函数指针的十六进制字符串单文件应用运行时设置运行时调用它查找捆绑在应用中的程序集。签名BundleProbeFn见 coreclrhost.h.NET 9 起宿主不再设置改由host_runtime_contract.bundle_probe提供HOSTPOLICY_EMBEDDED指示 hostpolicy 是否嵌入宿主可执行文件自包含单文件应用为true.NET 9 起宿主不再设置、运行时也不再读取单文件同时包含宿主与运行时组件构建期即可确定PINVOKE_OVERRIDE函数指针的十六进制字符串自包含单文件应用运行时设置运行时调用它检查被重定向的 p/invoke。签名PInvokeOverrideFn见 coreclrhost.h 与 mono-private-unstable-types.h.NET 9 起宿主不再设置改由host_runtime_contract.pinvoke_override提供单文件场景中 p/invoke 重定向的典型实现hostpolicy 内置的pinvoke_override会拦截对hostpolicy自身的两个导出函数corehost_resolve_component_dependencies、corehost_set_error_writer在 macOS 上还会把System.Security.Cryptography.Native.Apple的 DllImport 重定向到宿主内部实现hostpolicy_context.cpp。四、契约机制的版本演进与兼容策略综合文档与源码可以梳理出清晰的版本脉络.NET 8引入HOST_RUNTIME_CONTRACT宿主在coreclr_initialize时把契约指针编码为属性传入运行时通过HostInformation消费BUNDLE_PROBE、PINVOKE_OVERRIDE仍以独立属性传递但契约回调优先.NET 9BUNDLE_PROBE与PINVOKE_OVERRIDE两个属性不再由宿主设置完全迁移到契约回调HOSTPOLICY_EMBEDDED属性整体废弃构建期可知无需运行时通信.NET 10新增BUNDLE_EXTRACTION_PATH属性用于报告单文件 bundle 的解压目录。兼容策略体现在两层运行时侧exports.cpp中对BUNDLE_PROBE/PINVOKE_OVERRIDE属性的处理仍保留契约回调未设置时才回退读取因此老版本宿主依然可用host_runtime_contract.size字段让运行时能够探测新字段如external_assembly_probe是否在宿主版本中存在文档设计意图对既有字符串属性如探测路径类未来仍可通过get_runtime_property按需获取字符串同时契约可扩展出查询结构化信息的专用回调两者并行保证向后兼容的同时逐步降低成本。五、为什么说这是按需付费的架构升级回到文档开头提出的三个问题可以完整回答契约机制如何逐一化解结构化数据契约回调直接传递函数指针与结构体bundle 偏移量、R2R 镜像信息等不再依赖字符串编码复制开销get_runtime_property只查询所需的单个属性避免所有属性在宿主、运行时、库三层之间全量复制。HostInformation::GetProperty甚至采用两阶段缓冲策略先小缓冲试探、按需扩容进一步减少内存分配启动成本ENTRY_ASSEMBLY_NAME、ARGV0、BUNDLE_EXTRACTION_PATH等属性在 hostpolicy 中已是按需计算hostpolicy_context.cpp而非启动时预置探测路径等大批量属性在未来演进中也可平滑迁移到按需查询使每个应用只为真正需要的属性买单。六、延伸阅读与源码索引设计文档host-runtime-information.md、host-startup-hook.md、host-probing.md、host-components.md契约头文件host_runtime_contract.h结构体与属性常量定义hostpolicy 实现hostpolicy_context.cpp契约构造、get_runtime_property、bundle/pinvoke 回调CoreCLR 运行时消费侧hostinformation.cpp、exports.cpp其他宿主示例corerun.cpp、browserhost.cpp、wasihost.cpp回调签名定义coreclrhost.h、mono-private-unstable-types.h理解这份契约是深入掌握 .NET 宿主启动链路、单文件应用打包机制以及自定义托管宿主实现的关键一步。对于需要自研宿主如嵌入式场景的开发者host_runtime_contract就是连接宿主与运行时、兼顾灵活性与性能的推荐通道。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价