wxWidgets源码分析(2) - App主循环
发布日期:2021-05-09 05:57:56 浏览次数:14 分类:技术文章

本文共 8253 字,大约阅读时间需要 27 分钟。

目录

APP主循环

MainLoop

前面的wxApp的启动代码可以看到,执行完成wxApp::OnInit()函数后,接着就执行wxApp::OnRun()函数进入App的主循环,wxApp继承自wxAppBase,所以实际调用的是wxApp::OnRun(),过程如下:

wxAppBase::OnRun() -> wxAppConsole::OnRun() -> wxAppConsoleBase::MainLoop()

调用关系如下:

// src/common/appbase.cppint wxAppBase::OnRun() { return wxAppConsole::OnRun(); }int wxAppConsoleBase::OnRun(){    return MainLoop();}

下面继续分析wxAppConsoleBase::MainLoop()的代码:

  1. 构建消息循环辅助对象wxEventLoopBaseTiedPtr
  2. 调用当前App的OnLaunched()方法;
  3. 调用主循环Run()函数,但是这个Run函数到底是谁的函数呢?
// src/common/appbase.cppint wxAppConsoleBase::MainLoop(){    wxEventLoopBaseTiedPtr mainLoop(&m_mainLoop, CreateMainLoop());    if (wxTheApp)        wxTheApp->OnLaunched();        return m_mainLoop ? m_mainLoop->Run() : -1;}wxEventLoopBase *wxAppConsoleBase::CreateMainLoop(){    return GetTraits()->CreateEventLoop();}wxEventLoopBase* wxGUIAppTraits::CreateEventLoop(){    return new wxEventLoop;}

消息循环对象的创建

上文遗留一个问题,Run函数到底是哪个函数,这个问题肯定在wxEventLoopBaseTiedPtr对象中,我们看看这个消息循环辅助对象的创建:

  1. wxEventLoopBaseTiedPtr从哪里来的呢,这里需要重点关注宏展开:
  1. 使用wxDEFINE_TIED_SCOPED_PTR_TYPE宏创建出来的,wxDEFINE_TIED_SCOPED_PTR_TYPE(wxEventLoopBase)创建了wxEventLoopBaseTiedPtr类型;
  2. wxEventLoopBaseTiedPtr继承自wxEventLoopBasePtr,父类是通过 wxDEFINE_SCOPED_PTR_TYPE宏创建的,对于此对象来说创建的就是wxEventLoopBasePtr。
  1. 操作中需要重点关注的2个函数:T * operator->() constT & operator*() const
  1. 参考构造函数可以得出,这个变了保存的就是wxApp::m_mainLoop变量的地址,同时在构造函数中将wxApp::CreateMainLoop()赋值给了wxApp::m_mainLoop变量;
  2. 这两个函数是重载操作符,通过->操作实际使用的的是内部保存的*m_ptr变量,这样在通过m_mainLoop->Run()调用时,实际调用的是wxApp::m_mainLoop->Run()函数;

源代码如下:

// src/common/appbase.cpp// 类型 wxEventLoopBaseTiedPtr 的定义wxDEFINE_TIED_SCOPED_PTR_TYPE(wxEventLoopBase)// include/wx/scopedptr.h// wxDEFINE_TIED_SCOPED_PTR_TYPE 宏定义,可以看到这里的定义:// 1. 定义了 wxEventLoopBaseTiedPtr 类型// 2. 对应的父类是 wxEventLoopBasePtr#define wxDEFINE_TIED_SCOPED_PTR_TYPE(T)        \    wxDEFINE_SCOPED_PTR_TYPE(T)                 \    class T ## TiedPtr : public T ## Ptr        \    {                                           \    public:                                     \        T ## TiedPtr(T **pp, T *p)              \            : T ## Ptr(p), m_pp(pp)             \        {                                       \            m_pOld = *pp;                       \            *pp = p;                            \        }                                       \    private:                                    \        T **m_pp;                               \        T *m_pOld;                              \    };// 进而我们看下父类 wxEventLoopBasePtr 的定义// 改类中 重载*和->操作符#define wxDEFINE_SCOPED_PTR_TYPE(T)    \    wxDECLARE_SCOPED_PTR(T, T ## Ptr)  \    wxDEFINE_SCOPED_PTR(T, T ## Ptr)    #define wxDECLARE_SCOPED_PTR(T, name) \class name                          \{                                   \    ...    // 重载*和->操作符,很关键    T & operator*() const           \    {                               \        wxASSERT(m_ptr != NULL);    \        return *m_ptr;              \    }                               \    T * operator->() const          \    {                               \        wxASSERT(m_ptr != NULL);    \        return m_ptr;               \    }                               \};

再回头我们看下 wxAppConsoleBase::CreateMainLoop() 函数的实现,此对象返回了一个新创建的wxEventLoop对象,过程如下:

  1. 调用 CreateMainLoop 这个函数是父类的函数;
  2. wxAppConsoleBase::GetTraits()会调用CreateTraits来创建traits,这里CreateTraits是虚方法,而且wxApp的继承关系是wxAppw -> wxAppBase,所以实际调用的是wxAppBase::CreateTraits(),所以这个loopBase实际是调用wxGUIAppTraits::CreateEventLoop()实现消息循环对象的创建,最终创建了 wxEventLoop 对象;
// src/common/appbase.cpp// wxEventLoopBase *wxAppConsoleBase::CreateMainLoop(){    return GetTraits()->CreateEventLoop();}// src/common/appcmn.cpp// 虚方法,调用最后一次的实现wxAppTraits *wxAppBase::CreateTraits(){    return new wxGUIAppTraits;}wxAppTraits *wxAppConsoleBase::GetTraits(){    // FIXME-MT: protect this with a CS?    if ( !m_traits )    {        m_traits = CreateTraits();        wxASSERT_MSG( m_traits, wxT("wxApp::CreateTraits() failed?") );    }    return m_traits;}// src/common/app.cppwxEventLoopBase* wxGUIAppTraits::CreateEventLoop(){    return new wxEventLoop;}

消息循环

上面的代码分析,我们知道消息循环对象是wxEventLoop类型的,此对象保存在wxAppConsoleBase::m_mainLoop变量中,接着调用m_mainLoop->Run() 执行(下面的分析都是基于windows系统,其他的系统类似):

跟踪代码,先理清楚 wxEventLoop 的继承关系:

wxEventLoop -> wxGUIEventLoop -> wxMSWEventLoopBase -> wxEventLoopManual -> wxEventLoopBase

接着Run函数实际调用的是wxEventLoopBase::Run()

// src/common/apploopcmn.cppint wxEventLoopBase::Run(){    // ...忽略非关键流程的代码    // Finally really run the loop.    return DoRun();}

DoRun函数是wxEventLoopBase定义的纯虚方法,所以这个函数会调用最接近wxEventLoop的实现,这里调用的是wxEventLoopManual::DoRun(),继续看此函数的实现(只保留关键代码):

此函数有2层循环:

  1. 正常的消息循环,所有的用户消息都在这里接收和派发,如果收到WM_QUIT消息则退出此循环,进入到退出前剩余消息的循环;
  2. 退出前剩余消息的循环,检查剩余的Pending状态的消息,如果有则处理之,所有Pending状态的消息都处理完成后,退出此循环,函数返回。
