我怎样才能不断重复使用 HBITMAP 和 HDC?

How can I keep reusing HBITMAP and HDC continually?

出于性能原因,我正在尝试通过重新使用 HBITMAP 和 HDC 来使 HBIPMAP 正常工作。

这是我想做的一个小测试项目,目的是了解有关基于 CPU 的光栅化的更多信息。对于 window,我使用的是 SDL2。

如果我们注释掉下面的代码就可以工作:

DeleteDC(hdcMem);
hdcMem = CreateCompatibleDC(device);

找不到2018+年的例子

mBackBuffer 只是一个 Vector(DWORD)

void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
    // This is hacked code for an example.

    auto device = GetDC(hwnd);
    DWORD colorSize = 4;    // ARGB;

    // Create page section
    // https://docs.microsoft.com/en-us/windows/desktop/memory/creating-named-shared-memory
    HANDLE hMapFile;
    LPCTSTR pBuf;
    // https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createfilemappinga
    hMapFile = CreateFileMappingA
    (
        INVALID_HANDLE_VALUE,
        NULL,
        PAGE_READWRITE,
        0,
        width * height * colorSize,
        NULL
    );

    if (hMapFile == NULL)
    {
        return;
    }

    DWORD* buffer = (DWORD*)MapViewOfFile(
        hMapFile, 
        FILE_MAP_ALL_ACCESS, 
        0, 
        0, 
        width * height * colorSize
    );

    BITMAPINFOHEADER header;
    memset(&header, 0, sizeof(BITMAPINFOHEADER));
    // https://msdn.microsoft.com/en-us/02f8ed65-8fed-4dda-9b94-7343a0cfa8c1
    header.biSize = sizeof(BITMAPINFOHEADER);
    header.biWidth = width;
    header.biHeight = height;
    header.biPlanes = 1;
    header.biBitCount = 32;
    header.biCompression = BI_RGB;
    header.biSizeImage = width * height * sizeof(BYTE);
    header.biXPelsPerMeter = 0;
    header.biYPelsPerMeter = 0;
    header.biClrUsed = 0;
    header.biClrImportant = 0;


    tagBITMAPINFO bitmap;
    memset(&bitmap, 0, sizeof(tagBITMAPINFO));
    // https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/ns-wingdi-tagbitmapinfo
    tagRGBQUAD RGBQUAD;
    memset(&RGBQUAD, 0, sizeof(tagRGBQUAD));

    bitmap.bmiHeader = header;
    bitmap.bmiColors[0] = RGBQUAD;

    LPVOID p;
    // https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createdibsection
    auto hBitMap = CreateDIBSection
    (
        device,
        &bitmap,
        DIB_RGB_COLORS,
        &p,
        hMapFile,
        0
    );

    for (DWORD i = 0; i < width * height; ++i)
    {
        buffer[i] = 0xFF0000;
    }

    HDC hdcMem = CreateCompatibleDC(device);
    auto oldHBITMAP = (HBITMAP)SelectObject(hdcMem, hBitMap);

    BitBlt(
        device,
        0,
        0,
        width,
        height,
        hdcMem,
        0,
        0,
        SRCCOPY
    );

    DeleteDC(hdcMem);

    for (DWORD i = 0; i < width * height; ++i)
    {
        buffer[i] = 0;
    }

    hdcMem = CreateCompatibleDC(device);

    BitBlt(
        device,
        400,
        300,
        width,
        height,
        hdcMem,
        0,
        0,
        SRCCOPY
    );
}

输出是红色屏幕,但您应该看到右角的黑色部分。

您没有在此处编辑位图:

for (unsigned int i = 0; i < width * height; ++i)
{
    mBackBuffer[i] = 0;
}

只是填充数组,位图是从空值创建的。

这种做法是错误的。当目标 window 获得 WM_PAINT 时,您的所有工作都将被撤消。

