По мотивам SWL3
От: limax Россия http://mem.ee
Дата: 27.05.03 15:41
Оценка:
Определения:
SWL — оригинал by Kluev. здесь. Идея специализации аргументов обработчиков для конкретных сообщений и построение отсортированной карты сообщений для бинарного поиска по ней.
SWL2 — вариант by WolfHound. здесь
Автор: WolfHound
Дата: 27.04.03
. Много разных улучшений, более удобная карта и прочее.
SWL3 — вариант by lboss. здесь
Автор: lboss
Дата: 08.05.03
. Отсутствие явной карты сообщений. Её роль выполняют хитрые шаблоны возвращаемых значений обработчиков. Этот вариант и будем разбирать дальше.

Существуют несколько проблем:
  1. Необходимость ручного определения processMessage в каждом наследном классе. Не очень приятная альтернатива — запись базового класса в OnMessage<ThisClass,ID,BaseClass>. (OnMessage — это бывший MsgResult)

  2. Нет возможности сделать обработчик, а точнее нотификатор или трейсер, который будет вызываться всегда, вне зависимости от его переопределения в наследном классе. Причём первыми должны получать уведомление базовые классы, а затем наследники.

    Понадобилось для создания TopFrame со счётчиком экземпляров. Как только последнее окно TopFrame будет уничтожено, нужно вызвать PostQuitMessage. Трейсеры для WM_CREATE (++count(); ) и WM_DESTROY (if (0 == --count()) PostQuitMessage(0); ).

  3. Отсутствие проверки существования настоящего обработчика на этапе компиляции. Т.е. можно определить OnMessage<This,WM_PAINT> drebeden_den_den(int,char), но ошибка вылезет только на этапе выполнения в отладочной версии, при обработки этого сообщения, если оно вообще придёт за время тестирования.


Вот что я наваял по этому поводу:
  1. Создал маленькую обёртку MsgChain, которая переопределяет processMessage
    template <class TThis, class TBase> struct MsgChain : TBase
        {
        typedef MsgChain<TThis,TBase> Base; // для использования в наследнике
        virtual    bool    processMessage( MsgData & msg )
            {
            return Internal::MsgMap<TThis>::staticInstance().processMessage(*(TThis*)this, msg)
                || TBase::processMessage(msg);
            }
        };

    Использование:
    //Вместо
    class MyWnd : public Wnd
    //пишем
    class MyWnd : public MsgChain<MyWnd,Wnd>


  2. Создал TraceMessage похожий на OnMessage. Регистрирует обработчик в альтернативной карте сообщений. Реализация такая же как и у OnMessage, только используется другой экземпляр карты.

    Соответственно, парный TraceChain для traceMessage.
    template <class TThis, class TBase> struct TraceChain : TBase
        {
        typedef TraceChain<TThis,TBase> Base; // для использования в наследнике
        virtual    void    traceMessage( const MsgData & msg ) //const &msg, т.к. не должно влиять на обработку.
            {
            //обратный порядок
            TBase::traceMessage(msg);
            Internal::TraceMsgMap<TThis>::staticInstance().processMessage(*(TThis*)this, msg);
            }
        };


    Использование:
    #ifndef VERBOSE_LEVEL
    //выводить кое-что, самое основное.
    #define VERBOSE_LEVEL 1
    #endif
    #define Verbose(level) if (level<=VERBOSE_LEVEL)
    
    class TopFrame : public TraceChain<TopFrame,Wnd>
        {
        ThreadSafe::Count& count()
            {
            static ThreadSafe::Count i(0); //в однопоточной версии можно использовать обычный static int
            return i;
            }
        public:
            TraceMessage<TopFrame, WM_CREATE> traceMsg( const Msg<WM_CREATE>&)
                {
                int i=++count();
                Verbose(1) printf("%i: TraceMessage<TopFrame, WM_CREATE> onMsg(Msg<WM_CREATE>&)\n", i);
                return false;
                }
            TraceMessage<TopFrame, WM_DESTROY> traceMsg( const Msg<WM_DESTROY>&)
                {
                int i=--count();
                Verbose(1) printf("%i: TraceMessage<TopFrame, WM_DESTROY> onMsg(Msg<WM_DESTROY>&)\n", i);
                if (0==i)
                    ::PostQuitMessage(0);
                return true;
                }
            //...
        };



  3. В месте регистрации обработчика сделал проверку его существования с помощью sizeof.
    sizeof прямого вызова обработчика (а точнее возвращаемого значения функции) не скомпилируется, если обработчика нет.
    template <class TWinClass, UINT _msgID> class OnMessage {
    class Initializer { public:
        Initializer()
            {
            //make sure message there is a handler
            sizeof(((TWinClass*)0)->onMsg(MsgSpec<TWinClass,_msgID>(_msgID, 0, 0)));
            //MsgSpec<class,id> - наследник Msg<id>, от него уже ничего не наследуется.
            //если не использовать специализацию взломщика по классу, то можно написать и так:
            //sizeof(((TWinClass*)0)->onMsg(Msg<_msgID>(_msgID, 0, 0)));
    //продолжить регистрацию...


Какие ещё будут идеи?
Кстати, я не совсем понимаю, что делать с WM_COMMAND и WM_NOTIFY в SWL3. Вручную разбирать, по-старому в обработчике писать switch(ctrlID)/case как-то не хочется. Что можно придумать? Приделать ещё одну карту что ли? И специализировать по id контрола? Что-нибудь вроде OnCommand<ThisClass,id> onCommand(Command<id>&)? Но id ведь может меняться.

P.S. Сейчас пришло в голову, что осталась ещё одна проблема. Можно написать так:
OnMessage<ThisClass,1> onMsg(Msg<1>&);
OnMessage<ThisClass,1> onMsg(Msg<2>&);

и будет дважды зарегистрирован один и тот же обработчик, тогда как второй — вообще не будет.

Проблему можно решить, если проверять отсутствие зарегистрированного обработчика для данного ID при его регистрации. Тогда вторая регистрация выдаст ошибку при создании карты, т.е. на старте программы (ну или как минимум — до создания окна), чего вполне достаточно.

Однако если переделаем так:
struct A
  {
  OnMessage<ThisClass,1> onMsg(Msg<1>&);
  }
struct B : A
  {
  OnMessage<ThisClass,1> onMsg(Msg<2>&);
  }

то ошибка уже не отловится, т.к. во второй карте обработчик ещё не зарегистрирован, но наследуется от базового класса.
Есть идеи?

И вообще, предложения и замечания по SWL* охотно принимаются.
Have fun: Win+M, Ctrl+A, Enter
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.