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

VS2022迁移早期MFC剪贴板代码时EmptyClipboard()崩溃求助

问题分析与解决方案

核心原因:DIB内存大小计算错误导致堆破坏

你的崩溃本质是DIB内存分配不足,后续GetDIBits写入时越界破坏堆结构,最终在EmptyClipboard触发堆检测断点(调试模式)或直接终止(非调试模式)。翻倍内存后恢复正常,恰好验证了这一点——原来的计算未满足实际内存需求。

正确的DIB内存大小计算公式

DIB内存由三部分组成:BITMAPINFOHEADER结构、颜色表(仅当位深≤8时存在)、图像像素数据。计算时必须严格遵循DWORD对齐规则(每行像素数据长度必须是4的倍数),具体步骤如下:

  1. 计算单行列字节数:

    DWORD dwRowBytes = ((biWidth * biBitCount + 31) / 32) * 4;
    

    原理:将每行所需的比特数向上取整到32位(4字节)的倍数,转换为字节数。

  2. 计算图像像素数据总大小:

    DWORD dwImageSize = dwRowBytes * abs(biHeight);
    

    注意:biHeight可能为负数(表示DIB是顶向下存储),所以必须取绝对值。

  3. 计算颜色表大小:
    仅当位深≤8时需要颜色表,大小为:

    DWORD dwColorTableSize = 0;
    if (biBitCount <= 8)
    {
        dwColorTableSize = (1 << biBitCount) * sizeof(RGBQUAD);
    }
    

    比如8位灰度图有256个颜色表项,每个占4字节(RGBQUAD)。

  4. 完整DIB内存总大小:

    DWORD dwTotalSize = sizeof(BITMAPINFOHEADER) + dwColorTableSize + dwImageSize;
    

针对你的代码修改建议

将原代码中计算内存大小的逻辑替换为上述公式,示例如下:

// 替换原内存计算部分
BITMAPINFOHEADER& bi = m_Dibhead;
DWORD dwRowBytes = ((bi.biWidth * bi.biBitCount + 31) / 32) * 4;
DWORD dwImageSize = dwRowBytes * abs(bi.biHeight);
DWORD dwColorTableSize = (bi.biBitCount <= 8) ? (1 << bi.biBitCount) * sizeof(RGBQUAD) : 0;
DWORD dwTotalSize = sizeof(BITMAPINFOHEADER) + dwColorTableSize + dwImageSize;

HGLOBAL hGlobal = GlobalAlloc(GHND, dwTotalSize);
if (!hGlobal) return;
LPVOID ptr = GlobalLock(hGlobal);
if (!ptr) { GlobalFree(hGlobal); return; }

// 复制BITMAPINFOHEADER到内存
memcpy(ptr, &bi, sizeof(BITMAPINFOHEADER));

// 若有颜色表,复制颜色表数据(假设你已持有颜色表指针pColorTable)
if (dwColorTableSize > 0)
{
    memcpy((BYTE*)ptr + sizeof(BITMAPINFOHEADER), pColorTable, dwColorTableSize);
}

// 获取DIB像素数据,注意偏移位置
::GetDIBits(dc.m_hDC, hBitmap2, 0, (int)bi.biHeight,
    (BYTE*)ptr + sizeof(BITMAPINFOHEADER) + dwColorTableSize,
    (BITMAPINFO*)ptr, DIB_RGB_COLORS);

关于OpenClipboard参数的问题

旧代码无参数是因为早期SDK允许OpenClipboard默认传NULL,但VS2022使用的新版SDK要求显式传参。编译器报错可能是项目SDK版本配置冲突,解决方法:

  • 确保项目的Windows SDK版本设置为最新(在项目属性→常规中调整)
  • 显式传入NULL或窗口句柄(比如this->GetSafeHwnd()):
    if (!OpenClipboard(this->GetSafeHwnd()))
    {
        // 处理打开失败
        return;
    }
    

调试现象解释

  • 注释GetDIBits后不崩溃:因为没有向不足的内存写入数据,堆结构未被破坏
  • 首次复制正常后续失效:第一次堆破坏程度较轻,未立即触发断点,后续操作进一步损坏堆结构才引发问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 15:10:16