始终使用 WM_PAINT 和 BeginPaint 以您想要的方式绘制 windows。

这里有几个问题,有些与位图无关。

当不再需要句柄时,GetDC 中的句柄应由 ReleaseDC 清理。

来自 CreateFileMapping 的句柄应由 CloseHandle 清理,MapViewOfFile 应由 UnmapViewOfFile 清理。

HBITMAP 句柄必须由 DeleteObject

清理

建议在 SelectObject 之后通过调用 SelectOject(hMemDC, oldHBitmap)

进行清理

如果您不恢复旧位图,并尝试删除 hMemDC,Windows 将无法完成请求,因为在设备上下文中选择了另一个位图。 Windows 将尝试修复此错误,但如果代码太复杂,可能会失败。

请注意,Windows 限制了 10,000 个 GDI 句柄。如果您不正确管理这些句柄,应用程序将很快崩溃。有关这些功能,请参阅 WinAPI 文档。如有疑问,请使用任务管理器监控您的程序 "GDI handles"。

解决这些问题后,代码应该会按预期工作,请参见下面的示例。

当然这只是为了演示。在现实世界的应用程序中,您可能希望将 HBITMAP 保存在堆中,而不是堆栈中,以及其他一些值。您希望尽量减少重复创建这些句柄。

如其他答案和评论中所述,绘画应响应 WM_PAINT,从 BeginPaint 获得 HDC(并使用 EndPaint 清理) .因此你应该避免 GetDC/ReleaseDC

void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
    auto hdc = GetDC(hwnd);
    auto hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
        width * height * sizeof(DWORD), NULL);

    auto buffer = (DWORD*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 
        width * height * sizeof(DWORD));

    BITMAPINFOHEADER biheader = { sizeof(biheader), width, height, 1, 32, BI_RGB };

    LPVOID bits;
    auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&biheader, DIB_RGB_COLORS,
        &bits, hMapFile, 0);

    for(int i = 0; i < width * height; ++i)
        buffer[i] = 0xFF0000;

    auto memdc = CreateCompatibleDC(hdc);
    auto oldhbitmap = SelectObject(memdc, hbitmap);

    BitBlt(hdc, 0, 0, width, height, memdc, 0, 0, SRCCOPY);
    for(int i = 0; i < width * height; ++i)
        buffer[i] = 0;
    BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);

    SelectObject(memdc, oldhbitmap); //<- ***EDIT***
    //oldhbitmap is selected in to memdc, now we can destroy hbitmap and memdc

    DeleteObject(hbitmap);
    DeleteDC(memdc);
    ReleaseDC(hwnd, hdc);
    UnmapViewOfFile(buffer);
    CloseHandle(hMapFile);
}

旁注,对常量值使用引用运算符 & 不会有任何好处。只需更改函数原型如下:

void createDeviceFromHWND(const HWND hwnd, const int width, const int height);

此外,这可以在没有 CreateFileMapping 的情况下完成,并使用如下所示的 buffer。只要 hbitmap 有效,buffer 就会有效。

void test(const HWND hwnd, const int w, const int h)
{
    auto hdc = GetDC(hwnd);

    //use the negative value of height, so bitmap bits are not upside-down
    BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };

    DWORD* buffer;
    auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS,
        (void**)&buffer, NULL, 0);

    auto memdc = CreateCompatibleDC(hdc);
    auto oldbmp = SelectObject(memdc, hbitmap);

    for(int i = 0; i < w * h; ++i) buffer[i] = 0xFF0000;
    BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);

    //draw black square on top-left
    for(int y = 0; y < 100; y++)
        for(int x = 0; x < 100; x++)
            buffer[y * w + x] = 0;
    BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);

    //cleanup:
    SelectObject(memdc, oldbmp);
    DeleteObject(hbitmap); //<- buffer is not valid after hbitmap is destroyed
    DeleteDC(memdc);
    ReleaseDC(hwnd, hdc);
}