2016-09-20 14 views
0

В этой функции после примерно 90 вызовов (она вызывается в цикле, и идея состоит в загрузке отдельного изображения каждый раз, но я сохранил ее для одного изображения для простоты). Глобальные переменные теперь изменились на локальные.LoadBitmap не работает после нескольких вызовов

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

Вы, кажется, ничего не делаете с 'buffer'? Также добавьте для вызова ':: GetLastError()' в соответствующих местах и ​​запишите их (или используйте отладчик) –

+0

A [mcve], пожалуйста. Это должно быть полным и ** минимальным **. Ваши вызовы 'CBitmap :: Attach' и' Detach' являются массовыми убийствами, связанными с управлением ресурсами C++, и служат только для того, чтобы сделать ваш код более трудным для чтения. Если вы не знаете, почему это неправильно, вам нужно забрать хорошую книгу по программированию Windows API (Petzold's * «Programming Windows, 5th Edition» *), за которой следует одно для MFC («Прокси» * «Программирование Windows с MFC "*). Рассматривая историю вопроса, вы не добились большого прогресса. – IInspectable

+0

Используйте gdiplus, чтобы облегчить жизнь – seccpur

ответ

2

Detach уничтожит предыдущую ручку.

Обратите внимание, что если вы позвоните по телефону Detach после звонка SetBitmap, растровое изображение управления изображением будет уничтожено. В результате управление изображением наносится один раз, но он не будет перекрашиваться. Например, управление изображением гаснет, если диалоги изменены.

EDIT

Чтобы разрушить старый битмап, вызовите 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); 
    } 
} 
+0

На самом деле это было двухстрочное исправление после всего этого. Просто сохраните растровое изображение и немедленно уничтожьте t .. HBITMAP oldbmp = m_picControl.SetBitmap (m_bitmap); , сразу же вызовите DeleteObject на возвращаемом растровом изображении. Это потому, что мне не нужно было беспокоиться о его масштабировании. На самом деле это тоже должно работать для XP. –

+0

Ваше описание [CGdiObject :: Detach] (https://msdn.microsoft.com/en-us/library/x7ff7f4h.aspx) неверно. 'CGdiObject' экземпляры * собственные * соответствующие объекты GDI. 'Detach' disassociates ресурс GDI из объекта C++ и возвращает дескриптор объекта (теперь не имеющий права) GDI обратно вызывающему. Бросать эту ручку ** означает ** утечку ресурса. Предлагаемое решение держит жонглирование собственностью взад и вперед, без видимых причин. Он не читается и проявляется в том, что автор не совсем осознавал, что происходит на самом деле. – IInspectable

+0

На самом деле я сделал это. Я не использую метод Detach. Я также не использую глобальный CBitmap. Это использование возвращаемого значения из SetBitmap и использование DeleteObject на возвращенном растровом изображении, который сделал трюк. Ресурс определенно остался в памяти, вызывая утечку памяти GDI, как и в вашем другом полезном комментарии выше. Это решение, хотя я его немного изменил. –

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. 

Две основные вопросы:

  • Отказ удалить GDI ресурсов, принадлежащих код (возвращаемое значение SetBitmap). Это в конечном итоге приводит к любой попытке создать дополнительные ресурсы GDI для отказа.
  • Висячий указатель (HBITMAP), вызванный присвоением двух владельцев тому же ресурсу (hbmp_temp).
  • Здесь есть еще одна проблема. Поскольку вы назначили двух владельцев ресурсу bitmap, это приведет к двойному удалению. Это не так, потому что вы отказались от очистки ресурсов. (Тот факт, что вы не знаете, что вы делаете спас вас здесь. Имейте это в виду в следующий раз, когда вы celebrate your "can do" attitude.)

Ниже приводится версия с фиксированным планом управления ресурсами. Это не будет иметь для вас никакого смысла, поскольку вы не знаете 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 с помощью оберток управления ресурсами. Это последовательность операций:

  1. bmp_temp.Detach() освобождает право владения ресурсом GDI.
  2. SetBitmap() передает права собственности на ресурс GDI на элемент управления и возвращает предыдущий объект GDI (если есть).
  3. bmp_temp.Attach() приобретает право собственности на возвращаемый ресурс GDI. Это гарантирует, что предыдущий ресурс будет очищен, когда bmp_temp выходит за рамки (в конце функции).
+0

Это для Vista +? В Windows XP 'SetBitmap' может создать дополнительную растровую копию, которую этот метод не может учитывать. –

+0

@BarmakShemirani: Если явно не указано иное, код, отправленный мной, работает во всех поддерживаемых версиях Windows (то есть Windows Vista и выше). – IInspectable