Skip to content

MS_WindowMessage

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

ウィンドウ メッセージ

Windowsメッセージキュー

  • Windows メッセージ(WM_XXXX)を格納するキュー

  • キーボードなどのハードウェア入力やプログラムが
    Windows メッセージキューに Windows メッセージをキューイングする。

  • プロセス間通信や COM の STA などでも利用される。

Windowsメッセージ

Windows メッセージキューに格納された Windows メッセージは、

  • メッセージ・ループによって取り出され、
  • ウィンドウプロシージャ(WndProc)により処理される。

作成されるタイミング

ウィンドウを作成したスレッドには、Windows メッセージキューが与えられる。

通常、1 UI スレッドで複数ウィンドウを処理する。
この場合も1 Windows メッセージキューになる。

  • Windows Forms アプリケーションでも、この形になる。
  • Windows Forms では、通常、1プロセス、1 UI スレッド、
    1 Windows メッセージキュー。
  • 必要に応じてスレッドを生成して Form を生成すると
    別の Windows メッセージキューが生成される。
  • バックグラウンド・スレッドから上げたモーダル・ダイアログが
    モーダルにならない理由でもある。

補足(メッセージ キューは「遅延生成」される): 「ウィンドウを作成した
スレッドには与えられる」という記述をもう少し正確にしておく。

【生成のタイミング】★
   ・スレッド作成時には【まだ無い】
   ・以下のいずれかで初めて作られる
       - ウィンドウを作る(CreateWindow)
       - メッセージ系 API を呼ぶ
         (PeekMessage / GetMessage / PostThreadMessage)
   → PostThreadMessage が
     「まだキューが無い」スレッドに対して
     【失敗する】ことがあるのはこのため ★
     → 受け側で先に PeekMessage を空打ちしておく

【1 スレッド = 1 キュー】
   ・複数ウィンドウがあってもキューは 1 つ
   ・だから【1 つのウィンドウが固まると
     同じスレッドの全ウィンドウが固まる】★
     → 「応答なし」の正体
     → 重い処理は必ず別スレッド/非同期に逃がす
【「モーダルにならない理由」の補足】★
   モーダルの正体は
   「【同じスレッドの】他のウィンドウを
     無効化(EnableWindow(FALSE))して
     独自のメッセージ ループを回すこと」

   → 別スレッドから出したダイアログは
     【別のキュー】に属するため、
     元スレッドのウィンドウを止められない
   → .NET でも
     「バックグラウンド スレッドから ShowDialog」は
     期待通りに動かない ★
     → UI スレッドへマーシャリングして呼ぶ
       (→ [コントロールのInvokeメソッド](MS_ControlInvoke))

メッセージ・ループ

メッセージ・ループで使用するWin32API

移行メモ(誤字): 移行元の見出しは「Win32PI」だったため、
「Win32API」に修正した(本節以降 2 箇所)。

メッセージの取り出し

  • GetMessage
  • PeekMessage
  • GetQueueStatus(メッセージ状態確認)

メッセージの割り当て

  • DispatchMessage(WndProc へ割り当て)

待機

  • WaitForMultipleObjects
  • WaitForMultipleObjectsEx

その他

  • TranslateMessage(ショートカットの変換)

移行メモ(誤字): 移行元の「TransrateMessage」を
正しい API 名「TranslateMessage」に修正した。

補足(典型的なメッセージ ループ): 実際の形を示しておくと、
上記の API の役割が分かりやすい。

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {   /* 取り出し */
    TranslateMessage(&msg);                  /* キー → 文字 */
    DispatchMessage(&msg);                   /* WndProc へ */
}
【GetMessage と PeekMessage の違い】★
   GetMessage
     → メッセージが来るまで【ブロックする】
     → 待っている間 CPU を使わない ★
     → WM_QUIT を受け取ると 0 を返してループを抜ける
   PeekMessage
     → 【あれば取る、無ければすぐ返る】
     → ゲームのようにフレームを回し続ける用途
     → 【何もない時も CPU を食う】ので
       通常のアプリでは使わない

【TranslateMessage が要る理由】
   ・WM_KEYDOWN(仮想キー)だけでは
     「A キーが押された」しか分からない
   ・TranslateMessage が
     Shift 状態・IME・キーボード配列を考慮して
     【WM_CHAR(文字)を生成】する ★
   → これを飛ばすと文字入力ができなくなる

【WaitForMultipleObjects との組み合わせ】
   ・「メッセージも待ちたいし、
     イベント オブジェクトも待ちたい」場合
     → 【MsgWaitForMultipleObjects】を使う ★
     → 単なる WaitForMultipleObjects で待つと
       【メッセージが処理されずデッドロックする】
     → STA の COM を使うスレッドでは特に重要

