7、【MOD开发】【骑砍2】跨平台MOD构建环境(七)/:参考程序集 vs 运行程序集——两套 DLL 的分工

发布时间:2026/10/20 2:18:54
7、【MOD开发】【骑砍2】跨平台MOD构建环境(七)/:参考程序集 vs 运行程序集——两套 DLL 的分工
【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除7、【MOD开发】【骑砍2】跨平台MOD构建环境七/参考程序集 vs 运行程序集——两套 DLL 的分工背景上篇 【MOD开发】【骑砍2】跨平台MOD构建环境六/DLL 里到底装了什么——元数据、IL 与 JIT 把几个底层概念拆开了DLL 是容器元数据描述 APIIL 描述方法体JIT 在运行时把 IL 编译成机器码。这一篇回到构建环境里最常遇到的一对概念参考程序集与运行程序集。它们经常同名、同样是.dll但内容、用途、出现的位置都不一样。第二篇已经介绍过参考程序集这一篇把两者的分工讲透并和GamePath、Privatefalse/Private、元数据工具对应起来。骑砍2 一句话区分运行程序集元数据 IL 资源能真正跑起来游戏安装目录里的 DLL。参考程序集只有元数据公开 API方法体被剥离/替换成占位不能运行NuGet refs / targeting pack。两者都是.dll区别在内容不在扩展名。 运行程序集真正跑起来的 DLL运行程序集也叫实现程序集implementation assembly就是游戏运行时真正加载的 DLL。它包含元数据类型、成员、签名、特性、引用IL每个方法的具体指令资源字符串、图片等。在 Windows 上它们通常位于游戏安装目录/bin/Win64_Shipping_Client/*.dll 游戏安装目录/Modules/模块/bin/Win64_Shipping_Client/*.dll游戏启动、加载模块、调用某个方法时CLR/JIT 会读这些 DLL 里的 IL把它编译成当前 CPU 的机器码。运行程序集是 JIT 的输入。我们自己编译出来的 MOD DLL 也是运行程序集它包含 IL会被游戏加载再由 JIT 编译执行。 参考程序集只给编译器看的 DLL参考程序集reference assembly是专门给编译器用的 DLL。它通常来自NuGet 包例如bannerlord.referenceassemblies.*.NET 的 targeting pack / reference pack构建环境自己装配出来的“伪游戏目录”。它只保留公开 API 的元数据有哪些类型、成员、签名哪些是 public / protected方法参数和返回类型是什么。方法体被剥离或替换成不可执行的占位不同工具产物略有差异所以它不能运行通常比运行程序集小很多不需要加载依赖就能被读取。在这套环境里参考程序集通常放在vendor/game-refs/游戏版本/bin/Win64_Shipping_Client/*.dll output/refs/游戏版本/bin/Win64_Shipping_Client/*.dll注意它们和游戏安装目录里的运行程序集同名比如都叫TaleWorlds.Core.dll但来源和内容不同不能混用。 对比表维度参考程序集运行程序集用途编译期运行期内容元数据公开 API元数据 IL 资源方法体无 / 占位有 IL能否运行不能能来源NuGet refs / targeting pack游戏安装目录 / 发布包大小小大喂给谁编译器CLR / JIT 一个类比菜单 vs 完整菜谱把程序集想成一本菜谱参考程序集 菜单有哪些菜、配料表、价格编译器只需要知道“有没有这道菜、参数是什么”。运行程序集 完整菜谱 厨房真正做菜时要看做法运行时才需要 IL。也可以类比成 C/C 的“头文件 vs 实现”但要注意C# 的参考程序集仍然是程序集有元数据、有 PE 结构不是纯文本头文件。类比只是为了帮助理解“声明与实现分离”。 为什么编译用参考程序集编译器做类型检查只需要“类型和成员的签名”不需要方法体。用参考程序集至少有几个好处快不用加载依赖也不用解析 IL干净只暴露公开 API不会让代码意外依赖私有实现版本清晰编译期对着哪个版本的 refs就检查哪个版本的 API跨版本换游戏版本时换一套 refs 重新编译即可不用把整个游戏装到构建机避免“假通过”不会因为旧的运行程序集被复制进bin/而用旧 DLL 编新代码。这里要特别提Privatefalse/Private它让游戏 DLL 引用不复制到工程的bin/。如果复制了旧的运行程序集编译器可能拿它当引用来源于是“用旧 DLL 编新代码”也能通过真实的 API 变更就被掩盖了。参考程序集本身也不应该被复制进 MOD 输出——它只是编译期的“合同”。一句话编译看合同参考程序集运行看实现运行程序集。 在这套构建环境里怎么对应把前面的概念落到这套构建环境里环节用哪套 DLL说明编译参考程序集-p:GamePathrefs/指向“伪游戏目录”引用复制不复制Privatefalse/Private避免旧 DLL 进bin/元数据查询参考程序集第五篇的meta.sh只读元数据不需要 IL运行运行程序集游戏加载自己的 DLL再 JIT 编译MOD 产物运行程序集我们编译出的 DLL 也含 IL由游戏加载并 JIT用一张图表示编译期 参考程序集元数据 │ 编译器 ▼ Mod.dll元数据 IL 运行期 游戏运行程序集元数据 IL ┐ ├─ CLR / JIT ──► 机器码 Mod.dll元数据 IL ┘MOD DLL 在编译时引用的是参考程序集的“身份”程序集名、版本、类型签名运行时游戏提供同名的运行程序集JIT 再把双方的 IL 编译成机器码。 怎么自己验证想确认一份 DLL 是参考程序集还是运行程序集可以看几个地方看大小参考程序集通常明显更小看方法体用 ILSpy / ildasm 打开运行程序集能看到 IL参考程序集没有方法体或只有占位看来源vendor/game-refs/ver、output/refs/ver下的是 refs游戏安装目录bin/Win64_Shipping_Client下的是运行程序集看特性有些参考程序集带[assembly: ReferenceAssembly]但不是所有都有用元数据工具meta.sh只读元数据两套都能读它看到的是 API不是行为。⚠️ 常见误区“参考程序集不是 DLL”不对它也是 DLL只是被裁剪过。“DLL 一定可执行”不对参考程序集就不能运行。“运行时也需要参考程序集”不需要运行时用游戏自己的运行程序集。“编译必须用运行程序集”不一定参考程序集就是专门给编译器用的。“同名 DLL 就是同一个”不对同名但来源不同内容可能完全不同。“用 refs 编译运行时就不用管版本了”不对运行时必须提供匹配的运行程序集API 变更仍可能导致运行期问题。 本篇小结运行程序集 元数据 IL 资源能运行游戏安装目录里的 DLL。参考程序集 只有元数据公开 API方法体剥离/占位不能运行NuGet refs / targeting pack。两者都是.dll同名不同源区别在内容。编译用参考程序集运行用运行程序集GamePath指向 refsPrivatefalse/Private避免旧 DLL 复制进bin/。元数据工具只读元数据所以用参考程序集就够了。一句话记忆编译看参考程序集运行用运行程序集同名不同源都是 DLL。到这里DLL、元数据、IL、JIT、参考程序集、运行程序集这条概念链就闭合了。后面如果再展开崩溃日志判读、产物部署、跨版本移植复盘这些概念会一直用得上。OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog