数万次调用DrawTrainData后GetDC返回NULL的问题排查
问题:对话框应用中DrawTrainData函数调用数万次后::GetDC返回NULL的原因
问题背景
基于对话框的应用中,以下DrawTrainData函数被调用数万次后出现错误,排查发现::GetDC(this->GetSafeHwnd())返回NULL,请问问题出在哪里?
void CBitmapCtrl::DrawTrainData(Matrix* Imgs) { if (Imgs->size() > 0 && m_cropWidth != 0 && m_cropHeight != 0) { struct bitmap_img { BITMAPINFO bmi; unsigned char img[29 * 27 * 3]; } bitmap_img; for (auto n = 0; n < Imgs->size(); n++) { // 생성된 이미지 데이터를 RGB각각 복사.. for (auto i = 0; i < m_cropWidth * m_cropHeight * 3; i += 3) { bitmap_img.img[i] = static_cast<unsigned char>((*(*Imgs)[n])[i]); bitmap_img.img[i + 1] = static_cast<unsigned char>((*(*Imgs)[n])[i + 1]); bitmap_img.img[i + 2] = static_cast<unsigned char>((*(*Imgs)[n])[i + 2]); } BITMAPINFO bmi; ZeroMemory(&bmi, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = m_cropWidth; bmi.bmiHeader.biHeight = -m_cropHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB; bitmap_img.bmi = bmi; CRect r((n % 10) * m_cropWidth, (n / 10) * m_cropHeight, (n % 10) * m_cropWidth + m_cropWidth, (n / 10) * m_cropHeight + m_cropHeight); } CBitmap bmp; CDC digitDC; digitDC.CreateCompatibleDC(&memDC); HDC hdc = ::GetDC(this->GetSafeHwnd()); HBITMAP hbmp = CreateDIBBitmap(hdc,&(bmi.bmiHeader), CBM_INIT, bitmap_img.img, &bmi, DIB_RGB_COLORS); bmp.m_hObject = hbmp; CBitmap* pOldBitmap = digitDC.SelectObject(&bmp); memDC.StretchBlt(r.left,r.top,m_cropWidth*3,m_cropHeight*3,&digitDC,0,0,m_cropWidth,m_cropHeight,SRCCOPY); digitDC.SelectObject(pOldBitmap); digitDC.DeleteDC(); } Invalidate(); }
原因分析
核心问题是GDI资源泄漏,具体问题点如下:
- 未释放GetDC获取的HDC:每次调用
::GetDC获取窗口DC后,没有调用::ReleaseDC释放。Windows系统对每个进程的GDI资源数量有上限,数万次调用后资源被耗尽,导致::GetDC无法分配新的DC,最终返回NULL。 - HBITMAP资源管理不当:
CreateDIBBitmap创建的HBITMAP直接赋值给bmp.m_hObject,若CBitmap对象之前已有关联的位图,会导致旧HBITMAP泄漏;即使是首次赋值,这种直接操作成员变量的方式也不符合MFC资源管理规范,存在泄漏风险。 - 额外逻辑问题:循环内定义的
CRect r在循环结束后仅保留最后一次循环的值,后续StretchBlt使用的是这个最终值,属于逻辑错误,但不是导致当前崩溃的直接原因。
修复方案
- 必须释放GetDC获取的DC:
在使用完hdc后,立即调用::ReleaseDC释放资源:HDC hdc = ::GetDC(this->GetSafeHwnd()); if (hdc != NULL) { HBITMAP hbmp = CreateDIBBitmap(hdc,&(bmi.bmiHeader), CBM_INIT, bitmap_img.img, &bmi, DIB_RGB_COLORS); // 执行后续位图操作 ::ReleaseDC(this->GetSafeHwnd(), hdc); // 释放DC资源 } - 规范管理HBITMAP资源:
使用MFC的Attach方法关联HBITMAP,确保旧资源被正确清理:if (bmp.GetSafeHandle() != NULL) { bmp.DeleteObject(); } bmp.Attach(hbmp); - 添加返回值检查:
检查CreateDIBBitmap的返回值,避免使用无效的HBITMAP导致后续操作出错。
内容的提问来源于stack exchange,提问作者bongjae kim
相关产品推荐
相关产品推荐