int wxEventLoopManual::DoRun(){    // this is the event loop itself    for ( ;; )    {        // a message came or no more idle processing to do, dispatch        // all the pending events and call Dispatch() to wait for the        // next message        if ( !ProcessEvents() )        {            // we got WM_QUIT            break;        }    }    for ( ;; )    {        bool hasMoreEvents = false;        if ( wxTheApp && wxTheApp->HasPendingEvents() )        {            wxTheApp->ProcessPendingEvents();            hasMoreEvents = true;        }        if ( Pending() )        {            Dispatch();            hasMoreEvents = true;        }        if ( !hasMoreEvents )            break;    }    return m_exitcode;}

继续跟踪 wxEventLoopManual::ProcessEvents() 的处理:

  1. 检查是否有Pending的消息,如果有则处理之,这些消息是在收到本消息前还未处理的消息,所以优先处理,如果是程序退出请求,则完成并退出;
  2. 否则继续调用Dispatch处理当前消息。
bool wxEventLoopManual::ProcessEvents(){    if ( wxTheApp )    {        wxTheApp->ProcessPendingEvents();        if ( m_shouldExit )            return false;    }    return Dispatch();}

Dispatch是虚方法,此方法是体系结构相关的,需要实现在各体系结构中,对于本流程来说,调用的是wxGUIEventLoop::Dispatch(),这个函数的处理过程就是获取到下一条消息,然后处理。

函数中使用到了另外两个虚方法GetNextMessageProcessMessage,分别调用的是wxMSWEventLoopBase::GetNextMessagewxGUIEventLoop::ProcessMessage

// src/msw/evtloop.cpp// 体系结构相关的函数,用户派发消息bool wxGUIEventLoop::Dispatch(){    MSG msg;    if ( !GetNextMessage(&msg) )        return false;            ProcessMessage(&msg);    return true;}// 调用Win32API执行windows系统的消息收发bool wxMSWEventLoopBase::GetNextMessage(WXMSG* msg){    const BOOL rc = ::GetMessage(msg, NULL, 0, 0);    ...    return true;}

消息派发

上文中讲述了消息循环的过程,接着我们看消息的派发过程:

消息派发是由wxGUIEventLoop::ProcessMessage处理的,先预处理,也就是本窗口处理,如果是本窗口的消息则处理掉,否则再调用::DispatchMessage分发给对应的窗口。

// 派发消息void wxGUIEventLoop::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);    }}

我们先看看消息的预处理过程wxGUIEventLoop::PreProcessMessage

  1. 通过msg的HWND来获取wxWindow指针,通过调用wxGetWindowFromHWND实现,具体如何获取到的后文有介绍;
  2. tooltip消息的处理,鼠标移动时调用;
  3. accelerators消息,这种消息处理优先级最高;
  4. 其他消息的处理。

注意到消息处理流程:从本窗口开始,如果本窗口不处理则丢给父窗口处理,直到最顶层窗口,如果还没有处理则返回失败,继续调用系统函数分发之。

bool wxGUIEventLoop::PreProcessMessage(WXMSG *msg){    HWND hwnd = msg->hwnd;    wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);    wxWindow *wnd;#if wxUSE_TOOLTIPS    // we must relay WM_MOUSEMOVE events to the tooltip ctrl if we want it to    // popup the tooltip bubbles    if ( msg->message == WM_MOUSEMOVE )    {        // we should do it if one of window children has an associated tooltip        // (and not just if the window has a tooltip itself)        if ( wndThis->HasToolTips() )            wxToolTip::RelayEvent((WXMSG *)msg);    }#endif // wxUSE_TOOLTIPS    // try translations first: the accelerators override everything    for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )    {        if ( wnd->MSWTranslateMessage((WXMSG *)msg))            return true;        if ( wnd->IsTopLevel() )            break;    }    // now try the other hooks (kbd navigation is handled here)    for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )    {        if ( wnd->MSWProcessMessage((WXMSG *)msg) )            return true;        if ( wnd->IsTopLevel() )            break;    }    // no special preprocessing for this message, dispatch it normally    return false;}

总结

到此为止,消息的循环派发过程就已经可以正常使用了,在windows系统中,调用Win32的::DispatchMessage可以将消息发送给指定的窗口,实际的过程就是消息派发线程调用该窗口的WinProc函数。

上一篇:wxWidgets源码分析(3) - 消息映射表
下一篇:wxWidgets源码分析(1) - App启动过程

发表评论

最新留言

感谢大佬
[***.8.128.20]2025年04月30日 22时09分19秒