パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ソースコード(wxWidgetsのメッセージループ実装)

wxWidgetsライブラリウィンドウズメッセージループをどのように実装するかをソースコードで確認する。

ダウンロードリンク

最新版は以下からダウンロードできる。ただし本項目調査はバージョン3.1.0ソースコードを用いた。

ソースコード

wxWidgetsを利用するソースコード例として動作確認(1)のDesktop1プロジェクトを用いる。アプリケーションクラスDesktop1AppはDesktop1App.hに定義され、継承はDesktop1App->wxApp->wxAppBase->wxAppConsole->wxAppConsoleBaseである。wxApp(GUIを持つアプリケーション)がwxAppConsole(コンソールアプリケーション)を継承する事を留意しておく。

覚え書き
ウィンドウズはアプリケーションをデスクトップ(GUIを持つアプリケーションのほとんど)とコンソールで全く異なった物と扱う事が多いのでこの継承は不自然に見えるかもしれない。

ウィンドウズ依存のコードはメッセージループを含め隠されているが、wxWidgetsが定義するINPLEMENT_APPマクロがDesktop1App.cppファイルに展開する(IMPLEMENT_APP展開)。

#include "wx_pch.h"
#include "Desktop1App.h"
#include <wx/stdpaths.h>
//(*AppHeaders
#include "Desktop1Frame.h"
#include <wx/image.h>
//*)
IMPLEMENT_APP(Desktop1App);
bool Desktop1App::OnInit()
{
...
};

IMPLEMENT_APP展開

INPLEMENT_APP(include\wx\app.h:896)はwxIMPLEMENT_APP(include\wx\app.h:861);に#defineされる。wxWidgetsマニュアルはINPLEMENT_APPでなくwxIMPLEMENT_APPを記載する。wxIMPLEMENT_APPは、wxIMPLEMENT_WXWIN_MAIN(include\wx\msw\app.h:68)、wxIMPLEMENT_APP_NO_MAIN(include\wx\app.h:842)を用いて以下を展開する。ウィンドウズアプリケーションのメインエントリWinMainが定義されwxEntryをコールする。さらにwxGetApp、wxCreateApp、wxTheAppInitializerを定義する。

extern "C" int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
wxCmdLineArgType WXUNUSED(lpCmdLine),
int nCmdShow)
{
...
return wxEntry(hInstance, hPrevInstance, NULL, nCmdShow);
}
Desktop1App& wxGetApp() { return *static_cast<Desktop1App*>(wxApp::GetInstance()); }
wxAppConsole *wxCreateApp()
{
wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNATURE,
"your program");
return new Desktop1App;
}
wxAppInitializer
wxTheAppInitializer((wxAppInitializerFunction) wxCreateApp);

wxEntry(src\msw\main.cpp:281)はオーバーロード(src\msw\main.cpp:175)を経由してwxEntryRealをコールする。

wxEntryReal関数

wxEntryReal(src\common\init.cpp:470)がDesktop1AppのシングルトンポインタwxTheAppを構築/解体し、wxTheAppを用いるメインルーチンとして機能する。

int wxEntryReal(int& argc, wxChar **argv)
{
wxInitializer initializer(argc, argv);
...
wxTRY
{
// app initialization
if ( !wxTheApp->CallOnInit() )
{
return -1;
}
// ensure that OnExit() is called if OnInit() had succeeded
class CallOnExit
{
public:
~CallOnExit() { wxTheApp->OnExit(); }
} callOnExit;
// app execution
return wxTheApp->OnRun();
}
wxCATCH_ALL( wxTheApp->OnUnhandledException(); return -1; )
}

wxInitializerインスタンスがwxTheAppの構築/解体を管理する。コールするwxTheAppメンバ関数はwxAppConsoleBase基底クラスが定義する。

class WXDLLIMPEXP_BASE wxAppConsoleBase : ...
{
public:
...
virtual bool CallOnInit() { return OnInit(); }
...
virtual bool OnInit();
virtual int OnRun();
...
virtual int OnExit();
...
virtual void OnUnhandledException();
...
};

