如何持续重复使用 HBITMAP 和 HDC?

How can I keep reusing HBITMAP and HDC continually?

本文关键字:HBITMAP HDC 何持续      更新时间:2023-10-16

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

这是一个小型测试项目,我想做一些关于基于 CPU 的光栅化的信息。 对于窗口,我使用 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://learn.microsoft.com/en-us/windows/desktop/memory/creating-named-shared-memory
HANDLE hMapFile;
LPCTSTR pBuf;
// https://learn.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://learn.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://learn.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
);
}

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

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

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

CreateFileMapping的手柄应该用CloseHandle清理,MapViewOfFile应该用UnmapViewOfFile清理。

HBITMAP手柄必须通过DeleteObject清理

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

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

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

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

这当然只是为了演示。在实际应用程序中,您可能希望将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);
}

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

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

您在此处编辑位图时:

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

只需填充数组,位图就是从中创建的,带有 null。