如何将MFC中CBitmap剪贴板存储从OpenClipboard切换到COleDataSource
我有一个MFC应用程序,原本通过OpenClipboard()/SetClipboardData()/CloseClipboard()将CBitmap存储到剪贴板,代码如下:
if(OpenClipboard()) { if(EmptyClipboard()) { SetClipboardData(CF_BITMAP, pBitmap->m_hObject); CloseClipboard(); } } // Error reported omitted for brevity.
这段代码运行正常,但应用程序其他地方使用COleDataSource存储自定义数据到剪贴板:
COleDataSource* pOleSource = new COleDataSource; pOleSource->CacheGlobalData(uiRiggerFormat, hMem); pOleSource->SetClipboard();
这部分也能正常工作。
我希望改用COleDataSource存储CBitmap,这样就能同时把CBitmap和自定义数据放到剪贴板(接收端可选择所需格式)。
尝试直接将CBitmap的hObject传入COleDataSource::CacheGlobalData,但粘贴时崩溃,剪贴板对象大小为0:
COleDataSource* pOleSource = new COleDataSource; pOleSource->CacheGlobalData(CF_BITMAP, pBitmap->m_hObject); pOleSource->SetClipboard(); // FYI, I do not free pBitmap.
我猜测问题在于pBitmap(未释放)不在全局内存中,但对此了解有限。
之后尝试将pBitmap转移到全局内存:
BITMAP bmpInfo; pBitmap->GetBitmap(&bmpInfo); int size = bmpInfo.bmWidthBytes * bmpInfo.bmHeight; BYTE* pBits = new BYTE[size]; pBitmap->GetBitmapBits(size, pBits); HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size); LPVOID pMem = GlobalLock(hMem); memcpy(pMem, pBits, size); // Store it. COleDataSource* pOleSource = new COleDataSource; ASSERT(pOleSource); pOleSource->CacheGlobalData(CF_BITMAP, hMem); pOleSource->SetClipboard();
此时粘贴不再崩溃,但图片无法显示,我猜测是因为计算的大小仅为位图数据,而非剪贴板所需的完整位图结构。
更新
我找到了可行的代码(感谢Igor Tandetnik的建议):
tagSTGMEDIUM* pStorageMedium = new tagSTGMEDIUM; memset(pStorageMedium, 0, sizeof(tagSTGMEDIUM)); pStorageMedium->tymed = TYMED_GDI; pStorageMedium->hBitmap = HBITMAP(*pDestBitmap); COleDataSource* pOleSource = new COleDataSource; pOleSource->CacheData(CF_BITMAP, pStorageMedium); pOleSource->SetClipboard(); delete pStorageMedium;
但创建位图时使用的CDC指针pDC会导致内存泄漏:
CClientDC cdc(this); CDC* pDc = new CDC(); pDc->CreateCompatibleDC(&cdc); pBitmap->CreateCompatibleBitmap(&cdc, iBitmapWidth, iBitmapHeight); const CBitmap* pOldDestBitmap = pDc->SelectObject(pBitmap); CDC dcBitmap; dcBitmap.CreateCompatibleDC(pDc); const CBitmap* pOldBitmap = dcBitmap.SelectObject(&bmpBitmap); pDc->BitBlt(0, 0, iBitmapWidth, iBitmapHeight, &dcBitmap, 0, 0, SRCCOPY);
如果删除pDC避免泄漏,剪贴板中的位图就无法粘贴,请问如何解决这个内存泄漏问题?
一、正确使用COleDataSource存储CBitmap到剪贴板
你找到的CacheData结合STGMEDIUM的方式是正确的,因为CF_BITMAP对应的剪贴板数据是GDI位图句柄,而非全局内存块,所以不能用CacheGlobalData(该方法适用于基于全局内存的格式,比如CF_TEXT)。
使用STGMEDIUM传递HBITMAP时,需注意:
- 设置
tymed = TYMED_GDI,标识这是GDI对象 - 将
hBitmap赋值为CBitmap的句柄 COleDataSource会接管这个位图句柄的所有权,所以不需要自行释放pDestBitmap的句柄(除非后续还要使用它)
二、解决CDC的内存泄漏问题
内存泄漏的核心原因是:创建位图后未恢复DC中原来的位图对象,导致DC持有原位图引用,既造成泄漏,又让当前pBitmap无法被剪贴板正确接管。修复步骤如下:
1. 恢复DC中的原有位图
完成BitBlt操作后,必须将DC中原来的位图选回,修改代码如下:
CClientDC cdc(this); CDC* pDc = new CDC(); pDc->CreateCompatibleDC(&cdc); pBitmap->CreateCompatibleBitmap(&cdc, iBitmapWidth, iBitmapHeight); const CBitmap* pOldDestBitmap = pDc->SelectObject(pBitmap); CDC dcBitmap; dcBitmap.CreateCompatibleDC(pDc); const CBitmap* pOldBitmap = dcBitmap.SelectObject(&bmpBitmap); // 执行绘图操作 pDc->BitBlt(0, 0, iBitmapWidth, iBitmapHeight, &dcBitmap, 0, 0, SRCCOPY); // 关键:恢复DC中的原有位图,释放对当前位图的引用 dcBitmap.SelectObject(pOldBitmap); pDc->SelectObject(pOldDestBitmap); // 现在可以安全释放pDC delete pDc; pDc = nullptr;
2. 为什么恢复后剪贴板仍能正常工作?
当你把pBitmap的句柄传递给STGMEDIUM后,COleDataSource会在SetClipboard时将该位图的所有权转移给剪贴板系统,即使释放了pDC,剪贴板依然持有该位图的有效句柄,粘贴操作不受影响。
额外优化:避免动态分配CDC
可以直接使用栈上的CDC对象,彻底避免手动管理内存:
CClientDC cdc(this); CDC dcDest; dcDest.CreateCompatibleDC(&cdc); pBitmap->CreateCompatibleBitmap(&cdc, iBitmapWidth, iBitmapHeight); const CBitmap* pOldDestBitmap = dcDest.SelectObject(pBitmap); CDC dcBitmap; dcBitmap.CreateCompatibleDC(&dcDest); const CBitmap* pOldBitmap = dcBitmap.SelectObject(&bmpBitmap); dcDest.BitBlt(0, 0, iBitmapWidth, iBitmapHeight, &dcBitmap, 0, 0, SRCCOPY); // 恢复原有位图 dcBitmap.SelectObject(pOldBitmap); dcDest.SelectObject(pOldDestBitmap);
这样从根源上避免了内存泄漏问题。
内容的提问来源于stack exchange,提问作者Steve A