CallOnInitやCallOnExitクラスの存在で解りにくいが、まとめればwxEntryRealは以下に要約できる。

int wxEntryReal(int& argc, wxChar **argv) // 要約版
{
wxInitializer initializer(argc, argv); // wxTheApp管理
if (wxTheApp->OnInit()) // メインウィンドウ構築
{
int exitCode=wxTheApp->OnRun(); // イベントループ実行
wxTheApp->OnExit(); // 終了処理
return exitCode;
}
else
{
return -1;
}
}

wxTheAppシングルトンポインタ

wxTheApp(include\wx\app.h:757)はDesktop1Appのシングルトンインスタンスを指すwxApp型ポインタである(wxApp*)。Desktop1App型としてアクセスする場合はINPLEMENT_APP展開のwxGetAppで参照する(Desktop1App&)。

#define wxTheApp static_cast<wxApp*>(wxApp::GetInstance())

wxInitializerがwxTheAppの構築/解体を管理するが、wxTheAppを構築する関数(wxTheApp構築関数)は事前にグローバル定義する。wxAppInitializer(include\wx\app.h:790)クラスはこれを目的とするもので、wxAppConsoleBase::SetInitializerFunction(include\wx\app.h:453)でwxTheApp構築関数をグローバル変数ms_appInitFnへ保存する。以降、wxTheApp構築関数はwxAppConsoleBase::GetInitializerFunction(include\wx\app.h:455)で取得する。INPLEMENT_APP展開はwxTheApp構築関数としてwxCreateAppを定義し、wxAppInitializerグローバルインスタンス構築へ実引数として与える。

覚え書き
wxAppInitializerの存在は冗長に見えるが、WinMain実行前にライブラリ定義のグローバル変数へ代入するために必要となる。
class WXDLLIMPEXP_BASE wxAppInitializer
{
public:
wxAppInitializer(wxAppInitializerFunction fn)
{ wxApp::SetInitializerFunction(fn); }
};
...
class WXDLLIMPEXP_BASE wxAppConsoleBase : ...
{
public:
...
static void SetInitializerFunction(wxAppInitializerFunction fn)
{ ms_appInitFn = fn; }
static wxAppInitializerFunction GetInitializerFunction()
{ return ms_appInitFn; }
static wxAppConsole *GetInstance() { return ms_appInstance; }
static void SetInstance(wxAppConsole *app) { ms_appInstance = app; }
...
protected:
...
static wxAppInitializerFunction ms_appInitFn;
static wxAppConsole *ms_appInstance;
...
};

wxInitializer(include\wx\init.h:81)はコンストラクタ/デストラクタでwxInitialize/wxUninitializeをコールする。wxInitialize(src\common\init.cpp:530)/wxUnintialize(src\common\init.cpp:558)はリファレンスカウンタgs_initDataを用いて複数コールされてもwxTheAppはただ一つ(シングルトンインスタンス)であることを担保する。実際の構築/解体はwxEntryStart/wxEntryCleanupが行う。wxEntryRealは関数スコープにwxInitializerインスタンスを定義することでwxTheAppの構築/解体を管理する。

typedef wxAppConsole* (*wxAppInitializerFunction)();
...
class WXDLLIMPEXP_BASE wxInitializer
{
public:
...
wxInitializer(int& argc, wxChar **argv)
{
m_ok = wxInitialize(argc, argv);
}
...
~wxInitializer() { if ( m_ok ) wxUninitialize(); }
...
};
static struct InitData
{
InitData()
{
nInitCount = 0;
...
}
...
size_t nInitCount;
...
} gs_initData;
bool wxInitialize(int& argc, wxChar **argv)
{
...
if ( gs_initData.nInitCount++ )
{
return true;
}
return wxEntryStart(argc, argv);
}
void wxUninitialize()
{
...
if ( --gs_initData.nInitCount == 0 )
{
wxEntryCleanup();
}
}

wxEntryStart(include\wx\init.cpp:294)/wxEntryCleanup(include\wx\init.cpp:439)がwxTheAppを構築/解体する。wxTheAppの値はwxAppConsoleBase::SetInstance(include\wx\init.cpp:463)/wxAppConsoleBase::GetInstance(include\wx\init.cpp:462)で設定/取得する。なおwxEntryStartはwxAppConsoleBase::GetInitializerFunctionで得るwxTheApp構築関数(wxCreateApp)でwxTheAppを構築するが、wxAppConsole::SetInstanceコールはwxAppPtr(src\common\init.cpp:82)インスタンスを媒介する。

覚え書き
wxAppPtrクラスの必要性は良く理解できず冗長としか思えない。
bool wxEntryStart(int& argc, wxChar **argv)
{
...
wxAppPtr app(wxTheApp);
if ( !app.get() )
{
wxAppInitializerFunction fnCreate = wxApp::GetInitializerFunction();
if ( fnCreate )
{
app.Set((*fnCreate)());
}
}
...
app.release();
...
return true;
}
...
void wxEntryCleanup()
{
...
if ( wxTheApp )
{
wxTheApp->CleanUp();
wxAppConsole * const app = wxApp::GetInstance();
wxApp::SetInstance(NULL);
delete app;
}
...
}
class wxAppPtr : public wxAppPtrBase
{
public:
wxEXPLICIT wxAppPtr(wxAppConsole *ptr = NULL) : wxAppPtrBase(ptr) { }
~wxAppPtr()
{
if ( get() )
{
wxApp::SetInstance(NULL);
}
}
void Set(wxAppConsole *ptr)
{
reset(ptr);
wxApp::SetInstance(ptr);
}
...
};

wxAppメンバ関数コール(イベントループ)

wxEntryRealがwxTheAppのメンバ関数としてコールするwxAppConsoleBase::OnInit(src\common\appbase.cpp:269)、wxAppConsoleBase::OnRun(src\common\appbase.cpp:299)、wxAppConsoleBase::OnExit(src\common\appbase.cpp:308)は仮想メンバ関数である。OnInitとOnExitはオーバーライドしない限り何もせずリターンするが、OnRunは別の仮想メンバ関数wxAppConsoleBase::MainLoop(src\common\appbase.cpp:373)をコールする。MainLoopはwxEventLoopBaseインスタンスをwxAppConsoleBase::CreateMainLoopで構築しポインタとしてm_mainLoopメンバ変数に保持し、wxEventLoopBase::Runをコールしてイベントループを実行する。m_mainLoopはwxDEFINE_TIED_SCOPED_PTR_TYPE(include\wx\scopedptr.h:191)マクロ定義のwxEventLoopBaseTiedPtrスマートポインタが管理し、MainLoopから抜けると解体され関数コール前の値に復帰する。

wxDEFINE_TIED_SCOPED_PTR_TYPE(wxEventLoopBase)
...
int wxAppConsoleBase::OnRun()
{
return MainLoop();
}
...
int wxAppConsoleBase::MainLoop()
{
wxEventLoopBaseTiedPtr mainLoop(&m_mainLoop, CreateMainLoop());
...
return m_mainLoop ? m_mainLoop->Run() : -1;
}

wxAppConsoleBase::CreateMainLoop(src\common\appbase.cpp:255)はwxAppConsoleBase::GetTraits(src\common\appbase.cpp:340)でwxConsoleAppTraits(include\wx\msw\apptrait.h)インスタンスポインタを得て、wxConsoleAppTraits::CreateEventLoop(src\msw\basemsw.cpp:89)でwxEventLoopインスタンスを構築する。GUIを持つアプリケーション(wxUSE_GUIがtrue)ではwxEventLoopはwxGUIEventLoopに等しい(include\wx\evtloop.h:351)。

wxEventLoopBase *wxAppConsoleBase::CreateMainLoop()
{
return GetTraits()->CreateEventLoop();
}
...
wxAppTraits *wxAppConsoleBase::CreateTraits()
{
return new wxConsoleAppTraits;
}
wxAppTraits *wxAppConsoleBase::GetTraits()
{
if ( !m_traits )
{
m_traits = CreateTraits();
}
return m_traits;
}
wxEventLoopBase *wxConsoleAppTraits::CreateEventLoop()
{
return new wxEventLoop();
}
#if wxUSE_GUI
class wxEventLoop : public wxGUIEventLoop { };
#else // !wxUSE_GUI
...
#endif

