SendMessageとPostMessageの違いと使い方
Windowsアプリケーションでは、別のウィンドウやコントロールへ処理を依頼するために、ウィンドウメッセージを利用します。代表的なAPIがSendMessageとPostMessageです。どちらもHWNDとメッセージ番号を指定して呼び出しますが、処理の待ち方とデータの扱いには大きな違いがあります。
SendMessageは、送信先のウィンドウプロシージャがメッセージを処理し終えるまで、呼び出し元へ戻りません。処理結果を戻り値として受け取れるため、問い合わせや同期的な命令に向いています。
PostMessageは、メッセージを対象スレッドのキューへ登録した時点で戻ります。送信先の処理完了を待たないため、画面操作の通知や後で処理すればよいイベントに適しています。
Windows APIの宣言や定数を調べる場合は、Windows APIリファレンスのような資料も役立ちます。実装では関数の戻り値だけで判断せず、メッセージループ、スレッド境界、引数の有効期間まで確認することが重要です。
SendMessageの同期的な動作
SendMessageを呼び出すと、指定したウィンドウのウィンドウプロシージャへメッセージが届けられ、その処理が終わるまで呼び出し側のスレッドは待機します。通常はWndProcの戻り値がSendMessageの戻り値になるため、処理結果を直ちに利用できます。
同じスレッドにあるウィンドウへ送る場合は、システムがウィンドウプロシージャを直接呼び出す形に近い動作になります。別スレッドのウィンドウへ送る場合は、受信側スレッドがメッセージを処理するまで待つため、受信側が停止していると送信側も長時間止まる可能性があります。
LRESULT result = SendMessage(
hEdit,
WM_GETTEXTLENGTH,
0,
0
);
この例では、エディットコントロールが返した文字数をresultで受け取ります。応答が必要なカスタムメッセージや、処理完了を確実に待ちたい操作では、SendMessageの同期性が便利です。
PostMessageの非同期的な動作
PostMessageは、メッセージを受信側のキューへ追加するとすぐに制御を返します。WndProcが実行されるのは、対象スレッドがメッセージループからメッセージを取り出した後です。そのため、送信直後に受信側の処理結果を取得することはできません。
BOOL posted = PostMessage(
hWnd,
WM_APP + 1,
0,
0
);
戻り値は、メッセージをキューへ登録できたかどうかを示すものです。これはWndProcが正常終了したことを意味しません。対象ウィンドウが破棄された場合や、キューへ登録できない場合には失敗するため、必要に応じてGetLastErrorなども確認します。
PostMessageでは、呼び出し元のローカル変数へのポインターをlParamやwParamに格納する設計は避けます。関数から戻った後に受信処理が行われるため、ポインターが無効になる可能性があるからです。文字列や構造体を確実に渡す必要がある場合は、共有メモリ、コピー可能な値、専用の同期方式などを検討します。
パラメーターとメッセージの選び方
wParamとlParamは整数型の値を渡すための引数ですが、メッセージごとに意味が異なります。標準メッセージではMicrosoftの仕様に従い、カスタムメッセージでは送信側と受信側でデータ形式を厳密に一致させます。ポインターを渡せるかどうかも、同期送信か非同期送信かで判断が変わります。
ウィンドウ間でデータを渡す場合、WM_COPYDATAはSendMessageによる同期通信を前提とする代表例です。受信側が処理中にデータを参照できる仕組みですが、処理終了後まで送信側が待機するため、大きなデータや時間のかかる処理には適しません。
アプリケーション独自の通知にはWM_APP以上の番号を使う方法が一般的です。WM_USERの範囲はコントロールやクラス固有のメッセージと衝突する可能性があるため、対象ウィンドウの種類と予約範囲を確認します。異なるプロセス間で使う場合は、構造体のポインターやアドレスをそのまま渡さないことが基本です。
スレッド間通信で起きる問題
SendMessageを別スレッドへ送ると、受信側のメッセージ処理が終わるまで送信側が待機します。受信側が送信側からの応答を待っている場合、双方が停止するデッドロックが起こります。ロックを保持したままSendMessageを呼び出す設計も、同様の停止原因になります。
長時間応答しない相手への送信には、SendMessageTimeoutを使って待機時間を制限する方法があります。ただし、タイムアウトは処理を安全に中断したことを保証するものではありません。受信側で処理が継続している可能性を考慮し、共有データの排他や再送方針を設計します。
PostMessageは待機によるデッドロックを避けやすい一方、メッセージがすぐ処理される保証はありません。キューが詰まれば遅延しますし、終了直前のスレッドへ送った通知が処理されないこともあります。確実な完了通知が必要なら、イベント、ミューテックス、条件変数、I/O完了通知などの同期機構と組み合わせます。
実装前に確認するポイント
使い分けを決める前に、メッセージの目的とデータの寿命を整理します。単なる通知なのか、処理結果を受け取る要求なのかで選択はほぼ決まります。フックやウィンドウメッセージを組み合わせる実装では、フック処理の注意点も確認しておくと、スレッドやプロセス境界の見落としを減らせます。
次のような観点をコードレビュー時に確認すると、動作の不安定さを見つけやすくなります。
- 送信先のウィンドウハンドルが有効か
- 受信側スレッドにメッセージループがあるか
- 戻り値やエラー情報をどの範囲まで検証するか
- wParamとlParamの型、サイズ、所有権が一致しているか
デバッグでは、WndProcに入った時刻、メッセージ番号、スレッドID、処理時間を記録します。SendMessageなら送信側の停止時間、PostMessageならキュー投入後の遅延を測ることで、原因を切り分けやすくなります。
APIの違いを整理する
両方の関数はHWNDを対象にしますが、同期性、結果の取得方法、引数の安全性が異なります。特に、PostMessageの戻り値を「受信処理の成功」と解釈しないことが重要です。
| 比較項目 | SendMessage | PostMessage |
|---|---|---|
| 動作 | 受信処理の完了を待つ | キューへ登録して戻る |
| 戻り値 | WndProcの処理結果を受け取る | 登録成功または失敗を示す |
| 適した用途 | 応答が必要な要求、同期処理 | 通知、遅延実行、UIイベント |
| ポインター引数 | 処理中の有効性を管理しやすい | 関数復帰後に無効化しやすい |
| 主な注意点 | デッドロック、長時間ブロック | 未処理、遅延、結果取得不可 |
実装の基本方針は、結果をその場で必要とするならSendMessage、処理を依頼するだけならPostMessageです。ただし、SendMessageで重い処理を実行すると送信側まで固まり、PostMessageで重要なデータを渡すと寿命管理を誤りやすくなります。
迷った場合は、まず「受信側が処理を終えるまで待つ必要があるか」「渡す値は関数復帰後も有効か」「送信先が別スレッドまたは別プロセスか」を確認します。この三点を整理し、同期処理にはSendMessage、通知処理にはPostMessageを選ぶと、Windows APIを使ったメッセージ通信を安全に組み立てやすくなります。