前言
處於某些研究目的,我們經常需要在dx11繪製的介面上繪製我們自己的操作選單,以方便進行一些視覺化操作;這裡面imgui庫因為其優越的可用性,健壯性和美觀性,得到了很多人的青睞。那麼我們應該如何在一個帶有dx的軟體介面上利用imgui繪製我們自己的介面呢?下面的程式碼就是為了解決這個問題的(採用dx11版本舉例)。
程式碼樣例
#include <windows.h>
#include <memory>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <d3d11.h>
#include "Imgui/imgui.h"
#include "Imgui/imgui_impl_win32.h"
#include "Imgui/imgui_impl_dx11.h"
#ifdef _DEBUG
#define LOG(_text, ...) printf(_text "\n", __VA_ARGS__)
#else
#define LOG(_text, ...)
#endif
namespace
{
/*
* 0 ProcessAttach NoCall
* 1 ProcessAttach Called
*/
LONG volatile g_iDllMainProcessAttachFlag = 0;
LONG volatile g_iDX11NeedInit = TRUE;
BOOL g_bInitSuccess = FALSE;
HWND g_GameHwnd = NULL;
IDXGISwapChain* g_pHookedSwapChain = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
ID3D11Device* g_pd3dDevice = nullptr;
ID3D11DeviceContext* g_pd3dDeviceContext = nullptr;
ID3D11RenderTargetView* g_mainRenderTargetView = nullptr;
}
void CleanupRenderTarget()
{
if (g_mainRenderTargetView) { g_mainRenderTargetView->Release(); g_mainRenderTargetView = nullptr; }
}
void CleanupDeviceD3D()
{
CleanupRenderTarget();
if (g_pHookedSwapChain) { g_pHookedSwapChain->Release(); g_pHookedSwapChain = nullptr; }
if (g_pSwapChain) { g_pSwapChain->Release(); g_pSwapChain = nullptr; }
if (g_pd3dDeviceContext) { g_pd3dDeviceContext->Release(); g_pd3dDeviceContext = nullptr; }
if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = nullptr; }
}
void CreateRenderTarget()
{
ID3D11Texture2D* pBackBuffer = nullptr;
g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
pBackBuffer->Release();
}
LRESULT CreateDeviceD3D(HWND hWnd)
{
// Setup swap chain
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 2;
sd.BufferDesc.Width = 0;
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
UINT createDeviceFlags = 0;
//createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
D3D_FEATURE_LEVEL featureLevel;
ID3D11Device* pd3dDevice = nullptr;
ID3D11DeviceContext* pd3dDeviceContext = nullptr;
const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };
HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pHookedSwapChain, &pd3dDevice, &featureLevel, &pd3dDeviceContext);
if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available.
res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pHookedSwapChain, &pd3dDevice, &featureLevel, &pd3dDeviceContext);
// We just need SwapChain, to get its virtual table and hook It!!
if (pd3dDeviceContext) { pd3dDeviceContext->Release(); pd3dDeviceContext = nullptr; }
if (pd3dDevice) { pd3dDevice->Release(); pd3dDevice = nullptr; }
return res;
}
// Forward declare message handler from imgui_impl_win32.cpp
typedef LRESULT(WINAPI* typedef_WndProc)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
typedef HRESULT(STDMETHODCALLTYPE* typedef_Present)(
/* [in] */ IDXGISwapChain* This,
/* [in] */ UINT SyncInterval,
/* [in] */ UINT Flags);
typedef HRESULT(STDMETHODCALLTYPE* typedef_ResizeBuffers)(
/* [in] */ IDXGISwapChain* This,
/* [in] */ UINT BufferCount,
/* [in] */ UINT Width,
/* [in] */ UINT Height,
/* [in] */ DXGI_FORMAT NewFormat,
/* [in] */ UINT SwapChainFlags);
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static typedef_WndProc Real_WndProc = nullptr;
static constexpr typedef_WndProc Mine_WndProc = [](HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT
{
LOG("Mine_WndProc Hooked!!");
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
return S_FALSE;
return Real_WndProc ? CallWindowProcW(Real_WndProc, hWnd, msg, wParam, lParam) : S_OK;
};
static typedef_Present Real_Present = nullptr;
static constexpr typedef_Present Mine_Present =
[](IDXGISwapChain* This, UINT SyncInterval, UINT Flags) -> HRESULT {
LOG("Mine_Present Hooked!!");
if (InterlockedCompareExchange(&g_iDX11NeedInit, 0, 1)) {
g_pSwapChain = This;
g_pSwapChain->GetDevice(__uuidof(g_pd3dDevice), (void**)&g_pd3dDevice);
g_pd3dDevice->GetImmediateContext(&g_pd3dDeviceContext);
CreateRenderTarget();
ImGui_ImplWin32_Init(g_GameHwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
}
// Start the Dear ImGui frame
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
ImGui::Begin("AHA");
ImGui::Text("Gugu!!");
ImGui::End();
ImGui::Render();
g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
return Real_Present(This, SyncInterval, Flags);
};
static typedef_ResizeBuffers Real_ResizeBuffers = nullptr;
static constexpr typedef_ResizeBuffers Mine_ResizeBuffers =
[](IDXGISwapChain* This,
UINT BufferCount,
UINT Width,
UINT Height,
DXGI_FORMAT NewFormat,
UINT SwapChainFlags) -> HRESULT {
LOG("Mine_ResizeBuffers Hooked!!");
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
CleanupDeviceD3D();
InterlockedCompareExchange(&g_iDX11NeedInit, 1, 0);
return Real_ResizeBuffers(This, BufferCount, Width, Height, NewFormat, SwapChainFlags);
};
BOOL SetupHook() {
Real_WndProc = reinterpret_cast<decltype(Real_WndProc)>(SetWindowLongPtrW(g_GameHwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Mine_WndProc)));
DWORD dwOldProt = PAGE_EXECUTE_READ;
if (!VirtualProtect(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain), 1, PAGE_EXECUTE_READWRITE, &dwOldProt)) {
return FALSE;
}
Real_Present = static_cast<decltype(Real_Present)>(InterlockedExchangePointer(reinterpret_cast<volatile PVOID*>(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain) + 8), Mine_Present));
Real_ResizeBuffers = static_cast<decltype(Real_ResizeBuffers)>(InterlockedExchangePointer(reinterpret_cast<volatile PVOID*>(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain) + 13), Mine_ResizeBuffers));
VirtualProtect(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain), 1, dwOldProt, &dwOldProt);
return TRUE;
}
void RemoveHook() {
DWORD dwOldProt = PAGE_EXECUTE_READ;
if (!VirtualProtect(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain), 1, PAGE_EXECUTE_READWRITE, &dwOldProt)) {
return;
}
InterlockedCompareExchangePointer(reinterpret_cast<volatile PVOID*>(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain) + 8), Real_Present, Mine_Present);
InterlockedCompareExchangePointer(reinterpret_cast<volatile PVOID*>(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain) + 13), Real_ResizeBuffers, Mine_ResizeBuffers);
VirtualProtect(*reinterpret_cast<uintptr_t**>(g_pHookedSwapChain), 1, dwOldProt, &dwOldProt);
if (Real_WndProc) {
SetWindowLongPtrW(g_GameHwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Real_WndProc));
}
}
void LibraryHandler_ProcAttach(HANDLE hModule) {
LOG("Attach Begin!!");
// 這裡要修改為你需要繪製的父視窗的類名,可透過spy++獲取
g_GameHwnd = FindWindowW(L"ImGui Example", NULL);
LRESULT res = CreateDeviceD3D(g_GameHwnd);
if (res != S_OK) {
LOG("CreateDeviceD3D Failed, res=0x%X", static_cast<unsigned int>(res));
CleanupDeviceD3D();
return;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
//// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer Backends
if (!SetupHook()) {
LOG("Setup Hook Failed");
}
LOG("Attach Init Success");
g_bInitSuccess = TRUE;
}
void LibraryHandler_ProcDetach(HANDLE hModule) {
LOG("Detach Begin!!");
if (!g_bInitSuccess) {
return;
}
RemoveHook();
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
CleanupDeviceD3D();
}
BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpvReserved) {
static FILE* g_stream = nullptr;
switch (dwReason) {
case DLL_PROCESS_ATTACH: {
if (0 == InterlockedCompareExchange(&g_iDllMainProcessAttachFlag, 1, 0)) {
#ifdef _DEBUG
AllocConsole();
freopen_s(&g_stream, "CONOUT$", "w+", stdout);
#endif
auto threadObj = std::thread([hModule]() -> void { LibraryHandler_ProcAttach(hModule); });
threadObj.detach();
}
break;
}
case DLL_PROCESS_DETACH: {
LibraryHandler_ProcDetach(hModule);
#ifdef _DEBUG
fflush(stdout);
fclose(g_stream);
#endif
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}