-
Notifications
You must be signed in to change notification settings - Fork 0
MS_WindowMessage
- 戻る(ウィンドウ・システム)
- ウィンドウ メッセージ
- PostMessage
- SendMessage
-
Windows メッセージ(WM_XXXX)を格納するキュー
-
キーボードなどのハードウェア入力やプログラムが
Windows メッセージキューに Windows メッセージをキューイングする。 -
プロセス間通信や COM の STA などでも利用される。
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))
- メッセージ > メッセージ・ループ
http://wisdom.sakura.ne.jp/system/winapi/win32/win9.html
移行メモ(誤字): 移行元の見出しは「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 を使うスレッドでは特に重要
- WindowsフォームでWindowsメッセージを直接処理するには? - @IT
http://www.atmarkit.co.jp/fdotnet/dotnettips/467wndproc/wndproc.html
移行メモ(誤記): 移行元の見出しは「メッセージループ(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 つしか持たないため、 トップレベル ウィンドウにフックを掛ける ★
- RegisterClassA(ANSI)
- RegisterClassW(Unicode)
- IsWindowUnicode(ANSI?Unicode?のチェック)
- ,etc.
移行メモ(誤字): 移行元の「IsWindowsUnicode」を
正しい API 名「IsWindowUnicode」に修正した。
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)に合わせ
「システムハードウェア入力キュー」に修正した。
- マウスポインタ:マウスカーソルの下のウィンドウのスレッド
- キーストローク:フォアグラウンド・ウィンドウのスレッド
- その他、Alt + Tab などの特殊キー・シーケンスを処理する。
- ポストメッセージ(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 の説明はすべて このモデルの上に成り立っている
- フォーカス・ウィンドウ
- アクティブ・ウィンドウ
- 押下されているキー
- キャレット(入力カーソル)の状態
- 形
- 表示/非表示の状態
- キャプチャ
- WM_KILLFOCUS
- WM_SETFOCUS
- VIQ の切り替えはできない。
- 通常、RIT に接続されているときだけ呼び出す。
- スレッド1が RIT に接続している時にスレッド2のウィンドウに SetFocus を呼び出すと
2つのフォーカス矩形が現れ問題(RIT に接続する VIQ は切り替わらないので)。
移行メモ(誤字): 移行元の「スレッド2のウィンドに」を
「スレッド2のウィンドウに」に修正した。
ローカル入力状態からフォーカスを持つウィンドウの HWND を返す。
- ウィンドウをアクティブにする。
- RIT に接続する VIQ の切り替えはできない。
- 通常、RIT に接続されているときだけ呼び出す。
ローカル入力状態からアクティブなウィンドウの HWND を返す。
HWND_TOP を指定し SetWindowPos を呼び出す(RIT に接続する VIQ を変更可能)
移行メモ(表記): 移行元の「HWND**_**TOP」(全角アンダースコア)を
半角のHWND_TOPに修正した。
ウィンドウをアクティブにしてサイズ、位置、Z オーダの変更。
(RIT に接続する VIQ を変更可能)
ウィンドウをフォアグラウンドに移動するが(RIT に接続する VIQ を変更可能)
プログラマによって乱用されたため、MS は RIT に接続されているか、
RIT に接続されている別スレッドが一定時間以上、入力を受け取っていない場合にのみ
成功するように機能を追加した。
失敗の際は、タスクバーをフラッシュさせる。
指定したプロセスのスレッドが SetForegroundWindow を呼び出すことを許可する。
他の SetForegroundWindow 呼出をロックする
(Windows スタートメニューなどで使用されている)。
フォアグラウンドウィンドウの HWND を返す。
移行メモ(誤字): 移行元では
SetForgroundWindow/
AllowSetForgroundWindow/LockSetForgroundWindow/
GetForgroundWindowとForegroundのeが欠落していたため、
正しい API 名に修正した(4 箇所)。
補足(
SetForegroundWindowが失敗する条件は明文化されている): 原文の
「乱用されたため機能を追加した」という経緯は正しい。現在の条件を補う。【SetForegroundWindow が成功する主な条件】★ 次のいずれかを満たす場合のみ成功する。 ・呼び出しプロセスが【現在フォアグラウンド】である ・呼び出しプロセスが【直前のフォアグラウンド】だった ・【ユーザー入力を受け取ったばかり】である ・フォアグラウンド プロセスが存在しない ・フォアグラウンド プロセスが 【AllowSetForegroundWindow で許可した】★ ・デバッガ配下で実行されている ・フォアグラウンド ロックのタイムアウトが経過している (SPI_GETFOREGROUNDLOCKTIMEOUT) 【失敗時の挙動】 ・API は FALSE を返す ・代わりに【タスクバー ボタンが点滅する】(原文の記述どおり) → FlashWindowEx で明示的に点滅させることもできる 【設計としての示唆】★ 「自分のウィンドウを勝手に最前面に出す」という要求は 【OS が意図的に制限している】 → ユーザーの作業を奪う操作だから → 通知が必要なら 【トースト通知 / タスクトレイ / 点滅】を使う → 業務アプリで「必ず前面に出したい」という要件は UX として再考する価値がある
最後のキー入力を返す。
入力フォーカスを持ったウィンドウを持っている必要はない。
- キーが押されているか?
- 前回の呼び出し以降にキーが押されたか?
を返す。
入力フォーカスを持ったウィンドウを持っていること。
移行メモ(誤字・内容): 移行元の「持ってる必要はない」「持ていること」を
それぞれ「持っている必要はない」「持っていること」に修正した。移行メモ(正誤): なお、2 つの API の説明は
実際には逆に近い。
GetKeyStateは呼び出しスレッドのメッセージ処理時点の状態を返すため
メッセージを処理している文脈が前提であり、
GetAsyncKeyStateは名前のとおり非同期に現在の物理状態を返すため
フォーカスを持たなくても取得できる。
作者の記述はそのまま残したが、利用時は注意されたい。
マウス・カーソルのセット(砂時計など)
マウス・カーソルの表示・非表示
マウス・カーソルの範囲を指定する。
(非同期的なアクティブ化イベントで中止)
- マウス・カーソルがウィンドウの範囲内にあるかどうかに関わり無く、
すべてのマウス入力を受け取る。 - 一度に 1 つのウィンドウだけがマウスをキャプチャできる。
通常はマウスのボタン押下後に呼び出す。
上記のマウスのキャプチャを停止する。
複数のスレッドに複数の VIQ とローカル入力状態を共有させる。
ただし、THREADINFO 構造体の、
- ポストメッセージ
- 送信メッセージキュー
- 応答メッセージキュー
- ウェイクフラグ
については自分のものを使い続ける。
- 用途としては
- Windowを前面に出す(
MS_BringWindowToFront.md) - ジャーナルの記録・再生フックをインストールした場合。
- この API はシステムの強度を損なう可能性がある。
- Windowを前面に出す(
補足(
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) オブジェクトと合わせて、 長時間稼働する業務アプリでは 【定期的に監視する価値がある】
- メッセージ/メッセージ キュー - Microsoft Learn
https://learn.microsoft.com/ja-jp/windows/win32/winmsg/messages-and-message-queues - ウィンドウ メッセージ (Windows) - Microsoft Learn
https://learn.microsoft.com/ja-jp/windows/win32/winmsg/windowing
Tags: 移行, Windows, ウィンドウ・システム, プログラミング
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。