突然ですが、WTLの理解をもう少し深くしたいので、WTLのクラス、マクロについて調べ、まとめていくことにしました。
先ず最初のクラスは、CMessageLoopです。
CMessageLoopの機能
a.WTLにおけるメッセージループの標準実装の提供
b.メッセージフィルタの制御
c.アイドル時処理の制御
CMessageLoopのメンバ、メソッド
CMessageLoopは、次のメンバ変数、メンバメソッドを持っています。
メンバ変数
ATL::CSimpleArray<CMessageFilter*> m_aMsgFilter
CMessageFilterへのポインタを保持するコンテナです。
ATL::CSimpleArray<CIdleHandler*> m_aIdleHandler
CIdleHandlerクラスへのポインタを保持するコンテナです。
MSG m_msg;
MSG構造体です。
メンバメソッド
Run
メッセージループ本体
AddMessageFilter
メッセージフィルタを登録します。
RemoveMessageFilter
メッセージフィルタを削除します。
AddIdleHandler
アイドルハンドラを追加します。
RemoveIdleHandler
アイドルハンドラを削除します。
AddUpdateUI
UpdateUIハンドラを追加します。
IsIdleMessage
現在アイドル状態かどうかを検出します。
PreTranslateMessage
メッセージをTransleteする前の処理を行います。
OnIdle
アイドル時の処理を行います。
次に機能詳細を見ていきます。
a.WTLにおけるメッセージループの標準実装の提供
Windowsプログラミングではお決まりの処理、メッセージループの実装を提供します。
MFCでは隠蔽されて見えない部分ですね。
Run()メソッド
メッセージループの本体はRun()メソッドです。処理内容は、
1.メッセージを受け取り(GetMessage())、PreTranslateMessage(メッセージフィルタ)メソッドを呼び出す。
2.通常のメッセージTranslate,Dispatchをする
3.ユーザからのアクション(入力)がないとき、OnIdle(アイドル時処理)メソッドを呼び出す。
というものです。
1.と3.についてはb,cで詳しく見ていくとして、2.の部分を中心にのRunメソッドのソースを見てみます。
000465| // message loop 000466| int Run() 000467| { 000468| BOOL bDoIdle = TRUE; 000469| int nIdleCount = 0; 000470| BOOL bRet; 000471| 000472| for(;;) 000473| { 000474| while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE)) 000475| { 000476| if(!OnIdle(nIdleCount++)) 000477| bDoIdle = FALSE; 000478| } 000479| 000480| bRet = ::GetMessage(&m_msg, NULL, 0, 0); 000481| 000482| if(bRet == -1) 000483| { 000484| ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n")); 000485| continue; // error, don't process 000486| } 000487| else if(!bRet) 000488| { 000489| ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n")); 000490| break; // WM_QUIT, exit message loop 000491| } 000492| 000493| if(!PreTranslateMessage(&m_msg)) 000494| { 000495| ::TranslateMessage(&m_msg); 000496| ::DispatchMessage(&m_msg); 000497| } 000498| 000499| if(IsIdleMessage(&m_msg)) 000500| { 000501| bDoIdle = TRUE; 000502| nIdleCount = 0; 000503| } 000504| } 000505| 000506| return (int)m_msg.wParam; 000507| }
先ず、474-477行目で、アイドルかつ
target="_top">PeekMessage()を呼び出し、メッセージがない場合DoIdle()メソッドを呼び出しています。
DoIdle()メソッドの戻り値はデフォルト実装では必ずFALSEを返すので、bDoIdle変数にFALSEがセットされ、ループは1回で抜けるということですね。
次に、GetMessage()でメッセージキューからメッセージを取り出します。
ここで気になるのが、なぜPeekMessage()でPM_REMOVEで取り出さずに、あえてPM_NOREMOVEとしてメッセージキューにメッセージを残し、GetMessage()しているかです。
これは次のメッセージが来るまでビジーループとなってしまうのを防ぐためです。GetMessage()だと何もしていないときはCPUに制御を戻すのでアプリが「軽く」なります。
なぜなら、GetMessage()は、メッセージキューに何も無いときは、OSが呼び出し元のスレッドを休止するからです。休止している間はOSが他のプロセス、スレッドに制御を渡します。メッセージキューにメッセージが再び来ると、呼び出し元のスレッドがアクティブになり、制御が戻ってきます。PeekMessage()だけだとOSに余計なCPU負荷をかけアプリが「重く」なってしまいます。
なぜなら、PeekMessage()は、とメッセージがあるなしに関係なくすぐにカレントスレッドに戻って来るので、次のメッセージが来るまでビジーループとなってしまうからです。それなら、GetMessage()だけで作ればいいのではとも思いますが、そうするとアイドル時処理がきちんとできなくなります。
まとめると、
・メッセージがなく、アイドル時の場合はアイドル時の処理をして次のメッセージが来るまでスリープ
・メッセージがある場合はアイドル時の処理をせずメッセージを処理する
ということになりますね。
481-491行目でGetMessage()の戻り値をチェックしています。-1だとここで次のループに進みます。0の場合は終了なので抜けます。
-1の場合はここに書いてあるようにループを抜ける処理をしなくてはいけないようですが、そのままcontinueしています。いいのかな..
493-497行目ですが、ここでPreTranslateMessage()を呼び出し、戻り値がFALSEの時、すなわちメッセージをフィルタしないときは、メッセージをディスパッチします。
499-503行目では、アイドル状態かどうかを判定しています。
判定自体はIsIdleMessage()で行われています。
000509| static BOOL IsIdleMessage(MSG* pMsg) 000510| { 000511| // These messages should NOT cause idle processing 000512| switch(pMsg->message) 000513| { 000514| case WM_MOUSEMOVE: 000515| #ifndef _WIN32_WCE 000516| case WM_NCMOUSEMOVE: 000517| #endif //!_WIN32_WCE 000518| case WM_PAINT: 000519| case 0x0118: // WM_SYSTIMER (caret blink) 000520| return FALSE; 000521| } 000522| 000523| return TRUE; 000524| }
IsIdleMessage()を見ると処理済メッセージの種類によってIdleかIdleでないか(Idle処理をするか、しないか)を判断しているようです。
・短い間隔で発生するメッセージ(WM_MOUSEMOVE、WM_NCMOUSEMOVE、WM_PAINT、WM_SYSTIMER)=忙しい=bDoIdle=FALSE
・短い間隔で発生しないメッセージ=忙しくない=bDoIdle=TRUE
(続く..)