EternalWindows
メッセージ管理 / 送信メッセージキュー

プロセス間通信としてSendMessageを使用する場合、 SendMessageの呼び出し側はある1つの問題を抱えることになります。 それは、メッセージを受信したプロセスがそのメッセージの処理を終えない限り、 送信側プロセスのSendMessageも制御を返さないという点です。 たとえば、SendMessageを次のように実行したとします。

SendMessage(hwndTarget, WM_APP, 0, 0);

hwndTargetは別プロセス(または別スレッド)のウインドウハンドルとします。 そして、受信側プロセスはWM_APPを次のように処理したとします。

case WM_APP:
	// 5秒ほど掛かる処理を行う
	return 0;

この場合、SendMessageが制御を返すのは受信先がWM_APPを処理してからになりますから、 少なくとも送信元プロセスは5秒以上待機しなくてはならないことになります。 これでは、5秒間ほど送信元プロセスのウインドウを操作できなくなりますから、 こうした問題を解決する方法を考える必要があります。

まずは、タイムアウト値を指定することのできるSendMessageTimeoutを紹介します。

LRESULT SendMessageTimeout(
  HWND hWnd,
  UINT Msg,
  WPARAM wParam,
  LPARAM lParam,
  UINT fuFlags,
  UINT uTimeout,
  PDWORD_PTR lpdwResult
);

hWndからlParamまでは、SendMessageの引数と同じ意味を持ちます。 fuFlagsは、定義されている定数を指定します。 uTimeoutは、タイムアウト期間をミリ秒で設定します。 たとえば、この引数に1000を指定した場合、 1秒経ったらメッセージが処理されたかどうかに関係なく制御が返ります。 もし、タイムアウトによって制御が返ったならば、戻り値は0となります。 hWndが呼び出し側スレッドに関連付けられている場合はuTimeoutが無視され、 この関数はSendMessageと同じように機能します。 つまり、直ちにウインドウプロシージャが呼ばれます。 lpdwResultは、メッセージの戻り値が格納されます。 fuFlagsに指定できる定数を次に示します。

定数 意味
SMTO_ABORTIFHUNG 相手側のプロセスがハングアップ状態である場合、タイムアウト値を無視して制御を返す。
SMTO_BLOCK 関数が制御を返すまで呼び出し側スレッドは要求された操作を実行できない。
SMTO_NORMAL 呼び出し側スレッドは要求された操作を実行できる。

SMTO_BLOCKを指定するのはなるべく避けてください。 スレッドがSendMessageやSendMessageTimeoutを呼び出した場合、 ある程度の期間は処理を続行できない状態になるわけですが、 自分の送信メッセージキューと応答メッセージキューのチェックは行っているのです。 つまり、スレッドがSendMessageを呼び出して相手の応答を待っている間に、 別スレッドによるSendMessageの呼び出しが発生しても、それを処理することができるのです。 しかし、SMTO_BLOCKを指定するとそれすらも行わなくなります。 応答メッセージキューについて次節で説明します。

SendMessageTimeoutを使用すれば、待機する時間を一定にすることができますが、 待機そのものを行いたくない場合はSendNotifyMessageを呼び出します。

BOOL SendNotifyMessage(
  HWND hWnd,
  UINT Msg,
  WPARAM wParam,
  LPARAM lParam
);

hWndからlParamまでは、SendMessageの引数と同じ意味を持ちます。 この関数は、hWndが呼び出し側スレッドに関連付けられていない場合は、 メッセージを送信して直ちに制御を返します。 一方、hWndがスレッドに関連付けられている場合は、SendMessageと同じように機能することになり、 ウインドウがメッセージを処理するまで制御を返しません。

SendNotifyMessageに別プロセスのウインドウハンドルを指定した場合は、 メッセージを送信して直ちに制御を返すことになりますが、 この動作はPostMessageと非常によく似ています。 しかし、PostMessageがポストメッセージキューにメッセージを格納するのに対して、 SendNotifyMessageは送信メッセージキューにメッセージを格納するため、 優先順位の関係で後者のメッセージが早く処理されます。 実は、SendMessageもSendMessageTimeoutも相手のウインドウのウインドウプロシージャを直接呼び出しているのではなく、 単に送信メッセージキューにメッセージを格納しているだけです。 次に、各関数の特徴を示します。

SendMessage(hwndTarget, WM_APP, 0, 0); // 5秒後に制御を返す

SendMessageTimeout(hwndTarget, WM_APP, 0, 0, SMTO_NORMAL, 2000, NULL); // 2秒後に制御を返す

SendNotifyMessage(hwndTarget, WM_APP, 0, 0); // 直ちに制御を返す

送信メッセージキューが、ポストメッセージキューより優先順位が高いのには理由があります。 それは、送信メッセージキューの優先順位が低いと、 SendMessageで送信したメッセージがなかなか取得してもらえず、 SendMessageが制御を返す時間がより長くなってしまうからです。 メッセージキューに種類というものがなければ、 優先して処理すべきメッセージというものを区別できないことになっていたでしょう。

SendMessageについて初めて学習した頃は、ウインドウプロシージャに直接メッセージを送る関数として解釈したと思われますが、 この解釈は決して間違っているわけではありません。 指定したウインドウハンドルが呼び出し側スレッドに関連付けられている場合は、 その解釈通りSendMessageがウインドウプロシージャを呼び出すことになります。 また、SendMessageTimeoutとSendNotifyMessageは、SendMessageと同じように機能します。 送信メッセージキューというものが関わってくるのは、 あくまで指定したウインドウハンドルが呼び出し側スレッドに関連付けられていない場合のみです。

SendMessageCallbackについて

SendNotifyMessageは、メッセージを送信して直ちに制御を返すことのできる関数ですが、 この関数を使う限りでは、受信側がメッセージの処理を終えたタイミングを特定することはできません。 SendMessageを呼び出せば、制御が返った時点でメッセージが処理されたことを特定できますが、 この関数では後続の処理がブロックされてしまうことになります。 次に示すSendMessageCallbackは、SendNotifyMessageとSendMessageの良い点を取り入れた関数です。

SendMessageCallback(hwndTarget, WM_CLOSE, 0, 0, SendAsyncProc, NULL);

SendMessageCallbackは、SendNotifyMessageと同じようにメッセージを送信して直ちに制御を返します。 さらに、この関数は受信側がメッセージの処理を終えた場合に、 第5引数のコールバック関数を呼び出します。 つまり、処理を終えたタイミングを特定することができます。 コールバック関数のプロトタイプは、次のようになります。

VOID CALLBACK SendAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR uData, LRESULT lResult)
{

}

uDataは、SendMessageCallbackの第6引数が格納されます。 lResultは、メッセージの戻り値が格納されます。



戻る