30 октября 2006

Как легко и не принужденно держать себя за волосы

В моем проекте в части GUI написаны собственные классы типа CEdit, CButton и etc, которые имеют интерфейсы, используемые бизнес-логикой. Непосредственно реализация этих контролов представлена в классах типа CEditImpl, CButtonImpl и т.п., которые наследуются от ATL::CWindow. Эти C*Impl содержатся в виде private членов внутри соответствующих C*.

Все было отлично. Но вот по событию "нажатие кнопки", приходящему синхронно от CButton в бизнес-логику, эта бизнес-логика закрывает окно, в котором содержится кнопка и все рушится по assert-у в недрах ATL::CWindow. Фигня вся в том, что все это происходит в обработчике WM_LBUTTONDOWN, а закрытие окна ведет к удалению объекта этого самого окна, которое грохает коллекцию его контролов (CEdit, CButton, ...), они в свою очередь грохают объекты CEditImpl, CButtonImpl, ... А деструктор класса ATL::CWindow (от него унаследованы все CButtonImpl) ругается на то, что виндовый контрол "кнопка" еще не уничтожен - внутри его обработчика WM_LBUTTONDOWN мы сейчас сидим.

В ATL/WTL безопасно можно грохать оконный объект в виртуальной OnFinalMessage(), то есть объект CButtonImpl должен пережить CButton, а в CButtonImpl::OnFinalMessage() прибить себя. Но все же CButton тоже должен уметь убивать CButtonImpl, например если виндовый контрол еще не был создан - в таком случае до CButtonImpl::OnFinalMessage() дело не дойдет.

Вобщем было много головной боли по поводу того кто и когда должен грохнуть объект CButtonImpl: то ли сам CButtonImpl, то ли CButton, они должны между собой согласовать действия чтобы:
1. не упать по assert-у в недрах деструктора ATL::CWindow
2. все же удалить CButtonImpl
3. не удалить его несколько раз ;)

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

Решение получилось примерно такое:
class CButtonImpl : ATL::CWindow...
{
public:
void OnFinalMessage() { SharedPointer.release(); }

boost::shared_ptr<CButtonImpl> SharedPointer;
}

class CButton
{
public:
CButton() : _ButtonImpl(new CButtonImpl)
{
}

void Create()
{
_Button->Create();
_Button->SharedPointer = _Button;
}

private:
boost::shared_ptr<CButtonImpl> _ButtonImpl;
};

То есть условно говоря, CButtonImpl держит себя за волосы пока он существует как виндовый контрол.

Ссылка по теме: delete this

Комментариев нет: