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

使用GDI+时如何修复Access violation内存访问冲突错误?

GDI+截图发送内存访问错误修复

问题场景

开发服务器-客户端系统时,用GDI+捕获屏幕截图并发送给客户端,终端提示截图已保存并发送成功,但触发内存访问错误:

Exception thrown at 0x00007FF8627CA58B (GdiPlus.dll) in Server.exe: 0xC0000005: Access violation reading location 0x00000290B64B1F28

错误根源

核心问题是GDI+对象与GDI资源的释放顺序错误:

  • 创建Gdiplus::Bitmap对象时,它会内部引用传入的hBitmap句柄
  • 代码中先调用DeleteObject(hBitmap)释放了GDI位图资源,之后才销毁Bitmap对象并关闭GDI+,导致Bitmap对象在后续清理时访问已释放的内存,触发访问违规

修复步骤

调整资源释放顺序,严格遵循:

  1. 先销毁Gdiplus::Bitmap对象,释放其对hBitmap的引用
  2. 再删除hBitmap、memoryDC等GDI资源
  3. 最后调用GdiplusShutdown关闭GDI+

修复后的完整代码

std::string getCurrentTimestamp() {
    std::time_t t = std::time(nullptr);
    std::tm tm;
    localtime_s(&tm, &t);
    std::ostringstream oss;
    oss << (tm.tm_year + 1900) << "-"
        << (tm.tm_mon + 1) << "-"
        << tm.tm_mday << "_"
        << tm.tm_hour << "-"
        << tm.tm_min << "-"
        << tm.tm_sec;
    return oss.str();
}

void sendFile(SOCKET clientSocket, const std::string& filename) {
    std::ifstream inFile(filename, std::ios::binary | std::ios::ate);
    if (!inFile) {
        std::cerr << "Failed to open file: " << filename << std::endl;
        std::string errorMessage = "TYPE:text|SIZE:0\nFailed to open file.";
        send(clientSocket, errorMessage.c_str(), errorMessage.size(), 0);
        return;
    }

    // 获取文件大小
    std::streamsize fileSize = inFile.tellg();
    inFile.seekg(0, std::ios::beg);

    // 提取文件名并判断MIME类型
    size_t lastSlash = filename.find_last_of("/\\");
    std::string fileName = (lastSlash != std::string::npos) ? filename.substr(lastSlash + 1) : filename;
    std::string mimeType;
    if (fileName.size() >= 4 && fileName.compare(fileName.size() - 4, 4, ".png") == 0) {
        mimeType = "image/png";
    }
    else if (fileName.size() >= 4 && fileName.compare(fileName.size() - 4, 4, ".mp4") == 0) {
        mimeType = "video/mp4";
    }
    else {
        std::cerr << "Unsupported file format: " << filename << std::endl;
        std::string errorMessage = "TYPE:text|SIZE:0\nUnsupported file format.";
        send(clientSocket, errorMessage.c_str(), errorMessage.size(), 0);
        inFile.close();
        return;
    }

    // 发送文件头部信息
    std::string header = "TYPE:file|SIZE:" + std::to_string(fileSize) + "|FILENAME:" + fileName + "|MIMETYPE:" + mimeType + "\n";
    if (send(clientSocket, header.c_str(), header.size(), 0) != static_cast<int>(header.size())) {
        std::cerr << "Failed to send header" << std::endl;
        inFile.close();
        return;
    }

    // 分块发送文件数据
    char buffer[1024];
    while (inFile) {
        inFile.read(buffer, sizeof(buffer));
        int bytesRead = inFile.gcount();
        if (bytesRead > 0) {
            if (send(clientSocket, buffer, bytesRead, 0) != bytesRead) {
                std::cerr << "Failed to send file data" << std::endl;
                inFile.close();
                return;
            }
        }
    }

    std::cout << "File sent successfully!" << std::endl;
    inFile.close();
}

void takeScreenshotWithTimestamp(SOCKET clientSocket) {
    // 初始化GDI+
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    Gdiplus::Status status = Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    if (status != Gdiplus::Ok) {
        std::cerr << "Failed to initialize GDI+" << std::endl;
        return;
    }

    // 获取屏幕分辨率
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    // 创建屏幕兼容DC
    HDC screenDC = GetDC(NULL);
    if (screenDC == NULL) {
        std::cerr << "Failed to get screen DC" << std::endl;
        Gdiplus::GdiplusShutdown(gdiplusToken);
        return;
    }

    HDC memoryDC = CreateCompatibleDC(screenDC);
    if (memoryDC == NULL) {
        std::cerr << "Failed to create memory DC" << std::endl;
        ReleaseDC(NULL, screenDC);
        Gdiplus::GdiplusShutdown(gdiplusToken);
        return;
    }

    HBITMAP hBitmap = CreateCompatibleBitmap(screenDC, screenWidth, screenHeight);
    if (hBitmap == NULL) {
        std::cerr << "Failed to create compatible bitmap" << std::endl;
        DeleteDC(memoryDC);
        ReleaseDC(NULL, screenDC);
        Gdiplus::GdiplusShutdown(gdiplusToken);
        return;
    }

    SelectObject(memoryDC, hBitmap);

    // 捕获屏幕内容到内存DC
    if (!BitBlt(memoryDC, 0, 0, screenWidth, screenHeight, screenDC, 0, 0, SRCCOPY | CAPTUREBLT)) {
        std::cerr << "Failed to capture screen content" << std::endl;
        DeleteObject(hBitmap);
        DeleteDC(memoryDC);
        ReleaseDC(NULL, screenDC);
        Gdiplus::GdiplusShutdown(gdiplusToken);
        return;
    }

    // 生成带时间戳的文件名
    std::string filename = "Screenshot_" + getCurrentTimestamp() + ".png";

    // 新增局部作用域,确保Bitmap对象先销毁
    {
        Gdiplus::Bitmap bitmap(hBitmap, NULL);
        CLSID pngClsid;
        HRESULT hr = CLSIDFromString(L"{557CF406-1A04-11D3-9A73-0000F81EF32E}", &pngClsid);
        if (FAILED(hr)) {
            std::cerr << "Failed to get PNG CLSID" << std::endl;
            DeleteObject(hBitmap);
            DeleteDC(memoryDC);
            ReleaseDC(NULL, screenDC);
            Gdiplus::GdiplusShutdown(gdiplusToken);
            return;
        }

        status = bitmap.Save(std::wstring(filename.begin(), filename.end()).c_str(), &pngClsid, NULL);
        if (status != Gdiplus::Ok) {
            std::cerr << "Failed to save screenshot as PNG" << std::endl;
        }
        else {
            std::cout << "Screenshot saved as " << filename << std::endl;
        }
    } // Bitmap对象在此处自动销毁,释放对hBitmap的引用

    // 按顺序释放GDI资源和关闭GDI+
    DeleteObject(hBitmap);
    DeleteDC(memoryDC);
    ReleaseDC(NULL, screenDC);
    Gdiplus::GdiplusShutdown(gdiplusToken);

    sendFile(clientSocket, filename);
}

可选优化

  • 跳过本地文件保存步骤,直接将GDI+ Bitmap转换为内存流发送,减少磁盘IO开销
  • 完善send函数的错误处理,处理部分数据发送成功的情况(循环发送剩余数据)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:38:10