You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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接口):

  1. 定义函数指针类型
    匹配虚函数的调用约定(STDMETHODCALLTYPE即__stdcall),且成员函数需包含this指针作为第一个参数:

    #include <Shobjidl.h>
    #include <detours.h>
    
    // 定义原函数与钩子函数的指针类型
    typedef HRESULT(STDMETHODCALLTYPE* pfnGetSelectedItems)(
        IShellView* This,
        IShellItemArray** ppsai
    );
    
  2. 获取目标实例与虚表地址
    先获取IShellView的实例(可通过IShellBrowser::QueryActiveShellView等方式获取,需根据场景调整),再提取虚表:

    // 假设已获取到目标IShellView实例
    IShellView* pShellView = nullptr;
    // ... 此处添加获取pShellView的逻辑
    
    // 提取虚表:实例的第一个指针就是虚表指针
    void** vtable = *(void***)(pShellView);
    
  3. 定位目标函数在虚表中的位置
    虚表中的函数顺序与接口声明中虚函数的顺序一致。查看Shobjidl.h中IShellView的声明,确定GetSelectedItems的索引(例如,若它是接口中第N个虚函数,索引为N-1):

    // 替换为实际的索引值
    const int INDEX_GetSelectedItems = 12; 
    pfnGetSelectedItems TrueGetSelectedItems = (pfnGetSelectedItems)vtable[INDEX_GetSelectedItems];
    
  4. 实现钩子函数
    钩子函数需保留this指针参数,可在其中添加自定义逻辑,也可调用原函数:

    HRESULT STDMETHODCALLTYPE HookedGetSelectedItems(
        IShellView* This,
        IShellItemArray** ppsai
    ) {
        // 先调用原函数
        HRESULT hr = TrueGetSelectedItems(This, ppsai);
    
        // 添加自定义逻辑,比如修改返回结果或记录日志
        // ...
    
        return hr;
    }
    
  5. 使用Detours完成挂钩

    // 开始Detour事务
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    // 挂钩函数
    DetourAttach(&(PVOID&)TrueGetSelectedItems, HookedGetSelectedItems);
    // 提交事务
    LONG detourError = DetourTransactionCommit();
    
    if (detourError != NO_ERROR) {
        // 处理挂钩失败的情况
    }
    

关键说明

  • 虚表索引必须准确:需对照头文件中接口的虚函数声明顺序,索引从0开始计数。
  • 实例获取需匹配场景:不同的虚函数所属接口不同,获取实例的方式也不同(比如GetSelectedItems也可能属于其他shell接口,需根据实际场景调整)。
  • 调用约定必须一致:虚函数默认使用STDMETHODCALLTYPE,钩子函数的调用约定必须与原函数完全匹配,否则会导致栈溢出或崩溃。

内容的提问来源于stack exchange,提问作者Baris LaPaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 08:35:31