wxWidgets源码分析 - App主循环
Posted 百无一用程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了wxWidgets源码分析 - App主循环相关的知识,希望对你有一定的参考价值。
目录
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()
的代码:
- 构建消息循环辅助对象
wxEventLoopBaseTiedPtr
; - 调用当前App的
OnLaunched()
方法; - 调用主循环
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
对象中,我们看看这个消息循环辅助对象的创建:
wxEventLoopBaseTiedPtr
从哪里来的呢,这里需要重点关注宏展开:
- 使用
wxDEFINE_TIED_SCOPED_PTR_TYPE
宏创建出来的,wxDEFINE_TIED_SCOPED_PTR_TYPE(wxEventLoopBase)
创建了wxEventLoopBaseTiedPtr类型;wxEventLoopBaseTiedPtr
继承自wxEventLoopBasePtr
,父类是通过wxDEFINE_SCOPED_PTR_TYPE
宏创建的,对于此对象来说创建的就是wxEventLoopBasePtr。
操作中需要重点关注的2个函数:
T * operator->() const
和T & operator*() const
:
- 参考构造函数可以得出,这个变了保存的就是
wxApp::m_mainLoop
变量的地址,同时在构造函数中将wxApp::CreateMainLoop()
赋值给了wxApp::m_mainLoop
变量;- 这两个函数是重载操作符,通过
->
操作实际使用的的是内部保存的*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
对象,过程如下:
- 调用
CreateMainLoop
这个函数是父类的函数; 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层循环:
- 正常的消息循环,所有的用户消息都在这里接收和派发,如果收到
WM_QUIT
消息则退出此循环,进入到退出前剩余消息的循环; - 退出前剩余消息的循环,检查剩余的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()
的处理:
- 检查是否有Pending的消息,如果有则处理之,这些消息是在收到本消息前还未处理的消息,所以优先处理,如果是程序退出请求,则完成并退出;
- 否则继续调用
Dispatch
处理当前消息。
bool wxEventLoopManual::ProcessEvents()
{
if ( wxTheApp )
{
wxTheApp->ProcessPendingEvents();
if ( m_shouldExit )
return false;
}
return Dispatch();
}
Dispatch
是虚方法,此方法是体系结构相关的,需要实现在各体系结构中,对于本流程来说,调用的是wxGUIEventLoop::Dispatch()
,这个函数的处理过程就是获取到下一条消息,然后处理。
函数中使用到了另外两个虚方法GetNextMessage
和ProcessMessage
,分别调用的是wxMSWEventLoopBase::GetNextMessage
和wxGUIEventLoop::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
:
- 通过msg的HWND来获取wxWindow指针,通过调用
wxGetWindowFromHWND
实现,具体如何获取到的后文有介绍; - tooltip消息的处理,鼠标移动时调用;
- accelerators消息,这种消息处理优先级最高;
- 其他消息的处理。
注意到消息处理流程:从本窗口开始,如果本窗口不处理则丢给父窗口处理,直到最顶层窗口,如果还没有处理则返回失败,继续调用系统函数分发之。
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源码分析 - App主循环的主要内容,如果未能解决你的问题,请参考以下文章