.NET runtime 仓库 Mono 库模式(Library Mode)深度解析:将托管运行时与代码打包为原生库的完整实战指南
语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载导读本文基于 docs/design/mono/mono-library-mode.md 并结合 .NET runtime 仓库当前项目为 .NET 跨平台运行时覆盖 cloud、mobile、desktop 与 IoT中的LibraryBuilder任务源码系统讲解 Mono 的Library Mode库模式从自 .NET 8 起mono runtime 支持将自定义托管代码与运行时本身打包为单个静态或共享库供 Android/iOS 等原生应用直接链接与调用。读完本文你将掌握该模式的三大互操作支柱UnmanagedCallersOnly、Direct P/Invoke、mono runtime、运行时自动初始化与资源打包机制并能够在 Android Studio 与 Xcode 中完整集成构建产物。一、背景为什么需要 Library Mode对于大量原生应用而言直接复用 .NET 运行时库中丰富的 API可以避免在目标平台的原生语言里重新发明轮子。但 .NET 运行时库需要 .NET 运行时才能正常工作把整个 .NET 生态整体接入原生应用往往显得笨重且不必要。Library Mode 的解决思路是将 mono runtime 与调用 .NET API 的自定义托管代码捆绑成一个库让原生应用直接消费这个库——既缩小了接入足迹又获得了无缝的使用体验。该能力与 Native AOT 的原生互操作方向一致自 .NET 8 起由 mono runtime 支持使移动开发者能够通过单个静态库或共享库在其移动应用中利用现代 .NET API。为行文简洁下文将由 Mono 库模式生成、包含自定义托管代码与 mono runtime 的库统一简称为mono library。二、工作原理三大核心互操作组件Mono 库模式之所以能在原生代码与托管代码之间架起桥梁依赖以下三个核心组件UnmanagedCallersOnlyAttribute允许原生代码直接调用托管方法Direct P/Invoke直接平台调用允许托管代码直接调用原生函数mono runtime作为托管运行时承载上述两个方向的互操作。从原生应用调用托管 .NET API 有诸多场景例如当原生语言中没有对应实现时避免用原生语言重写业务逻辑。原生应用要调用这些 .NET API必须先识别对应的符号。整个流程如下自定义托管代码被编译成托管程序集后Mono AOT 编译器会为所有带有UnmanagedCallersOnlyAttribute的方法生成native-to-managed原生到托管包装器生成逻辑位于 src/mono/mono/mini/aot-compiler.c这些包装器的入口点符号由对应的UnmanagedCallersOnlyAttribute指定原生代码因此可以直接调用它们当 mono library 被链接/加载进原生应用后mono runtime 初始化时需预加载这些 AOT 编译过的程序集预加载逻辑由 src/tasks/LibraryBuilder/Templates/preloaded-assemblies.c 中生成的preload_assemblies_with_exported_symbols()完成从而让原生侧可以调用托管方法。反向的调用同样重要托管代码调用原生非托管函数可以让托管侧访问系统级操作并在没有托管对应物时复用原生库。其关键是托管侧需要知道原生函数的入口点。mono runtime 利用managed-to-native托管到原生包装器执行 Direct P/Invoke借助这些入口点指示原生运行时执行对应原生函数。Mono AOT 编译器会为被指定为可直接调用的 P/Invoke 方法生成这类包装器指定方式有三种粒度AOT 选项作用来自 aot-compiler.c 与帮助文本direct-pinvoke为托管程序集中遇到的所有 direct P/Invoke 生成直接调用direct-pinvokesstring为整个module或具体的module!entrypoint用分号分隔生成直接调用与direct-pinvoke互斥direct-pinvoke-listsstring从文件中逐行读取要生成直接调用的module或module!entrypoint与direct-pinvoke互斥从源码看这三者都要求配合staticAOT 选项使用aot-compiler.c且direct-pinvoke全量标志会覆盖前两个精确指定选项。此外指定条目若为空或被当作注释#开头会被忽略。互操作依赖一个已运行的托管运行时——即 mono runtime。mono runtime 虽然被链接进 mono library但必须处于运行状态互操作才能发生。其设计是原生应用一旦调用 mono library通过调用某个 native-to-managed 包装器的入口点符号mono runtime 便完成初始化。为此必须设置一个runtime-init-callback可手动设置也可自动设置使第一个被调用的 native-to-managed 包装器能够触发 mono 的运行时初始化函数。三、运行时自动初始化Auto-initialization默认情况下Mono 库模式采用自动初始化方案对应LibraryBuilder任务中的参数组合UsesRuntimeInitCallbacktrue默认开启见 LibraryBuilder.csUsesCustomRuntimeInitCallbackfalse即不使用自定义回调见 LibraryBuilder.cs。自动初始化涉及若干步骤模板见 autoinit.c用于把运行时配置到位一旦原生应用通过 native-to-managed 包装器入口点调用 mono library该回调以线程安全方式被调用一次回调内部依次执行注册 AOT 模块register_aot_modules()、注册资源 bundle、读取程序集位置环境变量AssembliesLocation默认环境变量名为DOTNET_LIBRARY_ASSEMBLY_PATH缺失时回退到./、初始化 runtimeconfigmonovm_runtimeconfig_initialize、注入RUNTIME_IDENTIFIER与APP_CONTEXT_BASE_DIRECTORY等 appctx 环境变量、设置程序集路径、mono_jit_set_aot_only(true)强制 AOT-only 模式、安装 AOT data 加载钩子、启用信号链式传递最后调用mono_jit_init创建默认 domain初始化完成后调用preload_assemblies_with_exported_symbols()预加载所有导出符号对应的程序集preloaded-assemblies.c自动初始化由模板文件底部的构造函数注入autoinit.c 中的__attribute__((constructor)) autoinit()在库加载时即调用mono_set_runtime_init_callback注册回调。当默认回调不合适时可以改用自定义回调UsesRuntimeInitCallbacktrueUsesCustomRuntimeInitCallbacktrueCustomRuntimeInitCallbackcustom callback此时由实现者负责设计线程安全的惰性运行时初始化实现。该参数在 Apple 构建目标中会转换为对应的 AOT 编译器参数当使用默认回调时传入裸的runtime-init-callback使用自定义回调时传入runtime-init-callbackCustomRuntimeInitCallback见 AppleBuild.targets。AOT 编译器侧对该参数的支持定义在 aot-compiler.c 与帮助文本 aot-compiler.c启用默认回调时native-to-managed 包装器直接调用运行时的初始化函数指定value时包装器直接调用该符号完成运行时初始化。四、资源打包Bundlingmono library 为原生应用提供了访问 .NET API 的能力相应地这些 API 依赖的资源也必须可访问包括程序集assemblies包含调试与符号信息的 PDB用于本地化的附属程序集satellite assemblies运行时配置runtimeconfig.bin、时区数据等其他数据资源。资源既可以放在磁盘上也可以采用更开箱即用的方案将 mono library构建为自包含库把所需资源直接打包进库本身BundlesResourcestrue。实现上所需资源的字节数据被存放在库中预分配的结构体中并在运行时初始化期间注册进 mono runtime见 autoinit.c 的mono_register_resources_bundle()调用以及 LibraryBuilder.cs 中WriteAutoInitializationFromTemplate对模板占位符的替换逻辑。当启用打包时LibraryBuilder会从BundledRuntimeConfig项读取DataSymbolruntimeconfig.bin 字节数组对应的符号与DataLenSymbol字节数组长度符号生成对应的 extern 声明并注入%RUNTIME_CONFIG_DATA%/%RUNTIME_CONFIG_DATA_LEN%runtimeconfig 会以内存数据arg-kind 1而非文件路径arg-kind 0的方式交给monovm_runtimeconfig_initialize见 autoinit.c。同时编译时会追加-D BUNDLED_RESOURCES1宏定义。五、构建侧实现LibraryBuilder MSBuild 任务Mono 库模式的构建由 src/tasks/LibraryBuilder/LibraryBuilder.cs 中的LibraryBuilderTask完成它是AppBuilderTask的子类负责把 AOT 编译产物、运行时静态库与模板代码链接成最终的原生库。其核心可配置属性如下属性说明Name必填生成库的名称产物命名为libName.so/libName.dylib/libName.a见 GetLibraryNameTargetOS必填目标系统支持android/ios/iossimulator/tvos/tvossimulator/maccatalyst见 ValidateValidTargetOSIsSharedLibrary决定生成共享库还是静态库默认true对应原生构建类型SHARED/STATIC见 LibraryBuilder.csUsesRuntimeInitCallback是否启用运行时初始化回调默认开启UsesCustomRuntimeInitCallback是否使用自定义回调默认false为false时使用autoinit.c模板生成自动初始化代码见 LibraryBuilder.csAssembliesLocation指向宿主设备上程序集位置的环境变量名默认DOTNET_LIBRARY_ASSEMBLY_PATH见 LibraryBuilder.csBundlesResources是否把资源打包进库启用时追加-D BUNDLED_RESOURCES1BundledRuntimeConfig打包的 runtimeconfig.bin 元数据项含DataSymbol、DataLenSymbol、DataLenSymbolValue元数据StripDebugSymbols是否剥离调试符号ExtraSources需要加入库的额外原生源文件ExtraLinkerArguments应用于库的额外链接器参数ExportedSymbols输出AOT 编译器识别出的导出符号集合最终写入mobile_symbols.txt构建流程中的几个关键环节对应 LibraryBuilder.cs收集产物遍历每个CompiledAssembly收集汇编文件AssemblerFile、LLVM 目标文件LlvmObjectFile、目标文件ObjectFile与导出符号文件ExportsFile从导出文件读取符号并记录导出符号所属的程序集导出符号校验若所有编译程序集都不含导出符号任务直接报错——库必须只导出由[UnmanagedCallersOnly(Entrypoint ...)]产生的符号否则生成的共享库无法使用LibraryBuilder.cs符号可见性控制Android 使用版本脚本version script把所有导出符号标记为global、其余为local模板见 linker-script.txtiOS 使用-exported_symbols_list平台差异Android 构建追加-D ANDROID1、-D HOST_ANDROID1、-fPIC、--soname且对android-arm64/android-x64追加-z,max-page-size16384以满足 16KB 内存页对齐要求Google 要求 64 位原生库按 16 字节对齐见 LibraryBuilder.csApple 构建追加-D HOST_APPLE_MOBILE1、-D FORCE_AOT1共享库还会执行install_name_tool -id rpath/库名见 LibraryBuilder.cs日志与头文件平台相关错误日志宏Android 使用__android_log_printApple 使用os_log_error定义在 library-builder.h 中。六、示例工作流从 dotnet SDK workload 构建官方推荐通过 dotnet SDK 的 workload 从托管工程直接发布 mono library示例工程为library-mode-sample包含ManagedProject等目录结构可用作对照参考。注意workload 的名称可能因 SDK 版本而异例如mobile-librarybuilder。可使用dotnet workload search并传入mobile或librarybuilder等关键词搜索可用的 workload。构建命令形如dotnet publish -r android-arm64 # Android 共享库 dotnet publish -r ios-arm64 # iOS 共享库七、Android 集成实战使用dotnet publish -r android-arm64构建 mono library 后产物位于 binaries 目录例如library-mode-sample/ManagedProject/bin/Release/net8.0/android-arm64/Bundle/libManagedProject.so当 mono library 以共享库默认且启用 bundling 构建时按以下步骤在 Android 原生工程中使用1. 打开/创建 Android 原生工程Android Studio。2. 拷贝 mono library 到工程的jniLibs目录放在对应架构子目录下目录不存在则创建例如app/src/main/jniLibs/arm64-v8a/libManagedProject.so3. 通过 Java Native InterfaceJNI加载 mono library在app/src/main/cpp/下创建 C 模块。如果没有 C 模块选项则手动创建.cpp文件与CMakeLists.txt内容如下。C 文件#include jni.h extern C void SayHello(); extern C { JNIEXPORT void JNICALL Java_com_example_package name_MainActivity_SayHello(JNIEnv *env, jobject thiz) { SayHello(); } }CMakeLists.txtcmake_minimum_required(VERSION 3.22.1) project(your project) add_library( android_library_mode SHARED filename.cpp ) find_library( log-lib log ) target_link_libraries( android_library_mode ${log-lib} ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libManagedProject.so)4. 创建Copy Files构建阶段目标选择Frameworks并把 mono library 包含进去。5. 加载链接了 mono library 的库即上面.cpp文件产出的android_library_mode使用System.loadLibrary(your library)。6. 声明原生方法并调用public class MainActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); System.loadLibrary(android_library_mode); SayHello(); } public native void SayHello(); }7. 构建并运行 Android 应用验证改动是否生效。八、iOS 集成实战使用dotnet publish -r ios-arm64构建 mono library 后产物位于 binaries 目录例如library-mode-sample/ManagedProject/bin/Release/net8.0/ios-arm64/Bundle/libManagedProject.dylib当 mono library 以共享库默认且启用 bundling 构建时按以下步骤在 iOS 原生工程中使用1. 打开/创建 iOS 原生工程Xcode。2. 将 mono library 拷贝到工程根目录注意是拷贝不是创建引用。3. 进入工程的Build Phases选项卡确认 mono library 已包含在Link Binary With Libraries一节中。4. 创建Copy Files构建阶段目标选择Frameworks并包含 mono library。5. 进入工程的Build Settings选项卡把第 2 步放置 mono library 的目录加入Library Search Paths。例如放在根目录则填写$(PROJECT_DIR)。6. 在原生代码中实例化并调用内置到 mono library 的自定义托管代码。例如在main.m中声明void SayHello(void);并调用SayHello();。7. 构建并运行 iOS 应用验证改动是否生效。九、总结与边界说明Mono 库模式将运行时 AOT 编译托管代码 资源打包为一个原生库为移动原生应用打开了通往现代 .NET API 的大门。其技术要点可概括为UnmanagedCallersOnly提供原生→托管方向Direct P/Invoke 提供托管→原生方向mono runtime 自动初始化默认或自定义回调串联两者bundling 保证开箱即用的资源可用性而LibraryBuilder任务则负责导出符号校验、链接脚本生成与平台差异化编译。需要说明的适用前提该模式目前面向 Android / iOS / iOS Simulator / tvOS / tvOS Simulator / Mac Catalyst 等移动目标其余平台尚不支持见ValidateValidTargetOS库必须包含由[UnmanagedCallersOnly(Entrypoint ...)]导出的符号否则构建会直接失败使用 Direct P/Invoke 的精确控制direct-pinvokes/direct-pinvoke-lists需要配合staticAOT 选项自定义运行时初始化回调时线程安全的惰性初始化是调用方责任。如需进一步深入可在仓库中继续阅读LibraryBuilder.cs、autoinit.c、preloaded-assemblies.c、AppleBuild.targets 以及 AOT 编译器中的 direct P/Invoke 与 runtime-init-callback 选项实现aot-compiler.c。赞分享语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载相关推荐Mono 代码编写指南dotnet/runtime 仓库 Mono 运行时 C 代码贡献规范Mono 代码编写指南dotnet/runtime 仓库 Mono 运行时 C 代码贡献规范 本指南面向在 src/mono/mono https://li语言运行时标准库JIT编译编译器.NET runtime 仓库中 Mono on Android 运行时问题调试完全指南.NET runtime 仓库中 Mono on Android 运行时问题调试完全指南 本篇技术指南聚焦于 .NET runtime 仓库中 Mono 运行时语言运行时标准库JIT编译编译器.NET Runtime 在 Linux 上交叉编译原生库与托管库的完整指南.NET Runtime 在 Linux 上交叉编译原生库与托管库的完整指南 本篇基于 .NET Runtime 仓库的交叉编译文档讲解如何在 Linux 主语言运行时标准库JIT编译编译器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →