【问题标题】:How can I keep reusing HBITMAP and HDC continually?如何不断重复使用 HBITMAP 和 HDC?
【发布时间】:2018-08-21 13:45:53
【问题描述】:

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

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

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

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

我在 2018+ 年找不到任何例子。

mBackBuffer 只是一个向量(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
    );
}

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

【问题讨论】:

  • 这是 C 还是 C++?用适当的语言标记您的问题将有助于提高其知名度。

标签: c++ sdl-2 gdi


【解决方案1】:

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

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

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

HBITMAP句柄必须由DeleteObject清理

建议在SelectObject 之后调用SelectOject(hMemDC, oldHBitmap) 进行清理

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

请注意,Windows 为您提供了 10,000 个 GDI 句柄的限制。如果您没有正确管理这些句柄,应用程序将很快崩溃。有关这些函数,请参阅 WinAPI 文档。如有疑问,请使用任务管理器监控您的程序的“GDI 句柄”。

修复这些问题后,代码应该可以正常工作,请参见下面的示例。

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

正如在其他答案和 cmets 中所指出的,应该在响应 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);
}

旁注,使用引用运算符&amp; 获取常量值不会获得任何好处。只需将函数原型更改如下:

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);
}

【讨论】:

  • 谢谢!另外,不知道 const 和 reference。有什么理由我必须用 WM_PAINT 做所有这些吗?我可以 100% 保证我不会在我的项目中使用 WM_PAINT。
  • SelectObject 出现错误,请参阅***EDIT***。在普通的 Windows 编程中,应该响应WM_PAINT 来完成绘制。 SDL 自己处理WM_PAINT,所以也许你的代码没问题。 GetDC(hwnd) 似乎仍然是错误的。顺便说一句,我应该补充一点,引用运算符在某些情况下很有用,例如在foo(const std::string &amp;large_text) 中,它可以防止复制。我的意思是你不需要像int 这样的内置类型。
  • 在删除“hBitmap”后,我尝试从函数内部返回上一个示例的“缓冲区”,它在读取像素数据时工作得很好。我在这里泄漏内存吗?如果我尝试释放“缓冲区”,则会收到访问冲突错误。
  • @victor bufferCreateDIBSection 所有,请参阅文档。我们不直接删除,我们调用DeleteObject(hbitmap)。如果您仍然需要buffer,则不应删除hbitmap
  • 我在 msdn 上到处查看,在您回答之前没有找到有关缓冲区的任何信息。它说明了一些关于同步性和 GdiFlush 的事情,但我正在从另一个窗口截取屏幕截图,没有其他任何东西可以访问由 CreateDIBSection 创建的句柄,而是一个单一的函数,所以这对我来说并不重要。我认为我所做的相当于保留一个已被释放的指针:数据在那里,但它不是我的,任何事情都可能发生。
【解决方案2】:

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

使用 WM_PAINT 和 BeginPaint 始终按照您想要的方式绘制窗口。

【讨论】:

  • 另外,没有必要为每次绘制显式创建HBITMAP - 如果要绘制的数据在字节缓冲区中,则可以直接使用StretchDIBits
  • 我不知道这会在我身上滚雪球。对不起。我实际上是在使用 SDL2 创建窗口。这是一个做一些基于 CPU 的光栅化的项目。
  • @user954753:我为你添加了标签。我想我不会再有什么帮助了。虽然这个答案是“正确的”,但一旦出现实际使用该库的答案,它就需要消失。
  • 好的,谢谢。我实际上正在尝试使用 Matteo Italia 的建议来解决问题——但事实证明它很麻烦^^。
【解决方案3】:

您没有在此处编辑位图:

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

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

【讨论】:

  • 好的。是的。那么如何使用 mBackBuffer 直接编辑 HBITMAP 呢?我真的需要每次都创建一个 HBITMAP 吗? mBackBuffer 只是一个 Vector
  • 只需使用 GetObject (docs.microsoft.com/en-us/windows/desktop/api/wingdi/…) 调用即可使用您拥有的 HBITMAP 获取 BITMAP 结构并在其中编辑 bmBits。
  • 您可以创建一个共享内存页面,并在其上创建一个 DIBSection。然后您可以将它映射到内存中,并通过 GDI 和修改原始像素来使用它。但是,如果您只想对一些像素数据进行 blit,只需使用 StretchDIBits
  • @MatteoItalia 我想听听你提到的内容,听起来更像是一个渲染管道,我只需要编辑原始像素并将其 bitblt 到设备。
  • @MatteoItalia 我有一个按照您的建议工作的版本。每次bitblting后都需要DeleteDC吗?
猜你喜欢
  • 2015-09-08
  • 1970-01-01
  • 2023-03-29
  • 2021-07-28
  • 1970-01-01
  • 2013-04-25
  • 1970-01-01
  • 1970-01-01
  • 2020-05-08
相关资源
最近更新 更多