C++中获取Overlay窗口Device Context(HDC)持续失败问题求助
GetDC获取Overlay窗口HDC反复失败的问题排查与解决
问题描述
开发C++ overlay应用时,初期运行正常,但一段时间后GetDC(hwndOverlay)反复返回NULL,触发无限重建overlay窗口的逻辑。已确认窗口创建正常,无其他线程访问HDC,GetLastError()未返回有效错误码。
相关代码
主逻辑代码
HMODULE hModuleGlobal; DWORD overlayThreadId; HANDLE overlayThreadHandle; uintptr_t mainModule = NULL; bool visuals = false; HWND hwndOverlay; void DoMagic() { if (GetAsyncKeyState(VK_F1) & 1) { visuals = !visuals; PrintConsoleInfo(); } if (hwndOverlay) { HDC hdcOverlay = GetDC(hwndOverlay); if (hdcOverlay == NULL) { std::cout << "Error: unable to get overlay DC" << std::endl; std::cout << "Closing overlay thread..." << std::endl; PostThreadMessage(overlayThreadId, WM_QUIT, 0, 0); WaitForSingleObject(overlayThreadHandle, INFINITE); std::cout << "Destroying overlay window..." << std::endl; DestroyWindow(hwndOverlay); std::cout << "Recreating overlay window..." << std::endl; overlayThreadHandle = CreateThread(nullptr, 0, OverlayThread, hModuleGlobal, 0, &overlayThreadId); } if (visuals) Visuals((uintptr_t)GetModuleHandle(L"client.dll"), hdcOverlay); } if (!CheckIfIngame()) Sleep(1000); Sleep(10); } DWORD WINAPI OverlayThread(LPVOID lpParam) { HMODULE hModule = (HMODULE)lpParam; hwndOverlay = CreateOverlayWindow(hModule); if (!hwndOverlay) { MessageBox(NULL, L"Error creating overlay window", L"Error", MB_ICONERROR); return 1; } MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } std::cout << "Overlay thread exiting..." << std::endl; return 0; } void PrintConsoleInfo() { system("cls"); HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); COORD pos = { 0, 0 }; SetConsoleCursorPosition(hConsole, pos); std::cout << "[F1] Visuals: "; SetConsoleTextAttribute(hConsole, visuals ? 10 : 12); std::cout << (visuals ? "ON" : "OFF") << std::endl; SetConsoleTextAttribute(hConsole, 15); std::cout << std::endl << "Press END to exit" << std::endl; } DWORD WINAPI MainFunc(HMODULE hModule) { AllocConsole(); FILE* f; freopen_s(&f, "CONOUT$", "w", stdout); hModuleGlobal = hModule; overlayThreadHandle = CreateThread(nullptr, 0, OverlayThread, hModule, 0, &overlayThreadId); mainModule = (uintptr_t)GetModuleHandle(L"client.dll"); if (hModule) { PrintConsoleInfo(); while (!GetAsyncKeyState(VK_END) & 1) DoMagic(); } else { std::cout << "Main module not found, press ENTER to exit..." << std::endl; getchar(); } PostThreadMessage(overlayThreadId, WM_QUIT, 0, 0); WaitForSingleObject(overlayThreadHandle, INFINITE); fclose(f); FreeConsole(); FreeLibraryAndExitThread(hModule, 0); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)MainFunc, hModule, 0, nullptr)); case DLL_PROCESS_DETACH: break; } return TRUE; }
CreateOverlayWindow函数代码
HWND CreateOverlayWindow(HINSTANCE hInstance) { const wchar_t CLASS_NAME[] = L"OverlayClass"; WNDCLASSEX wc = {}; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0)); wc.lpszClassName = CLASS_NAME; RegisterClassEx(&wc); SECURITY_ATTRIBUTES securityAttributes; securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); securityAttributes.lpSecurityDescriptor = nullptr; securityAttributes.bInheritHandle = FALSE; HWND hwndOverlay = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE, CLASS_NAME, L"Overlay", WS_POPUP, 0, 0, 1920, 1080, nullptr, nullptr, hInstance, &securityAttributes); //SetLayeredWindowAttributes(hwndOverlay, 0, 128, LWA_ALPHA); SetLayeredWindowAttributes(hwndOverlay, RGB(0, 0, 0), 0, LWA_COLORKEY); ShowWindow(hwndOverlay, SW_SHOW); UpdateWindow(hwndOverlay); return hwndOverlay; }
运行输出
Error: unable to get overlay DC Closing overlay thread... Overlay thread exiting... Destroying overlay window... Recreating overlay window... Error: unable to get overlay DC Closing overlay thread... Overlay thread exiting... Destroying overlay window... Recreating overlay window... Error: unable to get overlay DC Closing overlay thread... Overlay thread exiting... Destroying overlay window... Recreating overlay window...
原因分析
- HDC资源泄漏:每次调用
GetDC后未调用ReleaseDC,系统HDC资源耗尽后,后续GetDC会返回NULL。 - 线程同步问题:全局变量
hwndOverlay被主线程和overlay线程同时读写,未加同步保护,可能出现句柄无效的情况。 - 窗口类重复注册:每次重建overlay线程都调用
RegisterClassEx,未检查类是否已注册,可能导致注册失败间接影响窗口创建。 - CreateWindowEx参数错误:最后一个参数传递的是
&securityAttributes,但该函数的最后一个参数是窗口创建数据(CREATESTRUCT),误用安全属性结构体可能导致窗口创建异常。 - 窗口重建逻辑缺陷:一旦
GetDC失败就销毁重建窗口,但未排查资源泄漏问题,导致重建后依然很快耗尽HDC资源。
解决办法
1. 修复HDC资源泄漏
在使用完HDC后必须调用ReleaseDC释放资源:
if (hwndOverlay) { HDC hdcOverlay = GetDC(hwndOverlay); if (hdcOverlay == NULL) { // 错误处理逻辑... } else { if (visuals) Visuals((uintptr_t)GetModuleHandle(L"client.dll"), hdcOverlay); ReleaseDC(hwndOverlay, hdcOverlay); // 新增:释放HDC } }
2. 加同步保护全局窗口句柄
使用临界区保护hwndOverlay的读写:
CRITICAL_SECTION csOverlay; // 在DLL_PROCESS_ATTACH时初始化 InitializeCriticalSection(&csOverlay); // 在DoMagic中访问hwndOverlay时加锁 EnterCriticalSection(&csOverlay); if (hwndOverlay) { // 原逻辑... } LeaveCriticalSection(&csOverlay); // 在OverlayThread中设置hwndOverlay时加锁 EnterCriticalSection(&csOverlay); hwndOverlay = CreateOverlayWindow(hModule); LeaveCriticalSection(&csOverlay); // 在DLL_PROCESS_DETACH时销毁临界区 DeleteCriticalSection(&csOverlay);
3. 避免重复注册窗口类
注册前检查类是否已存在:
HWND CreateOverlayWindow(HINSTANCE hInstance) { const wchar_t CLASS_NAME[] = L"OverlayClass"; // 检查窗口类是否已注册 WNDCLASSEX wc = {}; if (!GetClassInfoEx(hInstance, CLASS_NAME, &wc)) { // 未注册才进行注册 wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0)); wc.lpszClassName = CLASS_NAME; if (!RegisterClassEx(&wc)) { MessageBox(NULL, L"Failed to register overlay class", L"Error", MB_ICONERROR); return NULL; } } // 后续窗口创建逻辑... }
4. 修正CreateWindowEx参数
去掉错误的安全属性参数传递(不需要自定义安全属性时,最后一个参数传NULL):
HWND hwndOverlay = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOACTIVATE, CLASS_NAME, L"Overlay", WS_POPUP, 0, 0, 1920, 1080, nullptr, nullptr, hInstance, nullptr); // 最后一个参数改为NULL
5. 优化窗口重建逻辑
先尝试排查资源问题,而非直接销毁窗口:
if (hdcOverlay == NULL) { DWORD err = GetLastError(); std::cout << "Error: unable to get overlay DC, error code: " << err << std::endl; // 根据错误码处理,比如资源耗尽时先尝试释放所有可能的资源,而非直接重建窗口 }
内容的提问来源于stack exchange,提问作者kuhi
相关产品推荐
相关产品推荐