.NETでメッセージループ(WndProc)を実装する方法

移行メモ(誤記): 移行元の見出しは「メッセージループ(WinProc)」
だったが、正しくは WndProc(Window Procedure)であるため修正した。

補足(.NET での実装手段): リンク先の要点を本文に取り込んでおく。

// ① Form / Control の WndProc をオーバーライドする(最も一般的)★
protected override void WndProc(ref Message m) {
    const int WM_DEVICECHANGE = 0x0219;
    if (m.Msg == WM_DEVICECHANGE) { /* 独自処理 */ }
    base.WndProc(ref m);        // ← 【必ず呼ぶ】★
}
【他の手段】
 ② 【NativeWindow】を派生し、AssignHandle する
     → 他人のウィンドウのメッセージを横取りできる
     → 原文で言う「サブクラス化」を .NET でやる方法 ★
 ③ 【IMessageFilter】を Application.AddMessageFilter で登録
     → 【DispatchMessage の前】に全メッセージを見られる
     → ショートカット キーの横取り等に使う
 ④ WPF の場合
     → HwndSource.FromHwnd(handle).AddHook(...)
     → WPF は基本的にウィンドウを 1 つしか持たないため、
       トップレベル ウィンドウにフックを掛ける ★

ウィンドウプロシージャ(WndProc)

WndProcで使用するWin32API

WndProc登録

  • RegisterClassA(ANSI)
  • RegisterClassW(Unicode)
  • IsWindowUnicode(ANSI?Unicode?のチェック)
  • ,etc.

移行メモ(誤字): 移行元の「IsWindowsUnicode」を
正しい API 名「IsWindowUnicode」に修正した。

WndProcのサブクラス化

WndProc のサブクラス化とは、WndProc の置き換えと、
元の WndProc へのチェーンを実装すること。
オブジェクト指向のサブクラスではないが、
それ相当のことをやっている(オーバーライド+ base 呼び出し)。

  • SetWindowLongPtrA(ANSI)

  • SetWindowLongPtrW(Unicode)

  • 差し替え前の WndProc を直接呼び出す

    • CallWindowProcA(差し替え前の ANSI の WndProc を直接呼び出す)
    • CallWindowProcW(差し替え前の Unicode の WndProc を直接呼び出す)
  • ,etc.

補足(サブクラス化の注意点と、現在の推奨 API): この技法は
他人のコントロールの挙動を変えるための古典的な手段だが、危険も伴う。

【従来の SetWindowLongPtr 方式の問題】★
   ・複数の主体が同じウィンドウをサブクラス化すると
     【チェーンが壊れる】
     → 解除の順序が逆になると
       既に無効な WndProc を呼んでしまう
   ・DLL がアンロードされた後に呼ばれると
     【アクセス違反】で落ちる

【現在の推奨:SetWindowSubclass】★
   comctl32.dll v6 以降が提供する
     SetWindowSubclass(hWnd, proc, idSubclass, refData)
     RemoveWindowSubclass(...)
     DefSubclassProc(...)
   → 【OS がチェーンを管理する】
   → 任意の順序で解除しても壊れない
   → 参照データ(refData)を紐付けられる

【32/64bit の注意】
   ・SetWindowLong(Ptr なし)は
     64bit でポインタが切り捨てられる ★
   → 常に【SetWindowLongPtr】を使う
     (32bit ビルドではマクロで SetWindowLong になる)

メッセージの送信

  • PostMessage
    メッセージループにキューイングする。

  • SendMessage
    WndProc をサブルーチン的に呼び出す(イベントの実装に使用する)

ハードウェア入力

ハードウェア入力モデルとローカル入力状態

ハードウェア入力モデル

  • SHIQ(system hardware input queue:システムハードウェア入力キュー)
  • RIT(raw input thread:現在の入力スレッド)/(SHIQ を待機、取出、変換)
  • VIQ(virtual input queue:仮想入力キュー)/(THREADINFO 構造体内)

移行メモ(誤字): 移行元の「システムハードウェア入力キー」を
略語(SHIQ = system hardware input queue)に合わせ
「システムハードウェア入力キュー」に修正した。

RITが接続するVIQのスレッドを識別する

  • マウスポインタ:マウスカーソルの下のウィンドウのスレッド
  • キーストローク:フォアグラウンド・ウィンドウのスレッド
  • その他、Alt + Tab などの特殊キー・シーケンスを処理する。

THREADINFO構造体

  • ポストメッセージ(PostMessage)
  • 送信メッセージキュー(SendMessage、別スレッド)
  • 応答メッセージキュー(SendMessage、別スレッド)
  • 仮想入力キュー(ハードウェア入力)
  • ウェイクフラグ(PostQuitMessage)
  • nExitCode(PostQuitMessage)
  • スレッドローカル
  • 入力状態管理変数

