资讯动态

8、【MOD开发】【骑砍2】跨平台MOD构建环境(八)/:游戏根 bin 与模块 bin——运行程序集的两种归属

发布时间:2026/10/30 2:29:50 来源:尧图企业网站定制
【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除8、【MOD开发】【骑砍2】跨平台MOD构建环境八/游戏根 bin 与模块 bin——运行程序集的两种归属背景上篇 【MOD开发】【骑砍2】跨平台MOD构建环境七/参考程序集 vs 运行程序集——两套 DLL 的分工 把一对容易混淆的概念拆开了运行程序集包含元数据、ILIntermediate Language中间语言和资源能够真正运行参考程序集只保留公开 API 的元数据方法体被剥离或替换成占位不能直接运行。上篇还给出两条实践结论编译期使用参考程序集运行期由游戏提供同名的运行程序集Privatefalse/Private阻止游戏 DLL 被复制进工程输出避免“用旧 DLL 编新代码”的假通过。不过上篇把游戏安装目录里的 DLL 统一归入运行程序集并列出了两个路径游戏安装目录/bin/Win64_Shipping_Client/*.dll 游戏安装目录/Modules/模块/bin/Win64_Shipping_Client/*.dll这两个路径里的 DLL 确实都是运行程序集但它们的归属、加载时机和依赖方向并不相同。上篇没有展开这层差异本篇继续把运行程序集拆成两种归属。骑砍2 先给结论维度结果用途共同点都是运行程序集都含元数据与 IL能参与运行根 bin游戏宿主与引擎的启动基础目录提供始终存在的核心 API 与原生依赖模块 bin单个模块的作用域目录承载该模块自己的程序集与私有依赖一句话概括根 bin 管启动模块 bin 管模块两者不是参考程序集与运行程序集的区别而是运行程序集的两种归属。 根 bin宿主与启动层根 bin 位于游戏可执行文件旁边游戏安装目录/bin/Win64_Shipping_Client/它是 .NET 应用的基础目录也是宿主程序集解析的默认来源。游戏启动链路需要的核心程序集从这里就绪例如TaleWorlds.Core.dll、TaleWorlds.CampaignSystem.dll、TaleWorlds.MountAndBlade.dll以及负责模块加载的TaleWorlds.ModuleManager.dll。根 bin 不只包含托管程序集。本次检查的 1.5.4 环境里根 bin 共有 85 个 DLL其中 57 个是托管程序集28 个是原生 DLL。原生部分包括 PhysX、Steam、EOS、音频和图形相关依赖它们和 C# 程序集一起构成游戏宿主。图 1 游戏根 bin 与模块 bin 的目录分工需要特别区分一个常见误解根 bin 不等于“全部引擎程序集”。例如TaleWorlds.MountAndBlade.GauntletUI.dll、TaleWorlds.MountAndBlade.View.dll位于Modules/Native/bin/Win64_Shipping_Client/。引擎的核心 API 在根 bin部分视图层和内容层程序集则按模块归属。因此根 bin 的边界不是“所有 TaleWorlds 开头的 DLL”而是启动宿主需要、且不依赖具体模块激活的程序集。 模块 bin模块作用域模块 bin 位于模块自己的目录下游戏安装目录/Modules/模块/bin/Win64_Shipping_Client/它不是社区 Mod 专用目录。Native、SandBox、StoryMode、NavalDLC、Multiplayer这些官方模块同样使用该路径。官方模块与社区模块的差别在ModuleType和发布者不在 bin 的位置。模块 bin 的内容由模块清单决定。以Treasury为例SubModule.xml在Modules/Treasury/SubModule.xml:26-36声明了 DLL 名称和入口类SubModulesSubModuleNamevalueTreasurySubModule/DLLNamevalueTreasury.dll/SubModuleClassTypevalueTreasury.TreasurySubModule//SubModule/SubModulesDLLName指向模块 bin 下的主程序集SubModuleClassType指向模块加载后要实例化的入口类型。模块目录还可以携带自己的依赖例如RTSCamera的 bin 中同时存在0Harmony.dll、MissionLibrary.dll和RTSCameraAgentComponent.dll。这类依赖与根程序集不同它们只服务于当前模块或者服务于一组需要一起加载的模块。把它们放进根 bin会让模块依赖变成全局存在破坏模块启用与禁用之间的边界。 本次安装的实测对比本次检查的安装环境包含原版模块、NavalDLC 和若干社区模块统计结果如下维度结果用途根 bin 文件数85其中 57 个托管程序集、28 个原生 DLL模块 bin 文件数61全部为托管程序集两边同名 DLL0未发现根 bin 与模块 bin 的文件名重叠模块 bin 的组成官方模块 社区模块说明该路径不是 Mod 专属“没有同名重叠”这一点很关键。它说明两个目录不是同一批 DLL 的复制关系而是两套不同的程序集集合。根 bin 提供宿主能力模块 bin 提供模块能力。这些数字会随游戏版本、DLC 和已安装 Mod 变化不能当作固定常量但“根 bin 是宿主基础目录、模块 bin 是模块作用域目录”这个结构关系不会变。 模块加载链路模块加载从可执行文件开始经过根 bin 进入模块管理器再读取模块清单。图 2 从启动到模块 DLL 的加载链路TaleWorlds.ModuleManager的元数据中有ModuleInfo、SubModuleInfo、GetSortedModules、GetActiveGameAssemblies等成员。ModuleInfo保存模块的FolderPath、SubModules、DependedModulesSubModuleInfo保存DLLPath、DLLName和SubModuleClassTypeName。加载链路可以概括为Bannerlord.exe → 根 bin 的核心程序集就绪 → ModuleManager 扫描 Modules → 读取 SubModule.xml → 按 DependedModules / ModulesToLoadAfterThis 排序 → 激活模块并加载模块 bin 中的 DLL根程序集先就绪模块程序集只在对应模块被激活后进入加载集合。模块清单中的依赖声明决定加载顺序也决定某个模块能否在缺少前置模块时启动。Treasury的SubModule.xml:8-25声明了Native、SandBoxCore、Sandbox、CustomBattle、StoryMode依赖并给Bannerlord.Harmony、Bannerlord.ButterLib、Bannerlord.MCM设置了可选的LoadBeforeThis顺序。模块管理器据此排序而不是按文件夹名的字典序加载。 引用方向决定依赖边界将根 bin 与模块 bin 中的程序集拆开看引用方向是单向的模块程序集引用根程序集根程序集不反向引用具体模块。图 3 模块程序集引用根程序集反向不成立抽查结果可以说明这一点维度结果用途TaleWorlds.CampaignSystem.dll引用TaleWorlds.Core、TaleWorlds.Library等根程序集核心系统不依赖SandBoxSandBox.dll引用TaleWorlds.CampaignSystem、TaleWorlds.MountAndBlade等根程序集内容模块依赖宿主 APIRTSCamera.dll引用根程序集同时引用SandBox、0Harmony、MissionLibrary模块可以使用其他模块和私有依赖这个方向是工程上的必然选择。引擎不可能在编译期知道未来会安装哪些模块因此根程序集不能引用模块程序集模块则可以引用稳定的宿主 API并在清单中声明自己需要的前置模块。反过来如果把模块 DLL 放进根 bin宿主仍然不会自动加载它没有模块清单就没有DLLName和SubModuleClassType的绑定关系即使因为其他引用被加载也会绕过模块启用状态和依赖排序。⚠️ 同名 DLL 与程序集身份模块 bin 可以携带私有依赖但这不意味着可以直接复制引擎 DLL。模块之间还出现了两组同名程序集0Harmony.dll出现在RTSCamera、RTSCamera.CommandSystem、UnlimitedClanLimits三个模块里三份文件哈希相同TaleWorlds.MountAndBlade.Multiplayer.dll出现在CustomBattle和Multiplayer两个模块里文件名、大小和版本都相同但 SHA-256 不同。后一组更能说明问题**同名、同版本不等于同一份内容。**仅凭文件名或版本号判断程序集身份并不可靠。抽查的 TaleWorlds 程序集没有强名称PublicKeyToken为空版本统一为1.0.0.0。在这样的前提下同名程序集一旦进入同一个加载上下文通常只能保留一份最终使用哪一份会受加载顺序影响。如果两份程序集进入不同的加载上下文还会出现更隐蔽的问题类型名称看起来相同但类型身份不同跨上下文传递对象时可能触发类型转换失败。表现形式可能是TypeLoadException、MissingMethodException或InvalidCastException而不是直观的“DLL 找不到”。因此模块 bin 可以放模块自己的依赖但不应该复制TaleWorlds.*根程序集。引擎 DLL 由游戏安装提供模块只引用其公开 API。 正确的产物放置规则根据上面的边界Mod 产物的放置规则可以整理成一张表维度结果用途Mod 主程序集Modules/模块/bin/Win64_Shipping_Client/由模块清单加载SubModule.xml模块根目录声明依赖、DLL 与入口类TaleWorlds.*不复制使用游戏根 bin 提供的运行时程序集第三方依赖模块 bin 或独立依赖模块跟随模块发布避免污染根 bin根 bin不修改由游戏自身维护随更新覆盖编译阶段继续使用参考程序集并通过Privatefalse/Private阻止引擎引用被复制到输出目录。这样产出的 ZIP 只包含 Mod 自己的 DLL、SubModule.xml和资产文件部署时直接解压到Modules/模块/。如果 Mod 依赖 Harmony、MCM 或某个前置模块正确做法是声明依赖并按该依赖自身的发布方式安装而不是把依赖 DLL 丢进根 bin。根 bin 是共享的启动目录任何额外写入都可能影响其他模块也会在游戏更新或完整性校验时被覆盖。 小结两个路径里的 DLL 都是运行程序集不是参考程序集。根 bin 是宿主与启动基础目录包含核心托管程序集和原生依赖。模块 bin 是模块作用域目录官方模块和社区 Mod 都使用它。模块程序集引用根程序集根程序集不反向引用具体模块。同名同版本的程序集也可能内容不同不能把文件名或版本号当作唯一身份。Mod 主程序集放模块 bin引擎 DLL 不复制依赖按模块方式交付。一句话记忆根 bin 提供宿主模块 bin 提供模块编译看参考程序集运行看两套运行程序集。OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog【MOD开发】【骑砍2】运行内存OOM 现场与虚拟内存术语

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

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

免费获取报价 →
↑