MFC中多线程生成HDC后BitBlt至主HDC仅最后结果生效问题
问题分析与解决方案
问题根源
- 区域完全覆盖:所有线程执行
BitBlt时,都将自身绘制的位图以SRCCOPY模式写入主HDC的(0,0)起始区域,后完成的线程会直接覆盖前面线程的绘制结果,最终仅保留最后一个线程的输出。 - 无同步的并发操作:主HDC属于共享GDI资源,多线程无保护直接对其执行
BitBlt会引发资源竞争,即使没有覆盖问题也可能导致绘制错乱。 - GDI资源泄漏:线程中
SelectObject取出的旧位图未恢复到线程HDC,创建的brush也未主动销毁,会造成GDI资源泄漏。
修复方案
1. 解决区域覆盖问题
- 如果每个
Info对应独立的绘制区域:从info中获取专属目标矩形,调整BitBlt的目标坐标,避免全部绘制在同一区域:// 示例:从info中获取该线程负责的目标区域 CRect destRect = info.GetTargetRect(); ThreadSafeWinGdi::BitBlt(hDC, destRect.left, destRect.top, destRect.Width(), destRect.Height(), threadHdc, 0, 0, SRCCOPY); - 如果需要图层式叠加绘制:将
SRCCOPY替换为SRCPAINT等支持叠加的ROP码,同时给主HDC的BitBlt操作加锁。
2. 主HDC操作的线程同步
在ThreadSafeWinGdi中添加线程安全的BitBlt封装,保证同一时间只有一个线程操作主HDC:
BOOL ThreadSafeWinGdi::BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop) { static std::mutex bitBltMutex; std::unique_lock<std::mutex> lock(bitBltMutex); return ::BitBlt(hdcDest, nXDest, nYDest, nWidth, nHeight, hdcSrc, nXSrc, nYSrc, dwRop); }
3. 修复GDI资源泄漏
线程绘制完成后,必须恢复SelectObject取出的旧位图,并销毁创建的GDI资源:
// 绘制完成后执行资源清理 threadCDC->SelectObject(oldBitmap); // 恢复线程HDC的旧位图 ThreadSafeWinGdi::DeleteObject(newBitmap); ThreadSafeWinGdi::DeleteObject(brush.get()); // 销毁创建的画刷
修改后的线程lambda示例
futures.emplace_back(std::async(std::launch::async, [=]() { if (auto dcPtr = ThreadSafeWinGdi::CreateCompatibleDC(hDC)) { HDC threadHdc = dcPtr.get(); CDC* threadCDC = ThreadSafeWinGdi::FromHandle(threadHdc); auto view = static_cast<View*>(App::GetView()); CRect rect = view->GetViewRect(); HBITMAP newBitmap = ThreadSafeWinGdi::CreateCompatibleBitmap(hDC, rect.Width(), rect.Height()); auto oldBitmap = (HBITMAP)(threadCDC->SelectObject(newBitmap)); auto brush = ThreadSafeWinGdi::CreateSolidBrush(view->GetBkColor(_Env().GetEllipsoid())); ThreadSafeWinGdi::FillRect(threadHdc, rect, brush.get()); CManager manager; manager.SetEventWnd(coconutView); manager.SetDC(_iPane, threadHdc); manager.SetViewRect(_iPane, scrRect); manager.SetMapRect(_iPane, App::GetERManager().GetDrawMapRect(_iPane), FALSE); DrawInfo(manager, _iPane, info); // 同步叠加绘制到主HDC ThreadSafeWinGdi::BitBlt(hDC, 0, 0, rect.Width(), rect.Height(), threadHdc, 0, 0, SRCPAINT); // 清理GDI资源 threadCDC->SelectObject(oldBitmap); ThreadSafeWinGdi::DeleteObject(newBitmap); ThreadSafeWinGdi::DeleteObject(brush.get()); } }));
内容的提问来源于stack exchange,提问作者xiang xien
相关产品推荐
相关产品推荐

