发布者: Tank

上一部分分析了wxWidgets事件机制实现时一些基本概念,和所涉及到的数据结构。基于此继续讨论事件哈希表的建立,事件表相关宏处理的背后, 触发事件处理的方式,以及事件在不同平台的分发过程。

1、事件哈希表的实现

wxEventHashTable构造函数里面,并没有构建哈希表,而是用一个布尔变量标识哈希表尚未重建,采用这种延迟重建的方式来实现哈希表, 哈希表真正建立,发生在第一次查找事件表时,重建以后,设置布尔变量,以后只要对哈希表查找即可,从而加速了事件表的查找。 哈希函数采用除以size取余的方法。

以下是wxEventTable -> wxEventHashTable的过程

  1. 取当前事件表table
  2. table为空结束
  3. 遍历table中所有事件表条目,把事件表条目加入哈希表
  4. table = table->base,转1继续对父类事件表哈希
void wxEventHashTable::InitHashTable()
{
    // Loop over the event tables and all its base tables.
    const wxEventTable *table = &m_table;
    while (table)
    {
        // Retrieve all valid event handler entries
        const wxEventTableEntry *entry = table->entries;
        while (entry->m_fn != 0)
        {
            // Add the event entry in the Hash.
            AddEntry(*entry);

            entry++;
        }

        table = table->baseTable;
    }
 //....
}

void wxEventHashTable::AddEntry(const wxEventTableEntry &entry)
{
     //..
    EventTypeTablePointer *peTTnode = &m_eventTypeTable[entry.m_eventType % m_size];
    EventTypeTablePointer  eTTnode = *peTTnode;

    if (eTTnode)
    {
        if (eTTnode->eventType != entry.m_eventType)
        {
            // Resize the table!
            GrowEventTypeTable();
            // Try again to add it.
            AddEntry(entry);
            return;
        }
    }
    else
    {
        eTTnode = new EventTypeTable;
        eTTnode->eventType = entry.m_eventType;
        *peTTnode = eTTnode;
    }

    // Fill all hash entries between entry.m_id and entry.m_lastId...
    eTTnode->eventEntryTable.Add(&entry);
}

对于上面的第3步,把事件条目加入哈希表,wxEventTableEntry -> wxEventHashTable的过程

  1. 由事件表表条中事件类型对哈希表长度取模得到i 
  2. 根据i的值,得到事件类型表地址数组m_eventTypeTable的下标 
  3. m_eventTypeTable[i] = 0,说明该事件类型事件类型表尚未填入哈希表,转4 ,否则转5 
  4. 新建一个事件类型表,将表地址填入哈希表,转6 
  5. 该事件类型表已经存在,冲突,扩容哈希表,重新回到1 
  6. 将事件表条目的地址填入事件类型表的eventEntryTable,结束 

2、事件表有关宏的背后

wxEvtHandler中有三个静态成员,sm_eventTable sm_eventHashTable分别代表当前事件处理类事件表和事件哈希表, 要被子类连接事件表所有为protected。所有要处理事件的类继承wxEvtHandler,需要重新定义这个两个静态成员。 因此两个虚函数分别返回当前类中事件表和事件哈希表。另一个私有的静态成员sm_eventTableEntries表示当前类的 事件条目数组,用于构成事件表。

class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
{
//..
private:
    static const wxEventTableEntry sm_eventTableEntries[];
protected:
 static const wxEventTable sm_eventTable;
    virtual const wxEventTable *GetEventTable() const;

    static wxEventHashTable   sm_eventTable;
    virtual wxEventHashTable& GetEventHashTable() const;
//..
}

在类的声明中使用DECLARE_EVENT_TABLE(),实际上市覆盖wxEvtHandler中的事件表和事件哈希表,并重写返回当前 类事件表和事件哈希表的虚函数

#define DECLARE_EVENT_TABLE() \
    private: \
        static const wxEventTableEntry sm_eventTableEntries[]; \
    protected: \
        static const wxEventTable        sm_eventTable; \
        virtual const wxEventTable*      GetEventTable() const; \
        static wxEventHashTable          sm_eventHashTable; \
        virtual wxEventHashTable&        GetEventHashTable() const;

在类的实现中使用BEGIN_EVENT_TABLEEND_EVENT_TABLE()实际上初始化当前类的事件表(一个静态成员), 事件表父指针指向父类事件表,事件表的条目指针事件等于事件条目数组首地址。事件哈希表有事件表生成, 这里还没有构造,值是分配了31个为0的空间。中间的一堆事件映射宏实际上是用一堆5元组初始化当前类的事件条目表。

#define BEGIN_EVENT_TABLE(theClass, baseClass) \
    const wxEventTable theClass::sm_eventTable = \
        { &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; \
    const wxEventTable *theClass::GetEventTable() const \
        { return &theClass::sm_eventTable; } \
    wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable); \
    wxEventHashTable &theClass::GetEventHashTable() const \
        { return theClass::sm_eventHashTable; } \
    const wxEventTableEntry theClass::sm_eventTableEntries[] = { \

 EVT_MENU(Minimal_About, MyFrame::OnAbout)
 DECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL),

#define END_EVENT_TABLE() DECLARE_EVENT_TABLE_ENTRY( wxEVT_NULL, 0, 0, 0, 0 ) };

这样每个事件处理的类会用事件映射宏的5元组,构造事件条目表,事件条目表首地址, 放在事件表中,同时事件表会记录父类的事件表地址。哈希表初始化为一堆0地址,搜索一次后, 会把当前事件表和父类所有的事件表哈希到当前事件哈希表中,这样以后该类的对象, 事件表查找,只要查找已经构建好的事件哈希表, 事件复杂度近似O(1)。

3、引发处理事件的方式

上面已经分析过事件处理的流程,那么什么情况会触发一个事件被处理呢?有以下几种方式 * 窗口或控件感应到操作(来自用户的操作引发事件处理流程)  * 调用wxTheApp->ProcessPendingEvents() * 调用wxEvtHandler::ProcessEvent(wxEvent &event)(应用程序自己调用)

wx保存了一个全局的指针链表,里面保存当前应用程序所有事件处理类wxEvtHandler对象的指针

/* code: common/event.cpp:144  */
wxList *wxPendingEvents = (wxList *)NULL;

对于所有事件类的基类wxEvtHandler,有个成员m_pendingEvents保存当前wxEvtHandler对象的未决事件

class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
{
     //..
     wxList*             m_pendingEvents;
     //..
}

遍历全局链表未决事件链表wxPendingEvents,取出所有的wxEvtHandler,然后调用每个wxEvtHandler上的ProcessPendingEvents()方法

/* code: src/common/Appbase.cpp:267 */
void wxAppConsole::ProcessPendingEvents()
{

    // ...
  
   // iterate until the list becomes empty
    wxList::compatibility_iterator node = wxPendingEvents->GetFirst();
    while (node)
    {
        wxEvtHandler *handler = (wxEvtHandler *)node->GetData();
        wxPendingEvents->Erase(node);

        // In ProcessPendingEvents(), new handlers might be add
        // and we can safely leave the critical section here.
        wxLEAVE_CRIT_SECT( *wxPendingEventsLocker );

        handler->ProcessPendingEvents();

        wxENTER_CRIT_SECT( *wxPendingEventsLocker );

        node = wxPendingEvents->GetFirst();
    }
    
     // ...
}

wxEvtHandler上的ProcessPendingEvents()方法中wxEvtHandler遍历处理自己未决链表m_pendingEvents的事件

void wxEvtHandler::ProcessPendingEvents()
{
     //..
    size_t n = m_pendingEvents->size();
    for ( wxList::compatibility_iterator node = m_pendingEvents->GetFirst();
          node;
          node = m_pendingEvents->GetFirst() )
    {
        wxEventPtr event(wx_static_cast(wxEvent *, node->GetData()));

        // It's important we remove event from list before processing it.
        // Else a nested event loop, for example from a modal dialog, might
        // process the same event again.

        m_pendingEvents->Erase(node);

        wxLEAVE_CRIT_SECT( Lock() );

        ProcessEvent(*event);

        wxENTER_CRIT_SECT( Lock() );

        if ( --n == 0 )
            break;
    }
     //..
}

wxEvtHandler提供了向自己接未决链表加入事件的方法,wxEvtHandler:AddPendingEvent, 注意这里只是把event clone一份,然后加入未决链表, 并不处理就返回。

void wxEvtHandler:AddPendingEvent(wxEvent& event)

wxEvtHandler::ProcessEvent用于立即处理这个事件。

bool wxEvtHandler::ProcessEvent(wxEvent& event)

4、消息分发机制

上述所有wxEvent相关的东西都是跨平台的,wxEvent是wx定义出来的一个wx事件类, wx程序中处理的都是wxEvent,并不设计到具体平台上的消息处理。然后实际上, 对于GUI程序设计每个平台都有消息循环、消息分发机制,wx为了跨平台提供了一个抽象层, 屏蔽了平台相关的消息处理部分。在include/wx下的头文件提供了wx对外公共的接口, 利用里面的接口我们可以编写平台无关的GUI的程序,然而include/wx中头文件接口的实现却依赖于具体的平台, 实际上上平台相关的接口位于/include/wx/gtk /include/wx/msw等,但用户不必关心这里平台相关的细节, 只要利用提供的公共接口编程即可。下面分析wx中消息分发以及在不同平台的实现。

wx程序启动流程如下: * 建立个wxApp子类MyApp的实例 * 调用MyApp中重写的虚函数wxApp::OnInit完成初始化(主要是创建顶层窗口),返回false结束 * 调用AppBase::OnRun->wxAppBase::MainLoop进入消息循环

/* src/common/appcmn.cpp::357 */
int wxAppBase::OnRun()
{
    // see the comment in ctor: if the initial value hasn't been changed, use
    // the default Yes from now on
    if ( m_exitOnFrameDelete == Later )
    {
        m_exitOnFrameDelete = Yes;
    }
    //else: it has been changed, assume the user knows what he is doing

    return MainLoop();
}

// .....

int wxAppBase::MainLoop()
{
    wxEventLoopTiedPtr mainLoop(&m_mainLoop, new wxEventLoop);

    return m_mainLoop->Run();
}

/* inclucde/wx/app.h:329 */
class WXDLLIMPEXP_CORE wxAppBase : public wxAppConsole
{
     //..
     wxEventLoop *m_mainLoop;
     //..
}

m_mainLoop是个事件循环类wxEventLoop的对象指针,wxEventLoop是个平台相关的事件循环类,从此进入不同平台的消息循环

5、MSW版本中消息分发机制

WinMain -> wxEntry -> wxEntryReal -> wxAppBase::OnRun -> wxAppBase::MainLoop ->
wxEventLoopManual::Run -> wxEventLoop::Dispatch -> wxEventLoop::ProcessMessage
/* src/common.Evtloopcmn.cpp:65 */
int wxEventLoopManual::Run()
{ 
                while ( !Pending() && (wxTheApp && wxTheApp->ProcessIdle()) )
                    ;

                if ( m_shouldExit )
                {
                    while ( Pending() )
                        Dispatch();

                    break;
                }
}

程序进入一个无限的循环,如果没消息处理,就处理idle消息,有消息就分发消息

bool wxEventLoop::Dispatch()
{
     // ..

    MSG msg;
    BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);

    ProcessMessage(&msg);


     
     //..
}

取消息处理消息,这里的消息是WXMSG *msg typedef struct tagMSG WXMSG; 在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下(参见MSDN):

typedef struct tagMSG {     // msg 
   HWND hwnd;               //标识窗口过程接收消息的窗口
   UINT message;          //指定消息号
   WPARAM wParam;          //指定有关消息的附加信息。 确切含义取决于 message 成员的值
   LPARAM lParam;          //指定有关消息的附加信息。 确切含义取决于 message 成员的值。
   DWORD time;               //指定消息已传递的时间
   POINT pt;               //当消息已传递了,指定光标位置,在屏幕坐标
} MSG;

这里的消息就是windows中的消息了

void wxEventLoop::ProcessMessage(WXMSG *msg)
{
    // give us the chance to preprocess the message first
    if ( !PreProcessMessage(msg) )
    {
        // if it wasn't done, dispatch it to the corresponding window
        ::TranslateMessage(msg);
        ::DispatchMessage(msg);
    }
}

