В этой функции после примерно 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», что для меня ничего не значит.
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);
}
}
У вашего кода есть несколько проблем, некоторые второстепенные, другие фатальные (и реализация действительно только работает, потому что ОС готова справиться с этими распространенными ошибками). Вот аннотированный список оригинального кода:
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
выходит за рамки (в конце функции).