Прикрепление и отделение объектов - Классы-обертки и объекты

ОГЛАВЛЕНИЕ

Классы-обертки и объекты

Есть серьезное последствие прикрепления объекта Windows к объекту MFC. При уничтожении объекта MFC уничтожается прикрепленный объект Windows. Это имеет ряд серьезных последствий. Многие программисты делают следующую распространенную ошибку:

{
    CFont f;
    f.CreateFont(...);
    c_InputData.SetFont(&f);
}

Их удивляет отсутствие явного воздействия на управляющий элемент. Что поразительно, потому что раньше они делали нечто вроде:

{
    CFont f;
    f.CreateStockObject(ANSI_FIXED_FONT);
    c_DisplayData.SetFont(&f);
}

и это прекрасно работало!

Второй пример работал не лучше первого примера. Он преуспевал из-за специального случая, который они неосознанно использовали.

В первом примере происходит следующее: объект CFont создается в стеке, как ожидается, затем CreateFont с длинным список параметров создает объект HFONT, обозначаемый значением его описателя, и прикрепляет HFONT к CFont. Пока все хорошо. Метод SetFont вызывается для ссылки окна c_InputData, управляющий элемент CEdit (если не знаете, как сделать это, прочитайте статью об избегании GetDlgItem). В итоге он генерирует сообщение для управляющего элемента редактирования, который можно упростить, как показано ниже (читайте код MFC, чтобы узнать реальные детали).

void CWnd::SetFont(CFont * font)
{
    ::SendMessage(m_hWnd, WM_SETFONT, font->m_hObject, TRUE);
}

Заметьте, что управляющему элементу отправляется значение HFONT. Пока все хорошо.

Теперь покидаем блок, в котором была объявлена переменная. Вызывается деструктор CFont::~CFont. Когда вызывается деструктор для обертки, связанный с ним объект Windows уничтожается. Объяснение деструктора может быть упрощено и проиллюстрировано, как в следующем коде (истина несколько сложней: читайте исходники MFC сами):

CFont::~CFont()
{
    if(m_hObject != NULL)
    {
       ::DeleteObject(m_hObject);
    }
}

Когда управляющий элемент редактирования собирается рисовать себя в своем обработчике WM_PAINT, он хочет выбрать связанный с ним шрифт в свой контекст отображения (DC). Вы можете представить код таким, как показано ниже. Это очень упрощенный код, являющийся примерным, а не точным, и в исходниках MFC его нет, потому что он является частью нижележащей реализации Windows.

edit_OnPaint(HWND hWnd)
{
    HDC dc;
    PAINTSTRUCT ps;
    HFONT font;
    dc = BeginPaint(hWnd, &ps);
    font = SendMessage(hWnd, WM_GETFONT, 0, 0);
    SelectObject(dc, font);
    TextOut(dc, ...);
}

Теперь посмотрим, что происходит. CFont был уничтожен, что в свою очередь уничтожило HFONT. Но HFONT уже был передан управляющему элементу EDIT и находится там. Когда SelectObject делается внутри обработчика редактирования, он задает недействительный описатель, следовательно, SelectObject игнорируется. Поэтому оказывается, что изменений нет.

Так почему это работало, когда был выбран ANSI_FIXED_FONT? Готовые объекты имеют специальные свойства, и одним из специальных свойств является то, что DeleteObject игнорируется для готовых объектов. Вообще-то код был неправильным и работал только потому, что готовые объекты вообще не удаляются. (Если вы слышали, что нельзя удалять готовые объекты, то вы начинали как программист Windows 3.0 (Win16) или говорили с кем-то, кто начинал с этого. Данная ошибка была исправлена с выпуском 16-битной Windows 3.1.)

Как обойти это? Продолжайте читать...