управление жизненным циклом и ресурсами при использовании CImage с CStatic для отображения изменяющихся изображений - PullRequest
0 голосов
/ 28 июня 2018

У меня есть приложение для точек продаж на основе MFC, которое будет отображать серию изображений на дисплее, обращенном к клиенту, пока оператор звонит.

Для обработки представления изображений я создаю окно, содержащее элемент управления CStatic, который отображается на мониторе, обращенном к клиенту. Я использую класс CImage для загрузки изображения из файла на диске, а затем устанавливаю изображение в элемент управления CStatic.

Прототип с жестко закодированными именами изображений выглядит так:

CImage CImg;
HRESULT  hr;

CONST TCHAR *px;
// determine the image to display based on the current OEP group number being processed.
switch (m_aszCurrentOEPGroup) { 
    case 16:
        hr = CImg.Load( px = PifGetFilePath (_T("PlatedSandwiches.png"), "b", NULL) );
        break;

    case 20:
        hr = CImg.Load( px = PifGetFilePath (_T("modem_after20.png"), "b", NULL) );
        break;

    default:
        hr = CImg.Load( px = PifGetFilePath (_T("modem_after00.png"), "b", NULL) );
        break;
}

if( m_OEPCustDisplayImage.GetBitmap( ) == NULL )
    m_OEPCustDisplayImage.SetBitmap( HBITMAP(CImg) );

Приведенный выше код принадлежит классу, производному от CWnd и содержит переменную-член m_OEPCustDisplayImage, которая определена как CStatic m_OEPCustDisplayImage;. И, глядя на это, мне действительно нужно проверить переменную результата hr на предмет того, работает ли Load(). И, похоже, Load() может выдать исключение, поэтому я должен учитывать это.

Приведенный выше исходный код находится в функции члена класса, которая вызывается для вызова окна отображения изображения, обращенного к клиенту, загрузки требуемого изображения из файла в определенной папке и затем установки изображения в CStatic для отображения изображение.

Во всплывающем окне дисплея код фактически выполняет ShowWindow(SW_SHOW) при отображении изображения и ShowWindow(SW_HIDE) при нажатии или удалении отображаемого изображения.

Этот исходный код отображает изображения, как и ожидалось.

Однако меня беспокоит время жизни ресурса, особенно загруженных изображений. Я не хочу создавать утечку памяти.

Поскольку окно дисплея, обращенное к клиенту, создается один раз, а затем существует до выхода из приложения, деструктор для окна не является механизмом управления жизненным циклом ресурса.

CImage используется для загрузки изображения из файла, и, поскольку это локальный объект, при возврате функции его деструктор будет вызван для очистки.

Я пытаюсь понять фактический жизненный цикл изображения и что происходит с ресурсами, используемыми на следующих этапах:

  • определяется CImage
  • изображение загружается из файла в CImage с использованием load()
  • изображение помещается в CStatic с помощью SetBitmap()
  • объект CImage выходит из области видимости, а его деструктор называется
  • изображение в CStatic заменяется новым изображением или имеет значение NULL

Некоторые вопросы жизненного цикла, которые приходят на ум:

  • когда SetBitmap() используется для помещения изображения в CStatic, сделана новая копия изображения?
  • когда деструктор CImage срабатывает, когда объект выходит из области видимости, очищаются ли ресурсы изображения? (Я бы так и ожидал)
  • что мне нужно делать с изображением в CStatic при замене его на новое изображение?

В настоящее время в всплывающем коде окна, обращенного к клиенту, я просто устанавливаю изображение в CStatic на NULL с m_OEPCustDisplayImage.SetBitmap(NULL);, однако мне кажется, что это будет утечка ресурсов. Что происходит с изображением, которое отображалось в CStatic?

Поведение, которое я наблюдаю в приложении, заключается в том, что запрашиваемое изображение загружается и отображается с помощью элемента управления CStatic, размер которого изменяется до размера, равного CWnd, содержащему его, с использованием ::SetWindowPos(). Окно отображается и не отображается, как ожидалось. Изображения меняются, как и ожидалось.

Поскольку изображение отображается в CStatic до тех пор, пока следующее действие оператора не заставит отображаемое окно не отображаться, может показаться, что метод SetBitmap() в CStatic сделал свою собственную копию изображения.

Нужно ли делать что-то вроде следующего, как часть окна пользователя, отображаемого и всплывающего?

CGdiObject myTempObj(m_OEPCustDisplayImage.SetBitmap(NULL));

и затем позволить этому объекту выйти из области видимости, чтобы очистить любые ресурсы?

Я смотрел на окно «Вывод» Visual Studio после выхода из приложения и не вижу никаких признаков того, что эти образы вызывают утечку ресурсов из-за жалоб на отладку среды выполнения Visual Studio C ++, однако я не уверен, что утечка ресурсов этот тип, если он существует, будет определен средой выполнения C ++.

