ランチャーアプリみたいなものを作りたいと思っています。
ただし、以下の要件を満たすものとします。
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;
}
> そのアプリを終了させる。
ここを仕様レベルで再考するべし。
他アプリを「安全に」終了させる方法は原理的に存在しないので。
例えばコンソールアプリケーションに対して WM_CLOSE を送っても無駄だろう。
安全でなくていいなら TerminateProcess という手段もあるが、
こいつは自爆覚悟の最後の手段だ。
> tetrapodさん
ありがとうございます。
webを調べていて、以下のような記述を見つけました。
> アプリケーションの作り次第ではWM_CLOSEでは通らないルートに
>必要な終了処理が書いてあるかもしれませんし。
たぶん、こういうことをおっしゃっているんですね。
ボタンを押して起動するアプリは変更される可能性があるので、仕様を変更する
必要がありそうです。アプリが終了するまで、ボタンを押せないようにしておくとか。