C++20模板魔法:HookingAgent如何在编译期完成签名检查
游戏开发插件系统【免费下载链接】cp2077-archive-xlCyberpunk 2077 mod that allows you to expand game resources without conflicts.项目地址https://gitcode.com/GitHub_Trending/cp/cp2077-archive-xl点击查看免费下载在 Mod 开发中Hook 目标函数最容易犯的错误就是回调函数签名不匹配——参数少了、返回值写错往往要到运行时才崩溃。Cyberpunk 2077 资源扩展 Modcp2077-archive-xl的底层 Hook 框架Core::HookingAgent用 C20 模板 requires概念 部分特化把签名兼容性检查提前到了编译期签名写错代码根本编译不过。下面拆开 Detail.hpp 这套实现思路看看它是如何做到的。一、为什么需要编译期签名检查传统 Hook 库如 MinHook只关心地址给它一个指针和一个回调它不管你的回调签名对不对。但 Hook 的本质是用你的函数顶替原函数回调签名必须与被 Hook 的函数严格匹配Original流、或符合特定形态Before/After/Wrap流。这个项目的分层如下层文件职责Agent 层HookingAgent.hpp用户入口HookBeforeTTarget(cb)等模板 API检查层Detail.hppHookTraits/HookFlowTraits编译期签名匹配驱动层HookingDriver.hpp虚接口屏蔽具体 Hook 后端后端层MinHookProvider.cpp用 MinHook 完成真正的内存打点二、第一块积木RawFunc把地址 函数签名打包成类型一切的前提是目标函数的签名必须先以类型形式存在。RawFunc就是一个类型化的函数地址templateauto A, typename R, typename... Args class RawFuncA, R (*)(Args...) : public RawBase { ... };见 Raw.hpp项目里每个被 Hook 的函数都是这样声明的例如 EntitySpawner.hppconstexpr auto SpawnFromTemplate Core::RawFunc /* addr */ Red::AddressLib::EntitySpawner_SpawnFromTemplate, int32_t (*)(void*, void*, EntitySpawnerRequest*, ResourcePath);地址2509382878是一个跨版本可解析的 ID见 Library.hpp通过AddressResolver在运行时解析为真实地址。而函数指针类型R (*)(Args...)被编码进了模板参数——这就是后续签名检查的标准答案。三、第二块积木HookTraits用部分特化推导回调类型Detail.hpp 中的HookTraits是核心它做了两件事成员函数指针归一化。回调可能是普通函数指针、成员函数指针、或带operator()的 lambda/类。首层偏特化把成员指针和闭包统一降级为普通函数指针形式再递归到主特化templatetypename TRaw, typename TCallback struct HookTraits : HookTraitsTRaw, decltype(TCallback::operator()) {};对照 7 种合法签名形态。Hook 框架定义了 4 种流向HookFlowOriginal/Before/After/Wrap每种流向允许不同的回调签名。以Before为例允许IsArgsObservervoid(*)(Args...)观察原始参数IsBlindObservervoid(*)()盲挂不关心参数以及After特有的TailObserver拿到返回值、ResultWrapper改写返回值等判断全部是std::is_same_v的constexpr布尔值——纯编译期、零运行时开销static constexpr bool IsArgsObserver std::is_same_vCallable, ArgsObserverFuncTRawArgs...;注意这里还有一个细节按TRawRet是否为void分成两个特化Detail.hpp因为void函数本来就没有返回值观察者这类签名可用用requires std::is_void_vTRawRet区分避免误判。四、第三块积木requires约束把检查挂在入口上HookingAgent的每个 APIHook/HookBefore/HookAfter/HookWrap……都带requires约束HookingAgent.hpptemplatetypename TTarget, typename TCallback, HookRun TRun HookRun::Default requires Detail::HookFlowTraitsHookFlow::Before, TTarget, TCallback::IsCompatibleNonMember inline static auto HookBefore(TCallback aCallback)HookFlowTraits把当前流向允许哪些形态和回调属于哪类对齐Detail.hppstatic constexpr bool IsCompatible (TFlow HookFlow::Original IsOriginalCompatible) || (TFlow HookFlow::Before IsBeforeCompatible) || ...;于是检查链变成你调用HookBeforeF(cb)→ 编译器推导TTarget/TCallback→requires求值 → 若IsCompatible为false该重载直接不参与重载决议 → 编译失败。开发者得到的是没有匹配的重载而不是运行时的段错误。IsCompatibleMember/IsCompatibleNonMember两个变体进一步区分成员函数指针回调需要this绑定走实例方法构造HookWrapper时传入上下文对象普通函数指针回调则走static方法。两套 API 各自独立互不干扰。五、第四块积木HookWrapper抹平三种回调形态通过检查后回调会被包进HookWrapperDetail.hpp。它对三类形态各有一个特化templatetypename TCallback requires std::is_class_vTCallback class HookWrapperTCallback : public HookWrapperdecltype(TCallback::operator()) { ... };普通函数指针直接持有并转发成员函数指针持(回调, Context*)二元组调用时m_context-*m_callbacklambda/闭包类通过requires std::is_class_v匹配继承到函数指针版并做堆上生命周期管理。之后所有下游逻辑HookHandler::Handle的if constexpr分发都面对统一的operator()(Args...)接口。这就是检查在前、统一在后的典型模板设计。六、运行时部分模板只负责守门打点交给 Driver签名检查完成后HookHandler::Attach调用HookInstance::AttachDetail.hpp把处理函数和解析出的地址交给HookingDriver。项目里后端是 MinHook 封装的 MinHookProviderMH_CreateHookMH_EnableHook两步完成打点。驱动层是纯虚接口理论上可换成任意 Hook 后端而编译期检查逻辑完全不受影响——这正是分层的好处。实际使用就一行EntitySpawnerPatch.cppHookBeforeRaw::EntitySpawner::SpawnFromTemplate(OnSpawnEntity).OrThrow();HookBefore返回HookResult.OrThrow()在失败时抛出带地址的std::runtime_errorDetail.hpp。如果回调签名写错了你连这一行都编译不过。七、设计要点回顾这套实现的思路可以总结为四条签名即模板参数RawFuncA, R(*)(Args...)让地址 签名成为一个类型成为所有推导的锚点部分特化 std::is_same_v做形态匹配7 种合法回调形态全部变成constexpr bool零成本requires约束替代static_assert约束不满足时重载直接失效比事后报错更自然地融入重载决议Wrapper 归一化三种回调形态统一成同一接口下游if constexpr分发只依赖模板参数运行时分支全部编译期裁掉。如果你在给游戏写 Hook 框架这套RawFunc签名类型 →Traits匹配 →requires守门 →Wrapper归一化的四步走是一个非常值得抄的作业。核心代码量不到 600 行全部集中在 lib/Core/Hooking/ 与 lib/Core/Raw.hpp 中值得完整读一遍。赞分享游戏开发插件系统【免费下载链接】cp2077-archive-xlCyberpunk 2077 mod that allows you to expand game resources without conflicts.项目地址https://gitcode.com/GitHub_Trending/cp/cp2077-archive-xl点击查看免费下载相关推荐oneAPI TBB 约束 APIConstrained APIs深度解析C20 Concept 如何在编译期守护并行模板oneAPI TBB 约束 APIConstrained APIs深度解析C20 Concept 如何在编译期守护并行模板 导读 oneAPI Thr构建工具开发工具编译器Bruce面向ESP32设备的攻防测试固件一台掌机搞定WiFi、Sub-GHz与RFID实验Bruce面向ESP32设备的攻防测试固件一台掌机搞定WiFi、Sub GHz与RFID实验 Bruce是一款刷在ESP32设备上的开源固件把WiFi测试渗透测试网络安全嵌入式物联网模板与元编程现代C的编译时魔法模板与元编程现代C的编译时魔法 本文全面探讨了现代C中模板与元编程的核心技术从基础的函数模板和类模板概念入手深入解析了SFINAE机制、模板特化技教程上一篇Flare 路线图与社区参与指南6大未来功能规划 新手开源贡献完整教程下一篇cosmos-sdk 系统测试入门从查询、JSON 断言到 Genesis 与交易驱动的状态测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考