起動したアプリケーションの終了を取得する

CreateProcess()等で指定したアプリケーションを起動後、その終了を待つには
WaitForSingleObject()でアプリケーションがが終わるまで待機します。
しかしこの方法では待機している間プロセスが止まってしまい固まってしまいます。(*1)
タイマーをセットして定期的にGetExitCodeProcess()で起動したアプリケーションが
終了していないか調べる方法も有りますが、タイマーの間隔が短いとCPUに負荷がかかりますし
逆にタイマーの間隔を長くすると終了してから取得するまでのタイムラグが大きくなってしまいます。(*2)
そこで少し面倒ですが新しくスレッドを作成しそのスレッドでプロセスを起動後、終了するまで待機させ
終了後メッセージを送る事でアプリケーションの終了を効率的に取得できます。(*3)

(*1) WaitForSingleObjectで終了まで待機する。

// lpPath 起動するアプリケーションのパス
void ExitCheckWait(LPTSTR lpPath)
{
    // プロセスを起動
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOWNORMAL;
    ::CreateProcess(NULL, lpPath, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    CString strMessage = lpPath;
    AfxMessageBox(strMessage + _T("を起動しました。"));

    // 終了まで待機
    ::WaitForSingleObject(pi.hProcess, INFINITE);

    // 終了を告知
    AfxMessageBox(strMessage + _T("が終了しました。"));
    ::CloseHandle(pi.hThread);
    ::CloseHandle(pi.hProcess);
}

(*2) タイマーで終了を取得する。

// メンバ変数に追加
PROCESS_INFORMATION m_pi;

// タイマーIDの設定(.cppファイルの先頭辺りにでも記述)
#define PROCESS_CHECK_TIMER 100

// コンストラクタ
CXxxDlg::CXxxDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CXxxDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CTestDlg)
    //}}AFX_DATA_INIT
    m_pi.hProcess = NULL;  // メンバ変数の初期化
}

// メイン関数 (lpPath 起動するアプリケーションのパス)
void CXxxDlg::ExitCheckTimer(LPTSTR lpPath)
{
    // 既に起動されていないかチェック
    if (m_pi.hProcess)
        return;

    // プロセスを起動
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOWNORMAL;
    ::CreateProcess(NULL, lpPath, NULL, NULL, FALSE, 0, NULL, NULL, &si, &m_pi);

    // 監視用タイマーを作成
    SetTimer(PROCESS_CHECK_TIMER, 1000, NULL); // 1000ms毎にチェック

    CString strMessage = lpPath;
    AfxMessageBox(strMessage + _T("を起動しました。"));
}

// 終了をチェック (WM_TIMERのメッセージハンドラを追加)
void CXxxDlg::OnTimer(UINT nIDEvent) 
{
    if (nIDEvent == PROCESS_CHECK_TIMER)
    {
        // プロセスが終了していない場合dwExitCodeにSTILL_ACTIVEが格納される
        DWORD dwExitCode;
        ::GetExitCodeProcess(m_pi.hProcess, &dwExitCode);
        if (dwExitCode != STILL_ACTIVE)
        {
            KillTimer(PROCESS_CHECK_TIMER);
            ::CloseHandle(m_pi.hProcess);
            ::CloseHandle(m_pi.hThread);
            m_pi.hProcess = NULL;
            AfxMessageBox(_T("プロセスが終了しました。"));
        }
    }
    CDialog::OnTimer(nIDEvent);
}

(*3) 新しくスレッドを作成し終了後メッセージを送る。

// xxxDlg.cpp : インプリメンテーション ファイル
//

#include "stdafx.h"
#include "xxx.h"
#include "xxxDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// 終了メッセージのIDの設定
#define WM_EXITPROCESS (WM_APP + 1)

// ワーカースレッドに渡す起動及び終了メッセージ取得の為の構造体の定義
typedef struct tagRUNEXECINFO
{
    HWND hWnd;                // 終了メッセージを通知するウィンドウのハンドル
    char lpPath[MAX_PATH];    // 起動するアプリケーションのパス
    BOOL bRun;                // 起動準備が出来たがどうか調べるフラグ
} RUNEXECINFO, *PRUNEXECINFO;