補足(本節の出典と、その位置付け): SHIQ / RIT / VIQ / THREADINFO という
用語体系は、Windows の内部実装を解説した資料に由来するものである。

【出典】★
   ・Microsoft Systems Journal の記事
     「Win32 Q&A」(Jeffrey Richter 他)
   ・書籍『Advanced Windows』(Jeffrey Richter)
   → いずれも Windows NT 4.0 〜 2000 世代の解説

【注意】
   ・THREADINFO は【公開された構造体ではない】
     → ヘッダにも定義はなく、直接触ることはできない
     → 「概念モデル」として理解するもの ★
   ・実装の詳細は Windows のバージョンで変わりうる

【それでも知る価値がある理由】
   このモデルを知っていると、
     ・なぜ SetFocus が別スレッドに効かないのか
     ・なぜ SetForegroundWindow が失敗するのか
     ・なぜ AttachThreadInput が必要になるのか
   が【一貫して説明できる】★
   → 後述の各 API の説明はすべて
     このモデルの上に成り立っている

ローカル入力状態(THREADINFO構造体内)

キーボード入力、ウィンドウフォーカス情報

  • フォーカス・ウィンドウ
  • アクティブ・ウィンドウ
  • 押下されているキー
  • キャレット(入力カーソル)の状態

マウスカーソル管理情報

  • 表示/非表示の状態
  • キャプチャ

マウスによる切り替え

  • WM_KILLFOCUS

Win32API

SetFocus

  • WM_SETFOCUS
  • VIQ の切り替えはできない。
  • 通常、RIT に接続されているときだけ呼び出す。
  • スレッド1が RIT に接続している時にスレッド2のウィンドウに SetFocus を呼び出すと
    2つのフォーカス矩形が現れ問題(RIT に接続する VIQ は切り替わらないので)。

移行メモ(誤字): 移行元の「スレッド2のウィンドに」を
「スレッド2のウィンドウに」に修正した。

GetFocus

ローカル入力状態からフォーカスを持つウィンドウの HWND を返す。

SetActiveWindow

  • ウィンドウをアクティブにする。
  • RIT に接続する VIQ の切り替えはできない。
  • 通常、RIT に接続されているときだけ呼び出す。

GetActiveWindow

ローカル入力状態からアクティブなウィンドウの HWND を返す。

BringWindowToTop

HWND_TOP を指定し SetWindowPos を呼び出す(RIT に接続する VIQ を変更可能)

移行メモ(表記): 移行元の「HWND**_**TOP」(全角アンダースコア)を
半角の HWND_TOP に修正した。

SetWindowPos

ウィンドウをアクティブにしてサイズ、位置、Z オーダの変更。
(RIT に接続する VIQ を変更可能)

SetForegroundWindow

ウィンドウをフォアグラウンドに移動するが(RIT に接続する VIQ を変更可能)
プログラマによって乱用されたため、MS は RIT に接続されているか、
RIT に接続されている別スレッドが一定時間以上、入力を受け取っていない場合にのみ
成功するように機能を追加した。

失敗の際は、タスクバーをフラッシュさせる。

AllowSetForegroundWindow

指定したプロセスのスレッドが SetForegroundWindow を呼び出すことを許可する。

LockSetForegroundWindow

他の SetForegroundWindow 呼出をロックする
(Windows スタートメニューなどで使用されている)。

GetForegroundWindow

フォアグラウンドウィンドウの HWND を返す。

移行メモ(誤字): 移行元では SetForgroundWindow /
AllowSetForgroundWindow / LockSetForgroundWindow /
GetForgroundWindowForegrounde が欠落していたため、
正しい API 名に修正した(4 箇所)。

補足(SetForegroundWindow が失敗する条件は明文化されている): 原文の
「乱用されたため機能を追加した」という経緯は正しい。現在の条件を補う。

【SetForegroundWindow が成功する主な条件】★
   次のいずれかを満たす場合のみ成功する。
     ・呼び出しプロセスが【現在フォアグラウンド】である
     ・呼び出しプロセスが【直前のフォアグラウンド】だった
     ・【ユーザー入力を受け取ったばかり】である
     ・フォアグラウンド プロセスが存在しない
     ・フォアグラウンド プロセスが
       【AllowSetForegroundWindow で許可した】★
     ・デバッガ配下で実行されている
     ・フォアグラウンド ロックのタイムアウトが経過している
       (SPI_GETFOREGROUNDLOCKTIMEOUT)

【失敗時の挙動】
   ・API は FALSE を返す
   ・代わりに【タスクバー ボタンが点滅する】(原文の記述どおり)
     → FlashWindowEx で明示的に点滅させることもできる

