導入: なぜ外部プログラムの「完了待ち」が必要なのか
実務において、VBAから外部ツール(バッチファイル、テキスト編集ソフト、データ変換用プログラムなど)を呼び出すことは珍しくありません。通常、VBAの「Shell関数」を使うと外部アプリを起動できますが、VBA側は相手の終了を待たずに次の行を実行してしまいます。これでは、外部プログラムが作成したファイルを読み込もうとして「ファイルが見つからない」といったエラーが発生してしまいます。この問題を解決するのが、APIによる「同期処理」です。
基礎知識: APIとプロセスハンドルの概念
Windowsには、OSの機能を直接呼び出すための「Win32 API」という仕組みがあります。
今回使うのは以下の3つの関数です。
OpenProcess: 起動したプログラムの「プロセスハンドル(管理番号)」を取得します。
WaitForSingleObject: 指定したプロセスが終了するまで、VBAの処理を一時停止させます。
CloseHandle: 取得したハンドルを解放し、メモリを整理します。
これらを組み合わせることで、外部アプリの終了を正確に検知できるようになります。
実装: Win32 APIを用いた同期処理
以下の手順で実装を行います。
1. API関数をVBAで使えるように「宣言(Declare)」する。
2. Shell関数で外部プログラムを起動し、その「タスクID」を取得する。
3. タスクIDから「プロセスハンドル」を取得する。
4. WaitForSingleObjectで終了を待機する。
5. 最後にハンドルを閉じて終了する。
サンプルプログラム
以下のコードを標準モジュールに貼り付けて実行してください。
‘— Win32 API 関数の宣言 —
If VBA7 Then
‘ 64bit版Office用
Public Declare PtrSafe Function WaitForSingleObject Lib “kernel32” (ByVal hHandle As LongPtr, ByVal dwMilliseconds As Long) As Long
Public Declare PtrSafe Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Public Declare PtrSafe Function CloseHandle Lib “kernel32” (ByVal hObject As LongPtr) As Long
Else
‘ 32bit版Office用
Public Declare Function WaitForSingleObject Lib “kernel32” (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Public Declare Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Public Declare Function CloseHandle Lib “kernel32” (ByVal hObject As Long) As Long
End If
‘— Win32 API 定数の宣言 —
Public Const PROCESS_ALL_ACCESS As Long = &H1F0FFF
Public Const INFINITE As Long = -1&
Sub WaitRunExample()
Dim TaskId As Long
Dim hProc As LongPtr ‘ 64bit環境対応のためPtr型を使用
‘ 1. 外部プログラムの実行(パスは適宜変更してください)
TaskId = Shell(“notepad.exe”, vbNormalFocus)
‘ 2. プロセスハンドルの取得
hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, TaskId)
‘ 3. プロセスハンドルが取得できた場合のみ待機
If hProc <> 0 Then
‘ プログラムが終了するまでVBAの実行をここで待機させる
Call WaitForSingleObject(hProc, INFINITE)
‘ 4. プロセスハンドルを解放
Call CloseHandle(hProc)
End If
MsgBox “外部プログラムの終了を確認しました。”
End Sub
応用・注意点: 現場で役立つアドバイス
1. タイムアウト設定の重要性
サンプルでは「INFINITE(無限待機)」を使用していますが、外部プログラムがフリーズした場合、VBAも永遠に停止してしまいます。実務では、WaitForSingleObjectの第2引数にミリ秒単位で時間を指定し、一定時間経過したらエラーハンドリングを行う構成にするのが安全です。
2. WSH(Windows Script Host)の検討
もしAPIの宣言が難しい、あるいはコードを簡潔にしたい場合は、WScript.ShellオブジェクトのRunメソッドを使用する手もあります。「objShell.Run “コマンド”, 0, True」のように第3引数をTrueに設定するだけで、同期実行が可能です。基本的にはこちらの方が記述が簡単でおすすめです。
3. 64bit/32bit環境への配慮
コード例のように「#If VBA7」を使用することで、現代のOffice環境(64bit版と32bit版)の両方で安定して動作するようになります。実務で配布するツールであれば、必ずこの分岐を入れておきましょう。
