C++技术问题:新建窗口无法显示截取的屏幕截图
问题描述
原本开发的C++程序可截取屏幕并保存到本地(功能正常),移除保存功能后,希望在新建窗口中显示截取的截图。目前截图能正常生成,窗口也能成功创建,但窗口无法显示截图。
注意:在Visual Studio中运行代码时,需将
Properties -> Linker -> System -> SubSystem从Windows (/SUBSYSTEM:WINDOWS)修改为Console (/SUBSYSTEM:CONSOLE)
原代码如下:
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <objidl.h> #include <stdio.h> #include <gdiplus.h> using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; UINT size = 0; Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL; Gdiplus::GetImageEncodersSize(&num, &size); if (size == 0) { printf("Error: GetImageEncodersSize returned size 0\n"); return -1; } pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); if (pImageCodecInfo == NULL) { printf("Error: malloc failed to allocate memory for ImageCodecInfo\n"); return -1; } if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) { printf("Error: GetImageEncoders failed\n"); free(pImageCodecInfo); return -1; } for (UINT j = 0; j < num; ++j) { if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; } } free(pImageCodecInfo); return -1; } void TakeScreenshot() { // Get the dimensions of the whole desktop int dpi_x = GetDpiForSystem(); int dpi_y = GetDpiForSystem(); int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x); int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y); if (width == 0 || height == 0) { printf("Error: GetSystemMetrics returned invalid screen dimensions\n"); return; } // Create a bitmap to hold the screenshot HDC screen_dc = GetDC(NULL); if (screen_dc == NULL) { printf("Error: GetDC failed to get a handle to the screen device context\n"); return; } HDC mem_dc = CreateCompatibleDC(screen_dc); if (mem_dc == NULL) { printf("Error: CreateCompatibleDC failed to create a compatible device context\n"); ReleaseDC(NULL, screen_dc); return; } // Create a bitmap that is scaled to the appropriate DPI HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL); if (bitmap == NULL) { printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n"); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap); // Set the DPI of the memory DC to match the system DPI SetGraphicsMode(mem_dc, GM_ADVANCED); XFORM xform; xform.eM11 = (FLOAT)dpi_x / 96; xform.eM12 = xform.eM21 = xform.eM22 = 0; xform.eDx = xform.eDy = 0; SetWorldTransform(mem_dc, &xform); // Copy the screen contents to the bitmap if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) { printf("Error:BitBlt failed to copy screen contents to bitmap\n"); return; } // Save the bitmap to a file Gdiplus::Bitmap image(bitmap, NULL); if (image.GetLastStatus() != Ok) { printf("Error: Bitmap constructor failed to create a Bitmap object\n"); return; } CLSID png_clsid; int r = GetEncoderClsid(L"image/png", &png_clsid); if (r == -1) { printf("Error: unable to find image encoder for MIME type 'image/png'\n"); return; } HWND hwnd; WNDCLASSEX wc; HINSTANCE hInstance = GetModuleHandle(NULL); // Define window class ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = DefWindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = L"ScreenshotWindowClass"; RegisterClassEx(&wc); // Create window hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, L"ScreenshotWindowClass", L"Screenshot", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL); if (hwnd == NULL) { printf("Error: CreateWindowEx failed to create window\n"); return; } // Show screenshot in window HDC hdc = GetDC(hwnd); Graphics graphics(hdc); graphics.DrawImage(&image, 0, 0, width, height); ReleaseDC(hwnd, hdc); // Display window ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); // Wait for user to close window MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // Clean up UnregisterClass(L"ScreenshotWindowClass", hInstance); // Clean up SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); } int main() { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); TakeScreenshot(); SetThreadDpiAwarenessContext(dpi_awareness_context); GdiplusShutdown(gdiplusToken); return 0; }
解决方案
你的代码存在两个核心问题导致截图无法显示:
- 绘制时机错误:窗口显示前调用
DrawImage绘制的内容会被后续窗口刷新覆盖,Windows窗口的标准绘制时机是响应WM_PAINT消息。 - 资源生命周期问题:
Gdiplus::Bitmap image是局部变量,消息循环前就会被销毁,后续窗口重绘无法访问该资源。
修改后的完整代码
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <objidl.h> #include <stdio.h> #include <gdiplus.h> using namespace Gdiplus; #pragma comment (lib,"Gdiplus.lib") // 全局存储截图资源,确保窗口过程可访问 Bitmap* g_pScreenshot = nullptr; int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; UINT size = 0; Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL; Gdiplus::GetImageEncodersSize(&num, &size); if (size == 0) { printf("Error: GetImageEncodersSize returned size 0\n"); return -1; } pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size)); if (pImageCodecInfo == NULL) { printf("Error: malloc failed to allocate memory for ImageCodecInfo\n"); return -1; } if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) { printf("Error: GetImageEncoders failed\n"); free(pImageCodecInfo); return -1; } for (UINT j = 0; j < num; ++j) { if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; } } free(pImageCodecInfo); return -1; } // 自定义窗口过程,处理绘制和销毁消息 LRESULT CALLBACK ScreenshotWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); if (g_pScreenshot) { Graphics graphics(hdc); RECT rc; GetClientRect(hwnd, &rc); // 绘制截图到窗口客户区 graphics.DrawImage(g_pScreenshot, 0, 0, rc.right - rc.left, rc.bottom - rc.top); } EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, msg, wParam, lParam); } } void TakeScreenshot() { int dpi_x = GetDpiForSystem(); int dpi_y = GetDpiForSystem(); int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x); int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y); if (width == 0 || height == 0) { printf("Error: GetSystemMetrics returned invalid screen dimensions\n"); return; } HDC screen_dc = GetDC(NULL); if (screen_dc == NULL) { printf("Error: GetDC failed to get a handle to the screen device context\n"); return; } HDC mem_dc = CreateCompatibleDC(screen_dc); if (mem_dc == NULL) { printf("Error: CreateCompatibleDC failed to create a compatible device context\n"); ReleaseDC(NULL, screen_dc); return; } HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL); if (bitmap == NULL) { printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n"); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); return; } HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap); SetGraphicsMode(mem_dc, GM_ADVANCED); XFORM xform; xform.eM11 = (FLOAT)dpi_x / 96; xform.eM12 = xform.eM21 = xform.eM22 = 0; xform.eDx = xform.eDy = 0; SetWorldTransform(mem_dc, &xform); if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) { printf("Error:BitBlt failed to copy screen contents to bitmap\n"); return; } // 创建全局Bitmap对象存储截图 g_pScreenshot = new Bitmap(bitmap, NULL); if (g_pScreenshot->GetLastStatus() != Ok) { printf("Error: Bitmap constructor failed to create a Bitmap object\n"); delete g_pScreenshot; g_pScreenshot = nullptr; return; } HWND hwnd; WNDCLASSEX wc; HINSTANCE hInstance = GetModuleHandle(NULL); ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = ScreenshotWndProc; // 使用自定义窗口过程 wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = L"ScreenshotWindowClass"; RegisterClassEx(&wc); hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, L"ScreenshotWindowClass", L"Screenshot", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, NULL); if (hwnd == NULL) { printf("Error: CreateWindowEx failed to create window\n"); return; } ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } UnregisterClass(L"ScreenshotWindowClass", hInstance); // 清理截图资源 if (g_pScreenshot) { delete g_pScreenshot; g_pScreenshot = nullptr; } // 清理GDI资源 SelectObject(mem_dc, old_bitmap); DeleteObject(bitmap); DeleteDC(mem_dc); ReleaseDC(NULL, screen_dc); } int main() { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); TakeScreenshot(); SetThreadDpiAwarenessContext(dpi_awareness_context); GdiplusShutdown(gdiplusToken); return 0; }
关键修改说明
- 新增全局资源存储:用
g_pScreenshot全局指针保存截图的Bitmap对象,确保窗口过程能持续访问该资源。 - 自定义窗口过程:编写
ScreenshotWndProc处理WM_PAINT消息,在窗口需要重绘时绘制截图;处理WM_DESTROY消息终止消息循环。 - 移除无用代码:删除了原有的保存截图到文件的逻辑,减少冗余。
- 调整资源清理顺序:确保截图资源在窗口销毁后再释放,避免悬空指针问题。
内容的提问来源于stack exchange,提问作者kyle walker
相关产品推荐
相关产品推荐