【設計としての示唆】★
   「自分のウィンドウを勝手に最前面に出す」という要求は
   【OS が意図的に制限している】
     → ユーザーの作業を奪う操作だから
     → 通知が必要なら
       【トースト通知 / タスクトレイ / 点滅】を使う
     → 業務アプリで「必ず前面に出したい」という要件は
       UX として再考する価値がある

GetKeyState

最後のキー入力を返す。
入力フォーカスを持ったウィンドウを持っている必要はない。

GetAsyncKeyState

  • キーが押されているか?
  • 前回の呼び出し以降にキーが押されたか?

を返す。

入力フォーカスを持ったウィンドウを持っていること。

移行メモ(誤字・内容): 移行元の「持ってる必要はない」「持ていること」を
それぞれ「持っている必要はない」「持っていること」に修正した。

移行メモ(正誤): なお、2 つの API の説明は
実際には逆に近い
GetKeyState呼び出しスレッドのメッセージ処理時点の状態を返すため
メッセージを処理している文脈が前提であり、
GetAsyncKeyState は名前のとおり非同期に現在の物理状態を返すため
フォーカスを持たなくても取得できる
作者の記述はそのまま残したが、利用時は注意されたい。

SetCursor

マウス・カーソルのセット(砂時計など)

ShowCursor

マウス・カーソルの表示・非表示

ClipCursor

マウス・カーソルの範囲を指定する。
(非同期的なアクティブ化イベントで中止)

SetCapture

  • マウス・カーソルがウィンドウの範囲内にあるかどうかに関わり無く、
    すべてのマウス入力を受け取る。
  • 一度に 1 つのウィンドウだけがマウスをキャプチャできる。
    通常はマウスのボタン押下後に呼び出す。

ReleaseCapture

上記のマウスのキャプチャを停止する。

AttachThreadInput

複数のスレッドに複数の VIQ とローカル入力状態を共有させる。

ただし、THREADINFO 構造体の、

  • ポストメッセージ
  • 送信メッセージキュー
  • 応答メッセージキュー
  • ウェイクフラグ

については自分のものを使い続ける。

  • 用途としては
    • Windowを前面に出す(MS_BringWindowToFront.md
    • ジャーナルの記録・再生フックをインストールした場合。
    • この API はシステムの強度を損なう可能性がある。

補足(AttachThreadInput は「最後の手段」): 原文の
「システムの強度を損なう可能性がある」という警告は適切である。

【何が起きるか】★
   2 つのスレッドの入力状態が【1 つに融合される】
     → 片方のスレッドが固まると
       【もう片方も入力を受け付けなくなる】★
     → 相手が終了しても Detach し忘れると
       入力キューが壊れたままになる

【使うなら】
   ・必ず対で呼ぶ(TRUE で接続 → FALSE で解除)
   ・接続している時間を【最短にする】
   ・try-finally で確実に解除する

【「Window を前面に出す」の定番手順】
   ① AttachThreadInput(自スレッド, 対象スレッド, TRUE)
   ② SetForegroundWindow / BringWindowToTop
   ③ AttachThreadInput(..., FALSE)
   → 【動くが、行儀は良くない】★
   → OS の制限を迂回しているため、
     将来のバージョンで塞がれる可能性がある

【ジャーナル フック】
   ・JournalRecord / JournalPlayback フックは
     【Windows Vista 以降、UIPI により大きく制限】された
   ・現在の自動化は
     【UI Automation】(→ [UI Automation](MS_UIAutomation))
     または SendInput を使う ★

ユーザオブジェクト

ユーザオブジェクト(プロセスに占有)

  • アイコン
  • カーソル
  • ウィンドウクラス
  • メニュー
  • アクセラレータテーブル

補足(ユーザ オブジェクトにも上限がある): GDI オブジェクトと
同様、ユーザ オブジェクトにも枯渇がある

【上限】★
   ・プロセスあたり【10,000】(既定)
   ・セッションあたり【65,536】
   → タスク マネージャの [詳細] タブで
     「USER オブジェクト」列を表示すると確認できる ★

【枯渇するとどうなるか】
   ・CreateWindow が失敗する
   ・メニューやツールチップが出なくなる
   ・【例外ではなく「静かに壊れる」】ので発見が遅れる ★

【よくある原因】
   ・動的に生成したコントロールを Dispose していない
   ・ContextMenu / ToolTip を毎回 new している
   ・タイマーで画面を作り直している

 ※ [GDI](MS_GDI) オブジェクトと合わせて、
   長時間稼働する業務アプリでは
   【定期的に監視する価値がある】

参考


Tags: 移行, Windows, ウィンドウ・システム, プログラミング

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally