Harmony 补丁优先级与执行顺序控制:HarmonyPriority、HarmonyBefore 与 HarmonyAfter 实战指南

发布时间:2026/10/7 16:11:43
Harmony 补丁优先级与执行顺序控制:HarmonyPriority、HarmonyBefore 与 HarmonyAfter 实战指南 开发工具【免费下载链接】HarmonyA library for patching, replacing and decorating .NET and Mono methods during runtime项目地址https://gitcode.com/gh_mirrors/ha/Harmony点击查看免费下载HarmonyHarmonyLib在运行时以 IL 重写的方式为 .NET/Mono 方法注入 Prefix、Postfix、Transpiler 与 Finalizer 补丁。当多个插件Mod同时补丁同一方法时它们的执行顺序并不是简单的先注册先执行而是由一套优先级 依赖关系的排序机制决定。本文基于 Harmony/Documentation/articles/priorities.md 展开结合仓库中的源码实现Priority.cs、Attributes.cs、PatchSorter.cs与排序测试PatchSorting.cs系统讲解三种排序注解的语义、优先级常量体系、底层排序算法以及如何用它解决谁最后修改返回值这一经典冲突。核心思想补丁顺序不是线性的在 Harmony 中补丁的执行顺序并不由加载顺序决定。一个最后加载的插件/Mod其补丁依然可以排在最先执行的位置。为了实现这一点补丁需要通过方法注解annotation显式声明自己的排序意图。Harmony 提供了三个排序注解[HarmonyPriority(int)]设置该 Prefix/Postfix 的优先级。默认值为Priority.Normal即 400。[HarmonyBefore(string[])]声明该补丁应早于参数中列出的所有 Harmony ID 所注册的补丁执行。[HarmonyAfter(string[])]声明该补丁应晚于参数中列出的所有 Harmony ID 所注册的补丁执行。这三个注解在源码中的定义位于 Harmony/Public/Attributes.cs其AttributeUsage均为AttributeTargets.Class | AttributeTargets.Method即既可以标注在补丁方法上也可以标注在补丁类上见下文类级注解一节。它们都继承自HarmonyAttribute最终把值写入HarmonyMethod对应的priority、before、after字段见 Harmony/Public/HarmonyMethod.cs。优先级常量体系从 Last 到 First[HarmonyPriority(int)]接受任意整数值数值越大越先执行。仓库在 Harmony/Public/Priority.cs 中预定义了一组语义化常量方便开发者直接引用常量值语义Priority.Last0最后执行Priority.VeryLow100非常低Priority.Low200低Priority.LowerThanNormal300低于普通Priority.Normal400普通默认值Priority.HigherThanNormal500高于普通Priority.High600高Priority.VeryHigh700非常高Priority.First800最先执行关键事实Priority.Normal的默认值是 400这是注解默认优先级。在 Patch.cs 的构造函数中可以看到当priority -1即未显式声明HarmonyMethod.priority的默认值时会被归一为Priority.Normal。也就是说只要没有显式标注优先级所有补丁都处于同一档 400。同一优先级下的排序后注册者胜出当多个补丁优先级相同时Harmony 如何决定顺序答案藏在排序比较器 PatchJsonConverter.cs 中的PriorityComparer实现里if (priority ! theirPriority) return -(priority.CompareTo(theirPriority)); // 优先级高的排前面 return index.CompareTo(theirIndex); // 同优先级按注册序号排也就是说排序先比较priority降序优先级相同则比较index升序即注册顺序。这带来一个重要的实战推论同一优先级下后注册的 Postfix 排在前面、后执行因此它可以覆盖先注册 Postfix 对返回值的修改——这正是原文档示例所演示的场景。示例两个插件争夺返回值给定如下目标方法完整示例见 Harmony/Documentation/examples/priorities.csclass Foo { static string Bar() secret; }插件 1examples/priorities.csvoid Main_Plugin1() { var harmony new Harmony(net.example.plugin1); harmony.PatchAll(Assembly.GetExecutingAssembly()); } [HarmonyPatch(typeof(Foo))] [HarmonyPatch(Bar)] class MyPatch { static void Postfix(ref string result) result new secret 1; }插件 2examples/priorities.csvoid Main_Plugin2() { var harmony new Harmony(net.example.plugin2); harmony.PatchAll(Assembly.GetExecutingAssembly()); } [HarmonyPatch(typeof(Foo))] [HarmonyPatch(Bar)] class MyPatch { static void Postfix(ref string result) result new secret 2; }两个插件都未声明优先级默认同为Priority.Normal且插件 2 后注册。按照同优先级按 index 升序执行的规则插件 1 的 Postfix 先执行插件 2 的 Postfix 后执行后执行的会覆盖ref string result的修改。因此调用Foo.Bar()返回new secret 2——后注册的 Postfix 覆盖了先注册者改写的结果。用 HarmonyAfter 挽回执行顺序作为插件 1 的作者若你希望自己的 Postfix 在插件 2 之后执行从而最后修改返回值只需重写为examples/priorities.csvoid Main_Plugin1b() { var harmony new Harmony(net.example.plugin1); harmony.PatchAll(Assembly.GetExecutingAssembly()); } [HarmonyPatch(typeof(Foo))] [HarmonyPatch(Bar)] class MyPatch { [HarmonyAfter([net.example.plugin2])] static void Postfix(ref string result) result new secret 1; }[HarmonyAfter([net.example.plugin2])]声明该补丁应晚于Harmony ID 为net.example.plugin2的所有补丁执行。由于插件 2 的 Postfix 先执行、插件 1 的 Postfix 后执行现在Foo.Bar()会返回new secret 1插件 1 重新夺回了最后修改返回值的权利。注意HarmonyBefore/HarmonyAfter参数中的 ID 是创建Harmony实例时传入的标识字符串即new Harmony(net.example.plugin1)中的net.example.plugin1而不是类名或方法名。在 PatchSorter.cs 中可以看到依赖关系正是通过node.innerPatch.before/after.Contains(x.innerPatch.owner)来匹配的owner就是该 Harmony ID。用 HarmonyPriority 达到同样的效果原文档还给出了另一个等价方案插件 1 也可以改用[HarmonyPriority(Priority.Low)]标注自己的 Postfix使其优先级低于默认的Priority.Normal400从而排在插件 2 之后执行。两种方式都可行选择依据是跨插件协作、语义清晰优先使用HarmonyBefore/HarmonyAfter直接表达我要在谁之前/之后只关心相对先后、不关心具体插件使用HarmonyPriority数值比较即可。类级注解一次声明整类生效三个优先级注解不仅可用于补丁方法同样可标注在补丁类上一次性为类内所有补丁方法定义默认优先级。例如[HarmonyPriority(Priority.High)] [HarmonyPatch(typeof(Foo))] [HarmonyPatch(Bar)] class MyPatch { static void Prefix() { /* 继承 High 优先级 */ } static void Postfix(ref string result) result new secret; }类上的注解与该类内方法上的注解遵循合并规则方法级注解会覆盖/叠加类级注解。合并逻辑见 HarmonyMethod.cs 的HarmonyMethod.Merge——注意源码注释中明确提到一个细节priority字段默认值为-1合并时会跳过值为-1的项因此类级HarmonyPriority会被方法级显式标注正确覆盖不会被未标注的方法级默认值冲掉。同理类级HarmonyBefore/HarmonyAfter与方法级声明也会合并为最终的before/after数组。底层实现PatchSorter 的依赖图排序理解注解只是第一步底层排序算法更能帮助你预判复杂场景下的行为。Harmony 在 Harmony/Internal/PatchSorter.cs 中实现了完整的排序流程可归纳为三步建图为每个补丁创建PatchSortingWrapper遍历before/after中的 Harmony ID与补丁列表中的owner双向匹配建立依赖关系AddBeforeDependency/AddAfterDependency见 PatchSorter.cs。初始排序所有补丁按PriorityComparer优先级降序、index 升序预排序此顺序在后续处理中尽量保持。拓扑输出从队列中不断取出所有 after 依赖都已处理的补丁输出若补丁因依赖未满足而无法输出则进入 waiting list待依赖解除后再处理。after依赖即必须等某人先执行before依赖会被反向注册为对方目标 owner 的补丁的after依赖从而自动满足我必须在某人之前。循环依赖的兜底处理如果多个补丁相互声明依赖形成循环例如 A after B、B after C、C after A排序器无法完全满足所有约束此时会调用CullDependency()PatchSorter.cs从 waiting list 中按优先级从低到高寻找第一个未解决的依赖并将其移除即打破这条依赖边从而保证排序总能收敛、不会死循环。开启调试时debug参数为 true会通过FileLog.LogBuffered记录Breaking dependance between ...日志。这一系列行为都被 HarmonyTests/Patching/PatchSorting.cs 中的测试覆盖验证例如Test_PatchOrder_AllPrioritiesPatchSorting.cs验证九个优先级常量从First到Last严格降序执行Test_PatchOrder_BeforeAndAfterAndPrioritiesPatchSorting.cs验证before、after与优先级混合时的期望顺序Test_PatchOrder_TransitiveBefore/Test_PatchOrder_TransitiveAfterPatchSorting.cs验证依赖具有传递性A before B、B before C 时A 会排在 C 之前Test_PatchCycle0~Test_PatchCycle3PatchSorting.cs覆盖了单循环、双循环、交叉循环等循环依赖场景的打破结果。结果缓存与补丁变更检测排序结果会缓存在sortedPatchArray中PatchSorter.cs下次对同一方法排序时直接复用。若补丁列表发生变化增删补丁、修改 priority/before/after/owner/indexComparePatchListsPatchSorter.cs会通过PatchDetailedComparer逐字段比对来使缓存失效Test_PatchSorterCache0PatchSorting.cs专门验证了这一点。排序规则速查与实践建议综合文档与源码可以将 Harmony 的补丁排序规则总结为一张速查表声明方式影响优先级比较同优先级时不声明默认Priority.Normal400与所有未声明者同档按注册顺序后注册者后执行[HarmonyPriority(N)]数值越大越先执行先按优先级降序按注册顺序[HarmonyAfter([id])]晚于指定 ID 的补丁执行依赖约束优先于数值优先级满足依赖后保持优先级序[HarmonyBefore([id])]早于指定 ID 的补丁执行反向注册依赖同理满足依赖后保持优先级序实践建议多插件协同、API 稳定的库/框架优先使用HarmonyBefore/HarmonyAfter声明依赖即使对方插件未来调整了优先级数值你的相对顺序也依然成立。同插件内部多个补丁直接用HarmonyPriority控制即可不必引入跨 ID 依赖。Postfix 想最后说话标注[HarmonyPriority(Priority.First)]或[HarmonyAfter([...])]Prefix 想最先拦截同理。避免无意义的循环依赖虽然排序器会自动打破循环但打破规则是从低优先级下手可能产生与你预期相反的最终顺序。类级与方法级混用时确认合并结果是否符合预期必要时显式标注方法级优先级。相关资源文档原文Harmony/Documentation/articles/priorities.md完整示例代码Harmony/Documentation/examples/priorities.cs优先级常量定义Harmony/Public/Priority.cs排序注解定义Harmony/Public/Attributes.cs排序算法实现Harmony/Internal/PatchSorter.cs排序行为测试HarmonyTests/Patching/PatchSorting.cs赞分享开发工具【免费下载链接】HarmonyA library for patching, replacing and decorating .NET and Mono methods during runtime项目地址https://gitcode.com/gh_mirrors/ha/Harmony点击查看免费下载相关推荐从零开始部署Hermes Agent企业级多环境实战指南从零开始部署Hermes Agent企业级多环境实战指南 Hermes Agent是一款功能强大的AI代理平台支持从云服务器到边缘设备的全方位跨平台部署方案AI Agent人工智能AI 应用工具调用Agent 记忆交互助手RAG任务调度MCP 服务为什么选择DeepSeek-V4-Flash-NVFP4AMD平台AI大模型部署的5大优势为什么选择DeepSeek V4 Flash NVFP4AMD平台AI大模型部署的5大优势 DeepSeek V4 Flash NVFP4是基于AMD平台优化Harmony项目中的补丁优先级机制详解Harmony项目中的补丁优先级机制详解 什么是Harmony补丁优先级 在Harmony项目中补丁的执行顺序不是简单的线性顺序。即使某个插件或模块最后加载开发工具上一篇终极 tus-js-client 完整指南如何实现可靠的文件断点续传功能下一篇5个理由告诉你为什么tssh是比传统ssh更智能的选择创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询