wxGUIEventLoopクラス

wxGUIEventLoop(include\wx\msw\evtloop.h:25)はイベントループクラスのウィンドウズ実装で、継承はwxGUIEventLoop->wxMSWEventLoopBase->wxEventLoopManual->wxEventLoopBaseである。

wxAppConsoleBase::MainLoopはwxEventLoopBase::Run(src\common\evtloopcmn.cpp:57)を介しwxEventLoopManual::DoRun(src\common\evtloopcmn.cpp:240)をコールする。DoRunはいくつかの処理と共にwxEventLoopManual::ProcessEventsをコールする。wxEventLoopManual::ProcessEvents(src\common\evtloopcmn.cpp:208)はwxGUIEventLoop::Dispatchでイベント処理する。

int wxEventLoopBase::Run()
{
...
return DoRun();
}
...
int wxEventLoopManual::DoRun()
{
for ( ;; )
{
try
{
for ( ;; ) // メッセージループ
{
... // アイドル処理
if ( !ProcessEvents() )
{
break;
}
}
... // 終了時の残余イベント処理
break;
}
catch ( ... )
{
... // 例外処理
}
break;
}
return m_exitcode;
}
...
bool wxEventLoopManual::ProcessEvents()
{
... // ペンディングイベント処理
const bool res = Dispatch();
... // 例外処理
return res;
}

wxGUIEventLoop::Dispatch(src\msw\evtloop.cpp:171)はwxMSWEventLoopBase::GetNextMessageでメッセージ(msg)を取得しwxGUIEventLoop::ProcessMessageでメッセージを処理する。

bool wxGUIEventLoop::Dispatch()
{
MSG msg;
if ( !GetNextMessage(&msg) )
return false;
... // スレッド処理
ProcessMessage(&msg);
return true;
}

wxMSWEventLoopBase::GetNextMessage(src\msw\evtloopconsole.cpp:95)はwxMSWEventLoopBase::GetNextMessageTimeout(src\msw\evtloopconsole.cpp:100)をコールする。GetNextMessageTimeoutはウィンドウズAPIのPeekMessageでメッセージの有無を確認し、有ればmsg->message!=WM_QUITを返し、無ければウィンドウズAPIのMsgWaitForMultipleObjectsでスリープする。スリープはメッセージが同じスレッドのウィンドウにポストされるか、他スレッドからウェイクアップ要求(イベントm_heventWakeのセット)される事で解除する。メッセージポストの場合はwhileループを続行し、ウェイクアップの場合はメッセージにWM_NULLを設定してtrueを返す。

bool wxMSWEventLoopBase::GetNextMessage(WXMSG* msg)
{
return GetNextMessageTimeout(msg, INFINITE) == TRUE;
}
int wxMSWEventLoopBase::GetNextMessageTimeout(WXMSG *msg, unsigned long timeout)
{
while ( !::PeekMessage(msg, 0, 0, 0, PM_REMOVE) )
{
DWORD rc = ::MsgWaitForMultipleObjects
(
1, &m_heventWake,
FALSE,
timeout,
QS_ALLINPUT | QS_ALLPOSTMESSAGE
);
switch ( rc )
{
...
case WAIT_OBJECT_0: // 他スレッドからのウェイクアップ
wxZeroMemory(*msg);
return TRUE;
case WAIT_OBJECT_0 + 1: // メッセージポスト
break;
}
}
return msg->message != WM_QUIT;
}

wxGUIEventLoop::ProcessMessage(src\msw\evtloop.cpp:160)はメッセージをwxGUIEventLoop::PreProcessMessageで前処理(プリプロセス)してからウィンドウズAPI標準のメッセージ処理関数(TranslateMessageとDispatchMessage)に渡してメッセージ処理を完了する。

void wxGUIEventLoop::ProcessMessage(WXMSG *msg)
{
if ( !PreProcessMessage(msg) )
{
::TranslateMessage(msg);
::DispatchMessage(msg);
}
}

