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

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

关键修改说明

  1. 新增全局资源存储:用g_pScreenshot全局指针保存截图的Bitmap对象,确保窗口过程能持续访问该资源。
  2. 自定义窗口过程:编写ScreenshotWndProc处理WM_PAINT消息,在窗口需要重绘时绘制截图;处理WM_DESTROY消息终止消息循环。
  3. 移除无用代码:删除了原有的保存截图到文件的逻辑,减少冗余。
  4. 调整资源清理顺序:确保截图资源在窗口销毁后再释放,避免悬空指针问题。

内容的提问来源于stack exchange,提问作者kyle walker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 02:02:00