他アプリを安全に終了させたい

[戻る]
bluedemio 2007/10/09(火) 11:53:46
ランチャーアプリみたいなものを作りたいと思っています。
ただし、以下の要件を満たすものとします。

1. ランチャーアプリにはいくつかボタンがある。
2. ボタンが押されると、ボタンに対応したアプリを起動する。
   ただし、既にランチャーアプリが起動させたアプリがある場合は、
   そのアプリを終了させる。

こういったことを実現するために、webでいろいろ調べて以下のようなコードを書いた
のですが、起動中のアプリが編集中の場合(xボタンで終了させようとすると、はい/いい
え/キャンセルのダイアログが出現)、かなりの確率でOSが何の反応もしなくなり、電プチ
せざるを得ない状況になります。
デバッグをしていて気になっているのは次の点です。

1. 問題があるのは、EnumWindowsProc関数内の
   ::PostMessage(hWnd, WM_CLOSE, NULL, NULL); の部分らしい。
   この行をコメントアウトすると、OSが何の反応もしなくなることはない。というか、
アプリを終了させようとしないので問題がないのでしょう。
2. 1つのプロセスIDに対して、ウィンドウハンドルが複数存在するため、何度もWM_CLOSE
メッセージをpostしてしまう。
   EnumWindowsProc関数内の最後を、return FALSE にすることも考えましたが、そうす
るとアプリが終了しないケースが多いのでやめています。

上記2点が問題になっていそうだということは分かったのですが、自分のしたいことを実
現するためにはどう改良すればいいのか
検討がつかない状況です。解決の糸口になるようなことを分かる方がいらっしゃいました
ら教えてください。よろしくお願いします。

開発環境: WindowsXP SP2, MFC, Visual Studio 2005

文字数制限にひっかかるため、かなり省略します。
--------------------------------------------------------------------------------
void MainFrame::onExtCall(UINT nID)
{ // ボタンが押されたときの処理
    if (!m_pThread) return;
   ::EnumWindows(EnumWindowsProc, (LPARAM)this);

   runThread();
}

BOOL CALLBACK MainFrame::EnumWindowsProc(HWND hWnd, LPARAM lParam)
{ // 外部呼出の終了
   MainFrame* pMainFrame = (MainFrame*)lParam;
   DWORD pid;
   DWORD threadID = GetWindowThreadProcessId(hWnd, &pid);

   if (pid != pMainFrame->m_extProcessInfo.dwProcessId) return TRUE;
   ::SetForegroundWindow(hWnd);
   ::PostMessage(hWnd, WM_CLOSE, NULL, NULL);
   return TRUE;
}

void MainFrame::runThread()
{ // スレッドを開始する
   if (m_pThread) return;
   m_pThread = ::AfxBeginThread(createProcess,
(LPVOID)this,THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);
   m_pThread->m_bAutoDelete = TRUE;
   m_pThread->ResumeThread();
}

UINT MainFrame::createProcess(LPVOID pParam)
{ // スレッド関数
   MainFrame* pMainFrame = (MainFrame*)pParam;
   STARTUPINFO si;
   // siの設定, exePath, parameterの設定をここでやるが省略
   bool bRet = CreateProcessW(exePath.c_str(), (LPTSTR)parameter.c_str(), NULL,
NULL, FALSE, 0, NULL, NULL, &si, &pMainFrame->m_extProcessInfo);

   ::WaitForInputIdle(pMainFrame->m_extProcessInfo.hProcess, INFINITE);

   DWORD dwRet;
   while (1)
   {
      dwRet = WaitForSingleObject(pMainFrame->m_extProcessInfo.hProcess, INFINITE);
      if (dwRet != WAIT_TIMEOUT) break;
   }

   ::CloseHandle(pMainFrame->m_extProcessInfo.hThread);
   ::CloseHandle(pMainFrame->m_extProcessInfo.hProcess);
   ZeroMemory(&pMainFrame->m_extProcessInfo, sizeof(PROCESS_INFORMATION));
   pMainFrame->PostMessage(WndMsg::WM_CREATE_PROCESS, NULL, NULL);
   return 0;
}

// スレッド終了後に、新たにスレッドを作成
// WndMsg::WM_CREATE_PROCESSが投げられると、この関数が呼び出される
LRESULT MainFrame::onCreateProcess(WPARAM wParam/*=0*/, LPARAM lParam/*=0*/)
{
   if (m_pThread)
   {
      ::WaitForSingleObject(m_pThread, INFINITE);
      m_pThread = 0;
   }
   runThread();
   return 0;
}

tetrapod 2007/10/09(火) 12:34:28
> そのアプリを終了させる。
ここを仕様レベルで再考するべし。
他アプリを「安全に」終了させる方法は原理的に存在しないので。
例えばコンソールアプリケーションに対して WM_CLOSE を送っても無駄だろう。

安全でなくていいなら TerminateProcess という手段もあるが、
こいつは自爆覚悟の最後の手段だ。

bluedemio 2007/10/09(火) 13:00:49
[[解決]]
> tetrapodさん

ありがとうございます。
webを調べていて、以下のような記述を見つけました。

> アプリケーションの作り次第ではWM_CLOSEでは通らないルートに
>必要な終了処理が書いてあるかもしれませんし。

たぶん、こういうことをおっしゃっているんですね。
ボタンを押して起動するアプリは変更される可能性があるので、仕様を変更する
必要がありそうです。アプリが終了するまで、ボタンを押せないようにしておくとか。

古いスレッドには追加発言できません。