在这个函数中,在大约90次调用之后(它在一个循环中被调用,并且想法是每次加载一个单独的图像,但为了简单起见我将它保存到一个图像)。全局变量现在变为本地变量
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile)
{
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbmp_temp)
{
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
return;
}
我希望有人能想出一个错误的想法。 GetLastError返回“8”,这对我来说没有任何意义。
答案 0 :(得分:3)
Detach
会破坏之前的句柄。
请注意,如果在调用 Detach
后调用SetBitmap
,图片控件的位图将被销毁。结果是图片控件被绘制一次,但不会重新绘制。例如,如果调整对话框,则图片控件将变为空白。
修改强>
要销毁旧位图,请调用Detach
,然后调用DestroyObject
。实施例
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
m_bitmap.Attach(hbitmap);
如果是临时CBitmap
,则不需要DeleteObject
,因为DeleteObject
超出范围时会自动调用CBitmap
。
请注意,如果在调用SetBitmap
后销毁位图,则会破坏图片控件的位图。结果是图片控件被绘制一次,但不会重新绘制。例如,如果调整对话框,则图片控件将变为空白。
如果在堆栈上声明临时CBitmap
并附加位图句柄,则会出现同样的问题。该位图句柄将被销毁,图片控制无法重新绘制。
此外,Windows XP有时会制作需要销毁的重复位图。 SetBitmap
返回前一个位图的句柄。在Vista +中,返回的位图与m_bitmap
中保存的位图相同,我们已经使用Detach
销毁了位图。但是在XP中我们需要销毁这个副本,如果它是一个不同的句柄。
void CMyDialog::foo()
{
HBITMAP save = m_bitmap;
HBITMAP hbitmap = (HBITMAP)::LoadImage(0, filename,
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (hbitmap)
{
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
//Edit ****************************************
//Delete old handle, otherwise program crashes after 10,000 calls
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
//*********************************************
m_bitmap.Attach(hbitmap);
HBITMAP oldbmp = m_picControl.SetBitmap(m_bitmap);
//for Windows XP special case where there might be 2 copies:
if (oldbmp && (oldbmp != save))
DeleteObject(oldbmp);
}
}
此外,SetBitmap
需要HBITMAP
参数并返回HBITMAP
,因此您可以完全避免使用CBitmap
。以下示例适用于Vista +
void foo()
{
HBITMAP temp = (HBITMAP)::LoadImage(0,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if (temp)
{
HBITMAP oldbmp = m_picControl.SetBitmap(temp);
if (oldbmp)
DeleteObject(oldbmp);
}
}
答案 1 :(得分:1)
您的代码有几个问题,一些是次要的,另一些是致命的(实现实际上似乎只能起作用,因为操作系统已准备好处理这些常见的错误)。以下是原始代码的注释列表:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
// ^ should be const CString&
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
if (!hbmp_temp) {
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
// You already know, that the condition is true (unless your commented out code
// is supposed to run).
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
// ^ This should immediately follow the LoadImage call, to benefit from automatic
// resource management. (What's the point of using MFC when you decide to implement
// manual resource management on top of it?)
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// ^ Use named constants. No one is going to
// look up the documentation just to find
// out, what you are trying to do.
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
// The GDI object (hbmp_temp) now has two owners, the CBitmap instance bmp_temp, and
// the picture control. At the same time, you are throwing away the handle previously
// owned by the control. This is your GDI resource leak.
return;
// ^ Superfluous. This is merely confusing readers. Remove it.
}
// This is where things go fatal: The bmp_temp d'tor runs, destroying the GDI resource
// hbmp_temp, that's also owned by the control. This should really blow up in your face
// but the OS knows that developers cannot be trusted anymore, and covers your ass.
两个主要问题是:
SetBitmap
的返回值)。这最终导致任何尝试创建其他GDI资源失败。HBITMAP
)而导致的悬空指针(hbmp_temp
)。以下是具有固定资源管理的版本。这对你没有任何意义,因为你不能很好地了解MFC(或C ++),以及如何帮助理解自动资源管理。总之:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
// Immediately attach a C++ object, so that resources will get cleaned up
// regardless how the function is exited.
CBitmap bmp_temp;
if (!bmp_temp.Attach(hbmp_temp)) {
// Log error/load placeholder image
return;
}
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// Swap the owned resource of bmp_temp with that of the control:
bmp_temp.Attach(mProjectorWindow.m_picControl.SetBitmap(bmp_temp.Detach()));
}
最后一行是关键部分。它实现了将原始Windows API资源与资源管理包装器交换的规范方法。这是一系列操作:
bmp_temp.Detach()
释放GDI资源的所有权。SetBitmap()
将GDI资源的所有权传递给控件,并返回先前的GDI对象(如果有)。bmp_temp.Attach()
获取返回的GDI资源的所有权。这可以确保在bmp_temp
超出范围时(在函数末尾)清理以前的资源。