这里就是win32 SDK中的消息循环了,说白了wx中的事件处理,最终还是用了平台上的消息机制, wx只是完成了跨平台的封装,对用户屏蔽了平台相关的是实现细节,抽象出一个用户直接利用的抽象层。

  • windows中的消息WXMSG怎么转换成wxEvent*

    wxEntryReal -> wxEntryStart -> wxApp::Initialize() -> wxApp:: RegisterWindowClasses()

注册一个窗口类,这个窗口类绑定了窗口处理过程

/* src/msw/app.cpp */
bool wxApp::RegisterWindowClasses()
{
    WNDCLASS wndclass;
    wxZeroMemory(wndclass);

    // for each class we register one with CS_(V|H)REDRAW style and one
    // without for windows created with wxNO_FULL_REDRAW_ON_REPAINT flag
    static const long styleNormal = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    static const long styleNoRedraw = CS_DBLCLKS;

    // the fields which are common to all classes
    wndclass.lpfnWndProc   = (WNDPROC)wxWndProc;
    wndclass.hInstance     = wxhInstance;
    wndclass.hCursor       = ::LoadCursor((HINSTANCE)NULL, IDC_ARROW);

    // register the class for all normal windows
    wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wndclass.lpszClassName = wxCanvasClassName;
    wndclass.style         = styleNormal;

    if ( !RegisterClass(&wndclass) )
    {
        wxLogLastError(wxT("RegisterClass(frame)"));
    }

     //..
}

然后里创建窗口

/* src/msw/window.cpp */
bool wxWindowMSW::MSWCreate(const wxChar *wclass,
                            const wxChar *title,
                            const wxPoint& pos,
                            const wxSize& size,
                            WXDWORD style,
                            WXDWORD extendedStyle)
{
     wxString className(wclass);
    if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
    {
        className += wxT("NR");
    }

    // do create the window
    wxWindowCreationHook hook(this);

    m_hWnd = (WXHWND)::CreateWindowEx
                       (
                        extendedStyle,
                        className,
                        title ? title : m_windowName.c_str(),
                        style,
                        x, y, w, h,
                        (HWND)MSWGetParent(),
                        (HMENU)controlId,
                        wxGetInstance(),
                        NULL                        // no extra data
                       );

    if ( !m_hWnd )
    {
        wxLogSysError(_("Can't create window of class %s"), className.c_str());

        return false;
    }

    SubclassWin(m_hWnd);
}

其中,void wxWindowMSW::SubclassWin(WXHWND hWnd)用于设置新窗口的窗口处理过程

WXLRESULT wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{
     //...
     switch ( message )
    {
        case WM_CREATE:

         case WM_LBUTTONDOWN:
        case WM_LBUTTONUP:
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONDOWN:
        case WM_RBUTTONUP:
        case WM_RBUTTONDBLCLK:
        case WM_MBUTTONDOWN:
        case WM_MBUTTONUP:
        case WM_MBUTTONDBLCLK:
}

可见MSW 版本中的事件wxEvent由windows中消息WN_XX而来

6、GTK版本中消息分发机制

程序启动后,GTK版本进入的gtk的wxEventLoop事件循环类,GTK是一种事件驱动工具包,这意味着它将在gtk_main函数 中一直等待,直到事件发生和控制权被传递给相应的函数。gtk_main()是在每个GTK应用程序都要调用的函数。 当程序运行到这里时, Gtk将进入等待态,等候X事件(比如点击按钮或按下键盘的某个按键)、Timeout 或文件输入/输出发生。

/* src/gtk/evtloop.cpp */
int wxEventLoop::Run()
{
    // event loops are not recursive, you need to create another loop!
    wxCHECK_MSG( !IsRunning(), -1, _T("can't reenter a message loop") );

    wxEventLoopActivator activate(this);

    m_impl = new wxEventLoopImpl;

    gtk_main();

    OnExit();

    int exitcode = m_impl->GetExitCode();
    delete m_impl;
    m_impl = NULL;

    return exitcode;
}


-EOF-
睿初科技软件开发技术博客,转载请注明出处

blog comments powered by Disqus