wxGUIEventLoop::PreProcessMessage(src\msw\evtloop.cpp:67)はウィンドウズAPI標準のメッセージ前処理関数(モードレスダイアログ処理のIsDialogMessageとアクセラレータキー処理のTranslateAccelarator)を含む前処理を行う。

ただしどちらもwxWidgets独自実装を用いて直接コールしない。例外はモードレスダイアログがwxWidgets管理(wxWindow派生クラスインスタンスで保持)でない(コメントによればFind/Replaceダイアログのみ)場合で、その場合だけIsDialogMessageをコールする。

bool wxGUIEventLoop::PreProcessMessage(WXMSG *msg)
{
HWND hwnd = msg->hwnd;
wxWindow *wndThis = wxGetWindowFromHWND((WXHWND)hwnd);
wxWindow *wnd;
if ( !wndThis ) // ウィンドウがwxWidgets管理でない
{
while ( hwnd && (::GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD ))
{
hwnd = ::GetParent(hwnd); // wxWidgets管理の親ウィンドウを探査
wndThis = wxGetWindowFromHWND((WXHWND)hwnd);
if (wndThis != NULL)
break;
}
if ( !wndThis ) // トップレベルウィンドウがwxWidgets管理でない
{
return hwnd && ::IsDialogMessage(hwnd, msg) != 0;
}
}
... // クラッシュレポートなどクリティカルウィンドウがある場合の処理
... // ツールチップ処理
if ( !wndThis->MSWShouldPreProcessMessage((WXMSG *)msg) )
{
return false;
}
for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )
{
if ( wnd->MSWTranslateMessage((WXMSG *)msg))
return true;
if ( wnd->IsTopNavigationDomain(wxWindow::Navigation_Accel) )
break;
}
for ( wnd = wndThis; wnd; wnd = wnd->GetParent() )
{
if ( wnd->MSWProcessMessage((WXMSG *)msg) )
return true;
if ( wnd->IsTopNavigationDomain(wxWindow::Navigation_Accel) )
break;
}
return false;
}

wxWindowはウィンドウズ実装においてwxWindowMSW(include\wx\window.h:1932)に#defineされる。PreProcessMessageはいくつかのwxWindowMSW仮想メンバ関数をコールする。

wxWindowMSW仮想メンバ関数

wxWindowMSW::MSWShouldPreProcessMessage(src\msw\window.cpp:2447)はwxWindowMSW::MSWTranslateMessageとwxWindowMSW::MSWProcessMessageの実行可否を確認するがオーバーライドしない限りtrueを返す。wxWindowMSW::MSWTranslateMessage(src\msw\window.cpp:2437)はアクセラレータキーを処理し、wxWindowMSW::MSWProcessMessage(src\msw\window.cpp:2226)はキーボードフォーカス移動を行う。前者はウィンドウズAPIのTranslateAccelerator、後者はIsDialogMessageに相当するが、名称はTranslateMessageとProcessMessageに相当するかの誤解を与える。

bool wxWindowMSW::MSWShouldPreProcessMessage(WXMSG* WXUNUSED(msg))
{
return true;
}
...
bool wxWindowMSW::MSWTranslateMessage(WXMSG* pMsg)
{
return m_acceleratorTable.Translate(this, pMsg);
}
...
bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg)
{
if ( m_hWnd &&
HasFlag(wxTAB_TRAVERSAL) &&
(wxGetWindowExStyle(this) & WS_EX_CONTROLPARENT) ) // キーボードフォーカス移動する場合
{
MSG *msg = (MSG *)pMsg;
if ( msg->message == WM_KEYDOWN )
{
...
bool bProcess = true;
switch ( msg->wParam )
{
case VK_TAB: // タブキーの処理
...
break;
... // 矢印キー、リターンキーなどの処理
default:
bProcess = false;
}
if ( bProcess )
{
wxNavigationKeyEvent event;
... // イベント作成
if ( HandleWindowEvent(event) ) // イベント送出
{
...
return true;
}
}
}
if ( MSWSafeIsDialogMessage(msg) ) // バグパッチされたIsDialogMessageをコール
{
return true;
}
}
return false;
}