Ghost ウィンドウに注意しようの巻

2008年も明けて、数日経ちました。皆さんはどのように年越ししましたか?
さて、今年最初の更新は、このネタにしました。
突然ですが、皆さんは 「Ghost ウィンドウ」 ってご存知でしょうか。

Windows XP 以降の OS では、トップレベルのウィンドウを持つスレッドで、
5 秒以上メッセージ キューを処理しない (PeekMessage()とか) でチェックをしない場合、
OS はアプリケーションをハングアップしているとみなしてしまいます。

Windows 2000 までの OS では、ハングアップしていると判断することには同じだったのですが、
OS としては何も処理することはありませんでした。
このため、ユーザーから見た場合、ハングアップしたウィンドウを動かすことができないために
その裏に存在する別のウィンドウを操作できないという操作性の問題がありました。

そこで、Windows XP 以降ではハングアップしたウィンドウに対して、ダミーのウィンドウを作成し
ハングアップしているウィンドウであっても、ユーザーがウィンドウの移動を擬似的に
行えるような機能が追加されました。このダミーで作成されるウィンドウを 「Ghost ウィンドウ と呼びます。

ふむふむ とうなづく方もいらっしゃるのではないでしょうか。

ちなみに、Ghost ウィンドウの判定は、FindWindow() の戻り値のウィンドウハンドルに対して
GetClassName() でウィンドウのクラス名を取得してみると、"Ghost" となっていることでわかります。

hWnd = FindWindow(NULL, "ウィンドウ名");
if (hWnd)
{
  char clsname[128] = "";
  GetClassName(hWnd, clsname, sizeof(clsname));
  char buf[256] = "";
  wsprintf(buf, "ERROR: ClassName(Win32) = %s", clsname);
  MessageBox(NULL, buf, "Ghost Window ?", MB_OK);
}

Ghost ウィンドウになっていると、ERROR: ClassName(Win32) = Ghost と表示されるでしょう。

では、どうやって対応したらいいのか。
方法は 2 つあります。

  1. ハングアップしたと OS に判断させないようにする
  2. Ghost ウィンドウの機能を無効にしてしまう

1. については、ウィンドウを持つスレッドでループ処理を行う場合にループの中で定期的に
メッセージ キューをチェックするようにします。
具体的には、PeekMessage 関数を利用して、メッセージキューをチェックし、
ウィンドウ メッセージが存在する場合には、DispatchMessage 関数で
メッセージをディスパッチする方法が考えられます。

コード例:
MSG msg;
while(::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

おなじみのメッセージループですね。

2. については、Windows XP SP1 以降が前提ですが、DisableProcessWindowsGhosting APIを
アプリケーションから呼び出すことで、Ghost ウィンドウの機能を無効にすることが出来ます。

MSDN より DisableProcessWindowsGhosting Function

この API は呼び出したプロセスのみに対して機能し、API を呼び出した時点で
Ghost ウィンドウを無効にします。プロセスが終了するまで Ghost ウィンドウを無効にするのですが
再度有効にする方法は残念ながら提供されていません。

ちなみに、実行ファイルのプロパティを開き、[互換性]タブで Windows 2000 にしてしまう…というのもありですね。
Windows 2000 には、Ghost ウィンドウが無いわけですから。

なお、IsHungAppWindow API を使うことで応答が無くなった状態かどうかの判定ができます。

MSDN より IsHungAppWindow Function

使わないで済むなら、使いたくないものです。
くれぐれも、ウィンドウメッセージを処理せずに放置しておく…なんてことがないようにしましょう。

それでは、今回はこの辺で。

コメントを残す