RegisterWindowMessageでプロセス間の独自通知を定義する
Windows APIでアプリケーション固有の通知を実装する場合、ウィンドウメッセージは扱いやすく、メッセージループとの統合もしやすい仕組みです。特に、複数のプロセスが同じメッセージ名を共有する必要があるときは、RegisterWindowMessageが適しています。
この関数は文字列を登録し、システム全体で一意になるメッセージIDを返します。各プロセスが同じ文字列を使って登録すれば、同じIDを取得できるため、固定値を決めて配布する必要がありません。
ただし、登録しただけで通知が届くわけではありません。受信側ウィンドウのハンドルを把握し、SendMessageやPostMessageなどで送信する処理も必要です。メッセージの到達範囲や引数の渡し方まで含めて設計すると、安全で保守しやすい実装になります。
| 方式 | IDの決め方 | 主な用途 | 注意点 |
|---|---|---|---|
RegisterWindowMessage |
名前からシステムが割り当てる | プロセス間の共通通知 | 同じ文字列を使う必要がある |
WM_USER |
アプリケーション側で定義 | 同一アプリ内の独自メッセージ | 他プロセスとの共有には不向き |
WM_APP |
アプリケーション側で定義 | ウィンドウクラス単位の通知 | IDの衝突管理が必要 |
WM_COPYDATA |
固定メッセージ | データを別プロセスへ渡す | 受信中だけデータを扱う設計が必要 |
RegisterWindowMessageの役割
RegisterWindowMessageの宣言は、Unicode版では概ね次の形です。
UINT RegisterWindowMessageW(
LPCWSTR lpString
);
登録名を表す文字列を渡すと、戻り値としてメッセージIDが返されます。登録に失敗した場合は0です。通常、プログラムの初期化時に一度だけ呼び出し、取得した値をグローバル変数やクラスのメンバーとして保持します。
constexpr wchar_t kMessageName[] =
L"MyCompany.MyProduct.Reload";
UINT g_messageReload =
RegisterWindowMessageW(kMessageName);
if (g_messageReload == 0) {
// GetLastErrorでエラー内容を確認する
}
同じコンピューター上で別々のプロセスが同一の文字列を登録すると、同じメッセージIDが返ります。文字列は単なる説明文ではなく、プロセス間で共有する識別子です。製品名、会社名、機能名を組み合わせ、ほかのソフトウェアと衝突しにくい名前にします。
WM_USERやWM_APPとの使い分け
WM_USERやWM_APPは、ソースコード内で数値を定義して利用する独自メッセージです。自分のプロセス内だけで完結するウィンドウクラスやダイアログなら、これらのほうが簡単で、登録処理も必要ありません。
一方、異なる実行ファイルが同じ通知を認識する場合は、各プログラムが同じ登録文字列を使用できるRegisterWindowMessageが便利です。たとえば、常駐プログラムへ設定再読込を依頼するランチャーや、複数のGUIプロセス間で状態変更を伝える仕組みに利用できます。
メッセージIDをWM_USER + 1のように固定して別プロセスへ公開する方法もありますが、第三者のウィンドウクラスや将来の機能追加との衝突を管理しなければなりません。共有範囲が広いほど、登録名による識別のほうが意図を明確にできます。
登録と受信処理の実装
受信側は、ウィンドウプロシージャ内で登録済みのIDと受け取ったuMsgを比較します。登録はウィンドウ作成前でも構いませんが、送信側と受信側の双方で同じ名前を使い、初期化に失敗した場合は通知処理を無効にします。
LRESULT CALLBACK WindowProc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
if (uMsg == g_messageReload) {
ReloadConfiguration();
return 0;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
送信側が対象ウィンドウのハンドルを持っているなら、PostMessageWで非同期に通知できます。処理の完了を待つ必要がある場合はSendMessageWを使用しますが、受信側が長時間処理を行うと送信側も待機するため、デッドロックや応答停止に注意が必要です。
PostMessageW(
hwndTarget,
g_messageReload,
0,
0
);
送信先とプロセス境界
対象ウィンドウが明確なら、そのハンドルへ直接送信するのが基本です。複数の受信候補へ知らせる目的でHWND_BROADCASTを使う方法もありますが、デスクトップ上の多数のウィンドウへ通知されるため、受信側の判定やセキュリティ上の影響を考慮しなければなりません。
ウィンドウハンドルを発見する方法として、タイトルやクラス名の検索、名前付きミューテックスとの組み合わせ、プロセス起動時の登録情報などがあります。単純な通知だけならウィンドウメッセージで十分ですが、双方向の通信や大きなデータ転送が必要なら、名前付きパイプの基礎のようなIPC機構を検討します。
RegisterWindowMessageは通知の入口を作る機能であり、通信路そのものを提供するものではありません。受信側が終了している、対象ウィンドウがまだ作成されていない、別セッションに存在する、といった場合には、送信しても期待した動作にならないことがあります。
wParamとlParamで渡せる情報
wParamとlParamは、通知の種類、フラグ、数値IDなどの小さな値を渡す用途に向いています。たとえば、設定変更の範囲を整数で示したり、処理の優先度をビットフラグで表したりできます。値の意味と型は送受信側で同じヘッダーファイルに定義すると管理しやすくなります。
別プロセスへポインターを渡す設計は避けてください。ポインターはプロセスごとにアドレス空間が異なるため、受信側から有効なデータを参照できません。SendMessageであっても、任意の構造体を安全に共有できるわけではありません。データを渡す場合は、整数値、共有メモリ、WM_COPYDATA、名前付きパイプなど、用途に合った方法を選びます。
また、PostMessageでは送信後すぐに呼び出し元へ戻るため、lParamが指すメモリを呼び出し側で解放すると、受信時には無効な領域になり得ます。非同期通知では、引数に所有権や有効期間が発生しない形式を優先することが重要です。
セキュリティと運用上の注意
登録メッセージはIDが衝突しにくい一方、メッセージを送信できるプロセスを自動的に限定する機能ではありません。悪意のあるプログラムが同じ名前を登録して通知を送ったり、対象ウィンドウへ別のメッセージを送ったりする可能性があります。受信した値をそのままファイル操作や設定変更に使わず、入力値を検証してください。
権限レベルが異なるプロセス間では、ウィンドウメッセージの送受信が制限される場合があります。管理者権限で動くプロセスと通常権限のプロセスを連携させる場合は、メッセージだけを信頼境界にしないことが必要です。認証やアクセス制御が重要な処理では、専用のIPC機構を使い、送信元を確認できる設計にします。
APIの宣言、メッセージ関連の定数、Unicode利用時の文字列型などを確認するときは、Windows API資料も参照できます。古いサンプルではANSI版のRegisterWindowMessageAが使われていることがありますが、新規コードではRegisterWindowMessageWとUnicode文字列を統一して扱うと、名前の解釈差を抑えられます。
実装前に確認する項目
まず、通知が同一プロセス内だけで完結するのか、複数プロセスで共有されるのかを決めます。同一プロセス内ならWM_APPなどを検討し、プロセス間で同じ通知名を認識させるならRegisterWindowMessageを使います。データ転送が中心なら、登録メッセージに処理を詰め込まず、別の通信方式を組み合わせます。
次に、登録文字列、送信先ウィンドウの発見方法、同期か非同期か、引数の型と寿命、失敗時の扱いを仕様化します。特に、受信側が存在しない場合の戻り値、二重起動時の挙動、権限の異なるプロセスからの送信を確認すると、運用時の不具合を減らせます。
最初の実装では、共有ヘッダーに一意な登録文字列を定義し、送受信双方でRegisterWindowMessageWの戻り値を検査するところから始めます。次に、整数フラグだけをPostMessageWで渡す最小構成を作り、受信ログでメッセージIDと到達先を確認します。