// ワーカースレッドでアプリケーションを起動し待機、及び終了メッセージの送信
DWORD WINAPI ExitCheckThreadMain(LPVOID pParam)
{
    // 起動情報をコピー
    PRUNEXECINFO prei = (PRUNEXECINFO)pParam;
    HWND hWnd = prei->hWnd;
    char lpPath[MAX_PATH];
    ::lstrcpy(lpPath, prei->lpPath);
    prei->bRun = TRUE;

    
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOWNORMAL;
    if (::CreateProcess(NULL, lpPath, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
    {
        CString strMessage;
        strMessage.Format(_T("プロセス(ID = %d)を起動しました。"), pi.dwProcessId);
        AfxMessageBox(strMessage);
    }
    else
    {
        // 起動失敗
        return 1;
    }

    // 終了まで待機
    ::WaitForSingleObject(pi.hProcess, INFINITE);

    // メッセージを送信
    ::PostMessage(hWnd, WM_EXITPROCESS, (WPARAM)pi.dwProcessId, NULL);
    ::CloseHandle(pi.hThread);
    ::CloseHandle(pi.hProcess);

    return 0;
}

// メイン関数 (lpPath 起動するアプリケーションのパス)
void CTestDlg::ExitCheckThread(LPCTSTR lpPath) 
{
    // スレッドを起動
    RUNEXECINFO rei;
    DWORD dwThreadId;
    rei.hWnd = m_hWnd;
    strcpy(rei.lpPath, lpPath);
    rei.bRun = FALSE;
    ::CreateThread(NULL, 0, ExitCheckThreadMain, &rei, 0, &dwThreadId);

    // 起動情報がコピーされるまで待機
    while(!rei.bRun)
        ::Sleep(100);
}

// メッセージの取得 (仮想関数の追加でWindowProcを追加)
LRESULT CTestDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
    if (message == WM_EXITPROCESS)
    {
        CString strMessage;
        strMessage.Format(_T("プロセス(ID = %d)が終了しました。"), wParam);
        AfxMessageBox(strMessage);
    }
    return CDialog::WindowProc(message, wParam, lParam);
}

各サンプルの説明ですが(*1)と(*2)についてはコメントに書いてあるとおりですし、
メインのサンプルではないので説明は省略します。

で(*3)についての説明ですが、
マルチスレッドというと難しく思えるかもしれませんがワーカースレッドならばそれほど難しくはありません。
スレッドを作成するにはCreateThread()で作成できます。
CreateThread()を実行すると3番目の引数に指定された関数を別スレッドで実行します。
ここで設定する関数はグローバル関数でで以下の形式でなければなりません。
DWORD WINAPI ThreadFunc(LPVOID);
4番目の引数には関数に渡すデータを設定します。
普通では1つしかデータは渡せないのでここでは渡すデータが入った構造体を定義し
そのポインタを引数にする事で複数のデータを渡す事が出来ます。
構造体の定義はサンプルにある通りです。

順を追って説明していくと、まずは新スレッドに渡す構造体の初期化を行います。
この構造体に起動するアプリケーションのパスとメッセージを送る先のウィンドウハンドルを指定します。
それから子スレッドにて構造体の情報がコピーされるまで親スレッドでスコープを抜け
構造体の情報が破壊されない様に待機のフラグを設定しておきます。
そしてCreateThread()にて子スレッドを作成して構造体の情報がコピーされるまで待機します。
この時、子スレッドではまず構造体の情報をコピーします。
起動するアプリケーションのパスをコピーする際、lstrcpy()を使っていますが
これはCランタイムライブラリの関数のstrcpy()と同じ働きをするAPIです。
何故わざわざAPIを使用するかというとCreateThread()で作成されたスレッド内で
Cランタイムライブラリの関数は使わないようにとMSDNに記載されている為です。
# 多分ですが、マルチスレッドでCランタイムライブラリの関数を使用する場合は
# マルチスレッド対応のCランタイムライブラリとリンクしなければならない為だと思われます。
で、情報のコピーが完了したらフラグをリセットして親スレッドの待機状態を解除し
親スレッドをメインループに戻してやります。
次に(*1)と同じようにアプリケーションを起動し待機させ、終了したらPostMessage()
指定されたウィンドウに#defineマクロで定義したメッセージを送ります。
この時一緒に適当なデータを送る事も可能です。
その後、子スレッドは関数を抜けると自動的に終了されます。
関数の戻り値はGetExitCodeThread()を使って取得できる物になります。
サンプルではアプリケーションが起動できなかった場合、return 1として関数を抜けていますが
これは親スレッドでGetExitCodeThread()を使ってスレッドの終了状態を取得した時
終了コードが1の時はアプリケーションが起動できなかったと調べる事ができる様にです。
ただ、今回のサンプルではその辺の事は省略していますがデバッグ等で役に立つでしょう。
あとは親スレッドでメッセージが送られてきたのを取得することでアプリケーションの終了を取得できます。
メッセージの取得は仮想関数のWindowProc()で取得してやってください。

< 戻る << HOME ©1999-2001 by Akky, All right reserved.