如何使用Detours挂钩类实例方法?虚拟函数编译报错求助
挂钩虚函数
GetSelectedItems的问题与解决方法 问题背景
需要挂钩Shobjdl_core.h中声明的虚函数GetSelectedItems,函数声明如下:
virtual HRESULT STDMETHODCALLTYPE GetSelectedItems( /* [out] */ __RPC__deref_out_opt IShellItemArray **ppsai) = 0;
尝试编写挂钩代码时编译失败,报错Error C2065 'GetSelectedItems': undeclared identifier,错误代码如下:
HRESULT(WINAPI* TrueSleep)(IShellItemArray** ppsai) = GetSelectedItems; // Detour function that replaces the Sleep API. // HRESULT WINAPI TimedSleep(IShellItemArray** ppsai) { return 0; }
需要挂钩类实例方法的实现示例。
解决方法:挂钩虚函数的核心思路
虚函数的调用依赖虚表(vtable),每个类实例的第一个指针指向其虚表。要挂钩虚函数,需先从目标实例中提取虚表,找到目标函数在虚表中的地址,再用Detours挂钩该地址。
步骤与代码示例
以下以IShellView接口的GetSelectedItems为例(该函数属于IShellView接口):
定义函数指针类型
匹配虚函数的调用约定(STDMETHODCALLTYPE即__stdcall),且成员函数需包含this指针作为第一个参数:#include <Shobjidl.h> #include <detours.h> // 定义原函数与钩子函数的指针类型 typedef HRESULT(STDMETHODCALLTYPE* pfnGetSelectedItems)( IShellView* This, IShellItemArray** ppsai );获取目标实例与虚表地址
先获取IShellView的实例(可通过IShellBrowser::QueryActiveShellView等方式获取,需根据场景调整),再提取虚表:// 假设已获取到目标IShellView实例 IShellView* pShellView = nullptr; // ... 此处添加获取pShellView的逻辑 // 提取虚表:实例的第一个指针就是虚表指针 void** vtable = *(void***)(pShellView);定位目标函数在虚表中的位置
虚表中的函数顺序与接口声明中虚函数的顺序一致。查看Shobjidl.h中IShellView的声明,确定GetSelectedItems的索引(例如,若它是接口中第N个虚函数,索引为N-1):// 替换为实际的索引值 const int INDEX_GetSelectedItems = 12; pfnGetSelectedItems TrueGetSelectedItems = (pfnGetSelectedItems)vtable[INDEX_GetSelectedItems];实现钩子函数
钩子函数需保留this指针参数,可在其中添加自定义逻辑,也可调用原函数:HRESULT STDMETHODCALLTYPE HookedGetSelectedItems( IShellView* This, IShellItemArray** ppsai ) { // 先调用原函数 HRESULT hr = TrueGetSelectedItems(This, ppsai); // 添加自定义逻辑,比如修改返回结果或记录日志 // ... return hr; }使用Detours完成挂钩
// 开始Detour事务 DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); // 挂钩函数 DetourAttach(&(PVOID&)TrueGetSelectedItems, HookedGetSelectedItems); // 提交事务 LONG detourError = DetourTransactionCommit(); if (detourError != NO_ERROR) { // 处理挂钩失败的情况 }
关键说明
- 虚表索引必须准确:需对照头文件中接口的虚函数声明顺序,索引从0开始计数。
- 实例获取需匹配场景:不同的虚函数所属接口不同,获取实例的方式也不同(比如
GetSelectedItems也可能属于其他shell接口,需根据实际场景调整)。 - 调用约定必须一致:虚函数默认使用
STDMETHODCALLTYPE,钩子函数的调用约定必须与原函数完全匹配,否则会导致栈溢出或崩溃。
内容的提问来源于stack exchange,提问作者Baris LaPaz
相关产品推荐
相关产品推荐

