首页 > 技术文章 > wxWidgets源码分析(2) - App主循环

psbec 2018-10-13 22:38 原文

APP主循环

MainLoop

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

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

调用关系如下:

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

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

  1. 构建消息循环辅助对象wxEventLoopBaseTiedPtr
  2. 调用当前App的OnLaunched()方法;
  3. 调用主循环Run()函数,但是这个Run函数到底是谁的函数呢?
// src/common/appbase.cpp
int 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.cpp
wxEventLoopBase* 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.cpp
int 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函数。

推荐阅读