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

如何在DllMain钩子中获取DirectX函数地址?

解决方案:DLL内安全获取DirectX函数地址并完成Detours挂钩

核心问题分析

  1. DllMain是进程/线程初始化的关键入口,在此处阻塞等待或创建DirectX对象会触发死锁——因为DirectX初始化可能需要调用加载器锁,而DllMain本身持有加载器锁,导致互相等待。
  2. Detours的DetourTransactionCommit要求在创建目标线程的上下文或DllMain线程调用,但直接在DllMain操作DirectX又会失败,所以需要换一种异步初始化的思路。

可行方案步骤

1. 异步收集地址+APC跨线程执行挂钩

放弃在DllMain内阻塞等待,让初始化线程单独收集虚表地址,再通过Windows APC(异步过程调用)切换到DllMain线程执行Detours挂钩操作,既避免死锁,又满足Detours的线程上下文要求。

示例代码:

static HMODULE g_hModule = nullptr;
static volatile bool g_bAddressesCollected = false;
static void* g_pD3D9PresentAddr = nullptr;
static D3D9Present originalD3D9Present = nullptr;

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
    g_hModule = hinst;
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            // 禁用不必要的线程通知,减少开销
            DisableThreadLibraryCalls(hinst);
            // 启动地址收集线程,不阻塞DllMain
            std::thread(InitializeAddressCollection).detach();
            break;
    }
    return TRUE;
}

void InitializeAddressCollection()
{
    // 复用你原来的地址收集逻辑,只保存地址不挂钩
    if (HMODULE d3d9Module = GetModuleHandle("d3d9.dll"))
    {
        typedef IDirect3D9* (WINAPI* Direct3DCreate9Func)(UINT SDKVersion);
        Direct3DCreate9Func d3d9Create = (Direct3DCreate9Func)GetProcAddress(d3d9Module, "Direct3DCreate9");

        if (d3d9Create)
        {
            IDirect3D9* d3d9 = d3d9Create(D3D_SDK_VERSION);
            if (d3d9)
            {
                D3DPRESENT_PARAMETERS d3dpp = {};
                d3dpp.Windowed = TRUE;
                d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
                d3dpp.hDeviceWindow = GetDesktopWindow();

                IDirect3DDevice9* device = nullptr;
                if (SUCCEEDED(d3d9->CreateDevice(
                    D3DADAPTER_DEFAULT,
                    D3DDEVTYPE_HAL,
                    GetDesktopWindow(),
                    D3DCREATE_HARDWARE_VERTEXPROCESSING,
                    &d3dpp,
                    &device)))
                {
                    void* deviceVTable = *(void**)device;
                    // 保存Present函数的虚表地址
                    g_pD3D9PresentAddr = ((void**)deviceVTable)[17];
                    device->Release();
                }
                d3d9->Release();
            }
        }
    }

    g_bAddressesCollected = true;
    // 通过APC将挂钩操作投递到当前线程(DllMain线程)执行
    QueueUserAPC((PAPCFUNC)PerformHooks, GetCurrentThread(), 0);
}

void PerformHooks(ULONG_PTR dwParam)
{
    if (g_pD3D9PresentAddr)
    {
        originalD3D9Present = reinterpret_cast<D3D9Present>(g_pD3D9PresentAddr);
        // 执行Detours挂钩,此时在DllMain线程上下文,符合要求
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach((PVOID*)&originalD3D9Present, (PVOID)HookedD3D9Present);
        LONG r = DetourTransactionCommit();
        // 根据r值处理挂钩结果
    }
}

2. 替代方案:Hook目标进程的DirectX初始化入口

如果异步方案不适用,可以直接挂钩Direct3DCreate9或CreateDevice函数,在目标进程真正初始化DirectX设备时,动态获取虚表地址并执行挂钩,完全避开DllMain的限制。

示例代码:

typedef IDirect3D9* (WINAPI* Direct3DCreate9Func)(UINT SDKVersion);
Direct3DCreate9Func originalDirect3DCreate9 = nullptr;
typedef HRESULT (WINAPI* CreateDeviceFunc)(IDirect3D9*, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS*, IDirect3DDevice9**);
CreateDeviceFunc originalCreateDevice = nullptr;

IDirect3D9* WINAPI HookedDirect3DCreate9(UINT SDKVersion)
{
    IDirect3D9* pD3D9 = originalDirect3DCreate9(SDKVersion);
    if (pD3D9)
    {
        // Hook CreateDevice方法,获取设备创建时的虚表
        void* pVTable = *(void**)pD3D9;
        originalCreateDevice = reinterpret_cast<CreateDeviceFunc>(((void**)pVTable)[16]);
        
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach((PVOID*)&originalCreateDevice, (PVOID)HookedCreateDevice);
        DetourTransactionCommit();
    }
    return pD3D9;
}

HRESULT WINAPI HookedCreateDevice(IDirect3D9* pD3D9, UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface)
{
    HRESULT hr = originalCreateDevice(pD3D9, Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface);
    if (SUCCEEDED(hr) && ppReturnedDeviceInterface && *ppReturnedDeviceInterface)
    {
        // 此时获取真实设备的虚表地址,执行挂钩
        void* deviceVTable = *(void**)*ppReturnedDeviceInterface;
        D3D9Present originalPresent = reinterpret_cast<D3D9Present>(((void**)deviceVTable)[17]);
        
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach((PVOID*)&originalPresent, (PVOID)HookedD3D9Present);
        DetourTransactionCommit();
    }
    return hr;
}

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hinst);
        HMODULE d3d9Module = LoadLibrary("d3d9.dll");
        originalDirect3DCreate9 = (Direct3DCreate9Func)GetProcAddress(d3d9Module, "Direct3DCreate9");
        
        // 在DllMain中只Hook Direct3DCreate9,操作安全
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach((PVOID*)&originalDirect3DCreate9, (PVOID)HookedDirect3DCreate9);
        DetourTransactionCommit();
    }
    return TRUE;
}

关键注意事项

  • 永远不要在DllMain中执行阻塞操作、创建窗口或初始化DirectX,避免加载器锁死锁。
  • Detours的事务操作必须在目标线程的上下文中执行,APC是安全的跨线程执行方式。
  • DirectX虚函数索引可能因版本不同而变化,建议通过官方文档或offsetof宏确认索引,避免硬编码错误。

内容的提问来源于Stack Exchange,提问作者Ruan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:38:09