Приложение A: Изменения в источнике для исправления утечек ресурсов, повторное тестирование

При поиске того, не были ли изображения выпущены должным образом, я не смог определить это из-за другого источника утечек ресурсов, в результате которого я увидел в диспетчере задач Windows, что количество объектов GDI увеличивалось, однако я был неуверенный относительно того, что было из-за изображений и что было из-за утечки других ресурсов, указатели кнопок не были должным образом удалены.

Я мог видеть в окне «Вывод» Visual Studio, поскольку приложение прекратило список утечек указателей кнопок, а также их расположение. Однако я не увидел никаких признаков утечки ресурсов изображения.

Я отследил причину утечки ресурса указателей кнопок и исправил ее.

Я также изменил исходный код для изображений следующим образом.

В коде, который обрабатывает отображение или всплывающее окно в окне с CStatic отображением изображения, у меня есть следующий исходный код очистки, который перемещает владельца изображения с CStatic на CGdiObject, который затем переходит удаление изображения из области видимости:

CGdiObject myObj;
myObj.Attach(m_OEPCustDisplayImage.SetBitmap(NULL));

Затем в коде, который загружает сначала объект CImage с изображением из файла, а затем загружает изображение в объект CStatic, я внес следующие изменения, используя метод Detach() CImage с SetBitmap() из CStatic для передачи права собственности на изображение в CStatic:

if( ! FAILED(hr)) {
    if (m_OEPCustDisplayImage.GetBitmap( ) == NULL )
        m_OEPCustDisplayImage.SetBitmap( CImg.Detach() );
    else {
        CGdiObject myObj;
        myObj.Attach(m_OEPCustDisplayImage.SetBitmap(CImg.Detach()));
    }
}

После этих двух попыток я смог контролировать количество объектов GDI с помощью диспетчера задач Windows, работая с приложением в режиме отладки. Я мог видеть увеличение количества объектов GDI по мере того, как отображались окна с изображениями, а затем после того, как они не отображались и выдавались, счет возвращался к счетчику до того, как функциональность была запущена.

Установка точки останова таким образом, чтобы код для передачи права собственности на изображение в CGdiObject с SetBitmap(NULL) был заменен просто вызовом SetBitmap(NULL), что привело к увеличению числа объектов GDI, как и ожидалось.

Затем я перекомпилировал исправленный источник как сборку Release и применил приложение в этой области функциональности, наблюдая за отображением диспетчера задач Windows количества объектов GDI. Поведение счетчика должно было увеличиваться при отображении окон с изображениями и уменьшаться до базового значения, когда изображения не отображались.

1 Ответ

0 голосов
/ 28 июня 2018

Выше код использует файл PNG, который может иметь прозрачность. Это особый случай, когда SetBitmap заканчивается созданием другого дескриптора растрового изображения. Изображение останется даже после того, как оригинальный дескриптор растрового изображения будет уничтожен. В противном случае этот метод завершится ошибкой, например, для 24-битного растрового изображения.

SetBitmap использует STM_SETIMAGE API, описанный со следующими замечаниями:

В Windows XP, если растровое изображение передано в сообщении STM_SETIMAGE содержит пиксели с ненулевой альфа, статический элемент управления принимает копию растровое изображение. Это скопированное растровое изображение возвращается следующим STM_SETIMAGE сообщение. Код клиента может независимо отслеживать растровые изображения, передаваемые статический контроль, но если он не проверяет и не выпускает растровые изображения возвращается из STM_SETIMAGE сообщений, растровые изображения просочились.

Вы звоните SetBitmap только один раз, на основании проверки на GetBitmap. Вы пропускаете 1 дескриптор ресурса (допустимый лимит составляет 10000). Это может быть трудно обнаружить, но оно есть. Он может столкнуться с проблемой, если вы открываете и закрываете диалоговое окно много раз в одном и том же процессе.

Предпочтительно использовать CImage::Detach вместо HBITMAP(cimage_object)

Пример, показанный ниже, не будет иметь утечек ресурсов и работает с другими изображениями. Для очистки дескриптора растрового изображения в конце используется дополнительная переменная bitmap_cleanup.

class CMyDialog : public CDialogEx
{
    CStatic m_OEPCustDisplayImage;
    CBitmap bitmap_cleanup;
    ...
};

BOOL CMyDialog::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    CImage img;
    HRESULT hr = img.Load(L"test.png");
    if(SUCCEEDED(hr))
    {
        HBITMAP hbitmap = img.Detach();
        //CImage is no longer responsible for cleanup

        HBITMAP holdbmp = m_OEPCustDisplayImage.SetBitmap(hbitmap);

        //we have to cleanup previous handle if any
        if(holdbmp)
            DeleteObject(holdbmp);

        //pass the handle to bitmap_cleanup, to cleanup on exit
        bitmap_cleanup.Attach(hbitmap);
    }

    return TRUE;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...