如何在DllMain钩子中获取DirectX函数地址?
解决方案:DLL内安全获取DirectX函数地址并完成Detours挂钩
核心问题分析
DllMain是进程/线程初始化的关键入口,在此处阻塞等待或创建DirectX对象会触发死锁——因为DirectX初始化可能需要调用加载器锁,而DllMain本身持有加载器锁,导致互相等待。- 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
相关产品推荐
相关产品推荐

