Прикрепление и отделение объектов - Классы-обертки и объекты
ОГЛАВЛЕНИЕ
Классы-обертки и объекты
Есть серьезное последствие прикрепления объекта 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.)
Как обойти это? Продолжайте читать...