【VBA】起動した外部アプリが終了するまで待機する方法 (Windows API)

目次

はじめに

VBAの Shell 関数で外部のアプリケーションを起動すると、VBAはアプリケーションの起動だけを行って、その終了を待たずに、すぐに次のコードの実行に移ってしまいます。しかし、時には「ユーザーがメモ帳での編集を終えて、ファイルを保存して閉じるまで、マクロの処理を待機させたい」という場面があります。

このような「外部プロセスの終了待ち」は、いくつかのWindows API関数を組み合わせることで実現できます。この記事では、Shell で起動したアプリケーションのプロセスを監視し、それが終了するまでVBAの実行を待機させる、高度なテクニックを解説します。


外部アプリの終了を待機するVBAサンプルコード

このマクロは、Windowsの「電卓」を起動し、ユーザーが電卓のウィンドウを閉じるまでVBAの処理を中断します。電卓が終了したことを検知すると、その旨をメッセージで表示します。

Declareステートメントや Const は、モジュールの最上部Sub などのプロシージャよりも前)に記述する必要があります。

完成コード

'--- モジュールの最上部にAPI関数と定数を宣言 ---
' 64bit/32bit両対応
#If VBA7 Then
    Private Declare PtrSafe Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As LongPtr
    Private Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As LongPtr, lpExitCode As Long) As Long
    Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long
#Else
    Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
    Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
#End If

' プロセスの状態を照会する権限
Private Const PROCESS_QUERY_INFORMATION = &H400
' プロセスがまだアクティブであることを示す定数
Private Const STILL_ACTIVE = &H103


' 電卓を起動し、それが終了するまで待機する
Sub WaitForProcessToClose()
    Dim taskID As Long
    Dim processHandle As LongPtr
    Dim exitCode As Long
    
    MsgBox "電卓を起動します。電卓を終了すると、次のメッセージが表示されます。"
    
    '--- 1. Shell関数で電卓を起動し、タスクIDを取得 ---
    taskID = Shell("calc.exe", vbNormalFocus)
    
    '--- 2. OpenProcessで、タスクIDからプロセスのハンドルを取得 ---
    processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, taskID)
    
    '--- 3. プロセスの終了コードをポーリング(定期監視) ---
    Do
        ' GetExitCodeProcessで、プロセスの現在の終了コードを取得
        GetExitCodeProcess processHandle, exitCode
        ' ExcelがフリーズしないようにCPUを解放
        DoEvents
    ' 終了コードがSTILL_ACTIVE(実行中)である限り、ループを続ける
    Loop While exitCode = STILL_ACTIVE
    
    '--- 4. CloseHandleで、プロセスのハンドルを解放 ---
    CloseHandle processHandle
    
    MsgBox "電卓が終了しました。"
End Sub

コードの解説

1. Shell 関数

Shell は、アプリケーションを起動し、そのタスクIDtaskID)を返します。このIDは、Windowsがそのプロセスを識別するための番号です。

2. OpenProcess API関数

OpenProcess は、タスクIDを基に、そのプロセスをVBAから操作するための「プロセスハンドル」(processHandle)を取得します。ハンドルは、プロセスへの「接続許可証」のようなものです。

  • PROCESS_QUERY_INFORMATION: 「プロセスの情報を照会する」という権限を要求しています。

3. GetExitCodeProcess API関数 と Do...Loop

これが、プロセスの終了を監視する核心部分です。

  • GetExitCodeProcess: 指定したプロセスハンドル(processHandle)の終了コードを取得し、第2引数の exitCode 変数に格納します。
  • STILL_ACTIVE: GetExitCodeProcess が返す特別な定数値で、プロセスがまだ実行中であることを意味します。
  • Do...Loop While exitCode = STILL_ACTIVE: exitCodeSTILL_ACTIVE である限り(つまり、電卓がまだ閉じられていない間)、ループを続けます。ユーザーが電卓を閉じると、exitCode の値が変わり(通常は 0 になります)、ループが終了します。
  • DoEvents: ループ中に DoEvents を実行しないと、Excelが他の処理を行う余裕がなくなり、フリーズしたように見えてしまうため、必ず記述します。

4. CloseHandle API関数

OpenProcess で取得したプロセスハンドルは、使い終わったら必ず CloseHandle で解放する必要があります。これを怠ると、メモリリークなどの原因になる可能性があるため、忘れないようにしましょう。


まとめ

今回は、Windows APIを組み合わせて、Shellで起動した外部アプリケーションが終了するまでVBAの処理を待機させる、高度なテクニックを解説しました。

  1. Shell でアプリを起動し、タスクIDを取得。
  2. OpenProcess でタスクIDからプロセスハンドルを取得。
  3. GetExitCodeProcess をループで呼び出し、終了コードを監視。
  4. 最後に CloseHandle でハンドルを解放。

この一連の流れをマスターすれば、外部のプログラムとVBAを連携させ、より複雑で自動化されたタスクを実行するマクロを構築することが可能になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次