読者です 読者をやめる 読者になる 読者になる

ShellExecuteExで起動したアプリの制御

アプリを無理矢理動作させて、無理矢理印刷させるという、ある意味使い古されたスキームなんだけど、ウェブを探してもShellExecuteExを使った例は少ない。理由は簡単で、つい最近まで、プロセスハンドルから、プロセスIDを取得できなかったという簡単なこと。XP SP1以降でやっと、GetProcessIdが提供されるようになったみたい。

一方で、プロセスIDからOpenProcessしてみたらどうなんだというと、特定のプロセスに対してハンドルはいくつでも作成できてしまうので、別のハンドル値が返ってくる。比較するには適さないらしい。さらに、ハンドルからプロセスの素性を知るようなAPIは提供されていない。なので、複数のハンドルが同じプロセスを示しているかどうかについてはそんなに簡単には調べることはできなさそうだ。

struct FINDAPPWIN
{
  DWORD pid;
  HWND hwnd;
};

static BOOL CALLBACK findAppWindow(HWND hwnd, LPARAM lParam)
{
  FINDAPPWIN& faw = *(FINDAPPWIN*)lParam;
  DWORD pid;
  GetWindowThreadProcessId(hwnd, &pid);
  if(pid == faw.pid)
  {
    faw.hwnd = hwnd;
    return FALSE;
  }
  return TRUE;
}

// ...

SHELLEXECUTEINFO sei;
ZeroMemory(&sei, sizeof(sei));
sei.cbSize = sizeof(sei);
sei.lpVerb = "print";
sei.lpFile = "hogehoge.docx";
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_WAITFORINPUTIDLE;
ShellExecuteEx(&sei);

// 念には念を・・・
WaitForInputIdle(sei.hProcess, INFINITE);

FINDAPPWIN faw;
faw.pid = GetProcessId(sei.hProcess); // XP SP1 以降でサポート
faw.hwnd = NULL;
EnumWindows(findAppWindow, (LPARAM)&faw);
if(faw.hwnd)
{
  // アプリケーションのウィンドウが見つかった!
}
else
{
  // ない
}
CloseHandle(sei.hProcess);

ところで、なんでこんなことをしているかと言えば、Wordのドキュメントを印刷したいからなんだけど、パスワード保護がかかったりしているWordファイルは、ダイアログを表示しやがるので、これをどうにかしないと、ダイアログが表示された瞬間に処理が止まってしまう。最悪でもタイムアウト処理を入れて、TerminateProcessすべきだけど、それだと横暴すぎるので、できる範囲でがんばってみて、だめなら最後の手段を使うようにしたい。しかしながら、Word 2007, 2003で調べた感じでは、こいつらは、偽物のダイアログを使っている模様。少なくともウィンドウクラスが、"bosa_sdm_Microsoft Office Word 11"とか、"bosa_sdm_Microsoft Office Word 12"という名前。さらにボタンに見える物はボタンではないらしい(ウィンドウハンドルがない)。ので、やるとしても、無理矢理、[ESC]とかのキーイベントを送り込むしかないらしい。一方で、この名前は方々で使い回されており、たとえば、初回起動時に表示される「頭文字ダイアログ」や、マクロの警告ダイアログ(2007では出ない)も同じ名前。どのダイアログなのか区別するには、キャプションやメッセージも参考にせざるを得ないが、そんなことをするとローカライズの影響を受けるので面倒になる。どうしたものなのか。