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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 21:14:53