はじめに
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
は、アプリケーションを起動し、そのタスクID(taskID
)を返します。この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
:exitCode
がSTILL_ACTIVE
である限り(つまり、電卓がまだ閉じられていない間)、ループを続けます。ユーザーが電卓を閉じると、exitCode
の値が変わり(通常は0
になります)、ループが終了します。DoEvents
: ループ中にDoEvents
を実行しないと、Excelが他の処理を行う余裕がなくなり、フリーズしたように見えてしまうため、必ず記述します。
4. CloseHandle
API関数
OpenProcess
で取得したプロセスハンドルは、使い終わったら必ず CloseHandle
で解放する必要があります。これを怠ると、メモリリークなどの原因になる可能性があるため、忘れないようにしましょう。
まとめ
今回は、Windows APIを組み合わせて、Shell
で起動した外部アプリケーションが終了するまでVBAの処理を待機させる、高度なテクニックを解説しました。
Shell
でアプリを起動し、タスクIDを取得。OpenProcess
でタスクIDからプロセスハンドルを取得。GetExitCodeProcess
をループで呼び出し、終了コードを監視。- 最後に
CloseHandle
でハンドルを解放。
この一連の流れをマスターすれば、外部のプログラムとVBAを連携させ、より複雑で自動化されたタスクを実行するマクロを構築することが可能になります。