Прикрепление и отделение объектов - Прикрепление описателей к оберткам: Attach()

ОГЛАВЛЕНИЕ

Прикрепление описателей к оберткам: Attach()

Есть операция, соответствующая Detach. Это Attach. Операция Attach принимает аргумент HANDLE подходящего типа и прикрепляет этот описатель к существующему объекту MFC. К объекту MFC не должен быть уже прикреплен описатель.

При покупке сторонней DLL, сообщающей, что одна из ее функций возвращает HFONT, или HWND, или HPEN, или любой иной описатель, можно прикрепить этот объект к соответствующему уже существующему объекту MFC с помощью Attach. Допустим, есть DLL, имеющая операцию getCoolPen для операций, которые она хочет сделать. Она возвращает HPEN, который можно хранить. Но, может быть, удобно хранить его как объект MFC. Один способ сделать это - объявить, к примеру, в производном от CView классе переменную-член (вероятно, защищенную переменную-член),

CPen myCoolPen;
Затем можно сделать нечто вроде:
void CMyView::OnInitialUpdate()
{
    // ...
    myCoolPen.Attach(getCoolPen());
    // ...
}

Заметьте, что это требует понимания последствий вызова getCoolPen. Если автор DLL как следует документировал свой продукт, она четко заявит, что надо удалить HPEN при завершении работы с ним, или что нельзя удалять HPEN, потому что он общий. Часто такую полезную информацию можно узнать лишь путем тщательного изучения кода. Но допустим, известно, что надо делать.

Если надо удалить HPEN, когда он больше не нужен, не надо делать ничего особенного. При уничтожении представления вызываются деструкторы для всех его членов, а значит, вызывается деструктор CPen, удаляя нижележащий HPEN.

Если нельзя удалять HPEN, так как он общий, надо добавить в деструктор для класса, производного от CView, следующую строку:

CMyView::~CMyView()
{
    // ...
    myCoolPen.Detach();
    // ...
}

Создание объектов: FromHandle

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

Таким образом, при выполнении GetFont возвращается ссылка на временный объект. Этот указатель нельзя хранить, потому что в итоге занимаемое им место будет очищено. Следующий код в корне неверен:

class CMyClass : public CView {
protected:
    CFont * myFont;
};

// где угодно в реализации класса
myFont = GetFont();
// или
myFont = CFont::FromHandle(...);

Попытка использовать переменную myFont позже приведет к катастрофической ошибке. Возможно, ошибке ASSERT, или ошибке доступа, или неверному поведению, такому как отсутствие видимого изменения шрифта. Причина в том, что объект был создан с помощью GetFont, добавлен в список временных объектов, а затем удален. При удалении временного объекта нижележащий объект Windows не удаляется, так как временный объект считается лишь посредником.

Правильный способ хранения ссылки на нижележащий объект выглядит так:

CFont * f = GetFont();
if(f == NULL)
    myFont = NULL;
else
{ /* прикрепить его */
    myFont = new CFont;
    myFont->Attach(f->m_hObject);
} /* прикрепить его */

Здесь предполагается, что myFont равен NULL или его значение бессмысленно. Если он не равен NULL, наверняка он уже содержит действительную ссылку CFont. Вам надо решить, должны ли вы удалить эту ссылку, и если вы удалите эту ссылку, что должно произойти с нижележащим HFONT. Вы можете сделать это, только если переменная myFont еще не хранит ссылку на временный объект. В примере выше, поскольку каждый раз создается новый CFont, известно, что это не временный объект. Два возможных алгоритма таковы:

if(myFont != NULL)
    delete myFont; // удалить объект и HFONT
или, как вариант
if(myFont != NULL)
{
    myFont->Detach(); // не трогать HFONT
    delete myFont;
    myFont = NULL;
}

Не забудьте установить член myFont в NULL в конструкторе класса!

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

CFont  * f;
f = somewindow.GetFont();
delete f;

Если вы получите это, то почти сразу узнаете, что удалили временный объект; вскоре после возврата в главный цикл обработки сообщений вы получите ошибку подтверждения.

Окна: FromHandle и FromHandlePermanent

Для классов, производных от CWnd, можно получить временный объект CWnd * из FromHandle. Таким образом, если у вас есть класс CMyCoolWindow, и вы сделаете GetFocus, то можете получить либо не получить настоящий указатель на объект CMyCoolWindow *. Это порождает многочисленные ошибки. Например, следующий код:

CWnd * capture = GetCapture();
if(capture != NULL && capture->m_hWnd == m_hWnd)
{ /* Имеется захват */
    // ... делается что-то
} /* Имеется захват */

Если нужен описатель для реального объекта для данного HWND, надо использовать CWnd::FromHandlePermanent. Он вернет описатель из карты постоянного окна. Заметьте, что CWnd::FromHandle может вернуть описатель для постоянного окна, а может и не вернуть. Гарантии нет.

CWnd и потоки

Карта объекта является потоковой локальной. Отсюда следует, что если вы находитесь в потоке и выполняете CWnd::FromHandle, то получаете новый, временный объект окна, не являющийся тем же самым объектом C++, представлявшим ваш класс изначально. Таким образом, следующее всегда вызывает ошибку в потоке:

CMyCoolWindowClass * me = (CMyCoolWindowClass *)CWnd::FromHandle(hWnd);
me->MyCoolVariable = 17; // изменяет какое-то неизвестное место

В действительности вы получите обобщенный указатель CWnd, а если бы вы сделали:

me->IsKindOf(RUNTIME_CLASS(CMyCoolWindowClass))

то получили бы FALSE. Если бы вы сделали:

CMyCoolWindowClass * me = (CMyCoolWindowClass *)CWnd::FromHandlePermanent(hWnd);

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

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

Вывод

В то время как данная статья уделяет основное внимание объекту CFont, описанные в ней приемы применяются ко всем классам MFC, являющимся обертками для объектов Windows - CGDIObject, суперкласс CPen, CBrush, CFont, CRgn, CPalette и другие, где реализованы Attach, Detach и FromHandle. Подклассы, такие как CPen, переопределяют FromHandle, чтобы принимать HPEN и возвращать CPen *, но в действительности они просто вызывают суперкласс для выполнения всей работы и обеспечивают приведение типов, чтобы все работало правильно в среде C++. Кроме того, класс CWnd имеет Attach, Detach и FromHandle. Класс CWnd имеет еще одну операцию FromHandlePermanent, не рассмотренную в настоящей статье.

Все эти операции позволяют свободно перемещаться между областью объектов Windows, где объекты представлены экземплярами описателей, и областью объектов MFC, где объекты представлены экземплярами классов C++. Статья помогает понять отношение между этими двумя представлениями и то, как использовать их безопасно и без утечек.