wxWidgets源码分析(2) - App主循环

目录

  • APP主循环

    • MainLoop
    • 消息循环对象的创建
    • 消息循环
    • 消息派发
    • 总结

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函数。

原文地址:https://www.cnblogs.com/psbec/p/9784565.html

时间: 2024-09-29 11:40:45

wxWidgets源码分析(2) - App主循环的相关文章

wxWidgets源码分析(1) - App启动过程

目录 APP启动过程 wxApp入口定义 wxApp实例化准备 wxApp的实例化 wxApp运行 总结 APP启动过程 本文主要介绍wxWidgets应用程序的启动过程,从app.cpp入手. wxApp入口定义 wxApp通过IMPLEMENT_APP宏注册App类,这个宏同时定义了入口,实现在wx/app.h文件中. // wx/app.h 文件中定义 #define IMPLEMENT_APP(app) wxIMPLEMENT_APP(app); // 可以忽略 wxIMPLEMENT_

jQuery源码分析-jQuery中的循环技巧

Js代码   作者:nuysoft/JS攻城师/高云 QQ:47214707 EMail:[email protected] 声明:本文为原创文章,如需转载,请注明来源并保留原文链接. 前记:本文收集了jQuery中出现的各种遍历技巧和场景 Js代码   // 简单的for-in(事件) for ( type in events ) { } Js代码   // 缓存length属性,避免每次都去查找length属性,稍微提升遍历速度 // 但是如果遍历HTMLCollection时,性能提升非常

wxWidgets源码分析(9) - wxString

目录 wxString wxString的中文字符支持 wxString与通用字符串的转换 字符集转换 wxString wxString的中文字符支持 中文字符的编码格式如下: 汉字 GBK 区位码 UTF-8 UTF-16 中 D6 D0 54 48 E4 B8 AD 4E 2D 文 CE C4 46 36 E6 96 87 65 87 不同操作系统的默认内码 Windows系统(默认GBK):41 42 d6 d0 ce c4 Linux系统(默认UTF-8):41 42 e4 b8 ad

wxWidgets源码分析(8) - MVC架构

目录 MVC架构 wxDocManager文档管理器 框架菜单命令的执行过程 SDI消息传递总结 更新视图 MVC架构 wxDocManager文档管理器 wxWidgets使用wxDocManager类来管理MVC中的文档和视图的对应关系,使用方法: 创建一个wxDocManager对象,然后向此对象中增加文档模板wxDocTemplate对象,文档模板对象中说明了文档类型和该文档对应的文档类.视图类: 将此wxDocManager对象传递给wxDocParentFrame类(SDI),这样框

nova-api源码分析(APP的调用)

3.2  调用APIRouter的 __call__函数 nova/wsgi.py class Router(object): def __init__(self, mapper): self.map = mapper self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map) @webob.dec.wsgify(RequestClass=Request) def __call__(self, req):

nova-api源码分析(APP的创建)

二.nova-api中app加载过程分析 目录结构如下: 上面介绍了nova-api发布所用到的一些lib库,有了上面的基础知识,再来分析nova-api的发布流程,就比较轻松了.nova-api可以提供多种api服务:ec2, osapi_compute, osapi_volume, metadata.可以通过配置项enabled_apis来设置启动哪些服务,默认情况下,四种服务都是启动的.从nova-api的可执行脚本中,可以看出每个nova-api服务都是通过nova.service.WS

wxWidgets源码分析(5) - 窗口管理

窗口管理 所有的窗口均继承自wxTopLevelWindows: WXDLLIMPEXP_DATA_CORE(wxWindowList) wxTopLevelWindows; wxTopLevelWindows Frame窗口创建过程 在使用Frame窗口的时候,我们一般从wxFrame继承,创建时通过调用Create方法进行创建: debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id) { ... Create(parent, id

wxWidgets源码分析(4) - 消息处理过程

目录 消息处理过程 消息如何到达wxWidgets Win32消息与wxWidgets消息的转换 菜单消息处理 消息处理链(基于wxEvtHandler) 消息处理链(基于wxWindow) 总结 消息处理过程 消息如何到达wxWidgets Windows程序有其自身运行的一套规律,::SendMessage是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSen

wxWidgets源码分析(7) - 窗口尺寸

目录 窗口尺寸 概述 窗口Size消息的处理 用户调整Size消息的处理 调整窗口大小 程序调整窗口大小 wxScrolledWindow设置窗口大小 获取TextCtrl控件最合适大小 窗口尺寸 概述 类型 说明 Size 当前窗口实际大小,通过wxWindow::SetSize()获取: Client Size 客户区大小: Best Size 最合适的大小,用户可以实现wxWindow::DoGetBestSize()方法,自定义返回最合适的大小: Best Client Size 最合适