単一スレッドの呪縛を解く:マルチプロセス・オーケストレーションによるPPTX超高速並列変換エンジンの設計思想
VBA(Visual Basic for Applications)を「単なるマクロ」と考えているうちは、真の自動化エンジニアへの道は遠い。
我々が直面する実務の最前線では、数百、数千というスライド資産を、定められた時間内にPDF化し、クラウドストレージへと同期しなければならない局面が頻発する。しかし、PowerPointのCOMオブジェクトモデルは、本質的にシングルスレッドで動作する。一つの`PowerPoint.Application`インスタンスが重い変換処理を行っている間、他のスレッドは待機を強いられ、マルチコアCPUの演算リソースは閑散としたまま、時計の針だけが進んでいく。
この「単一スレッドの限界」を突破するには、VBAのコードを書き換えるだけでは不十分だ。「プロセスの分離」と「並列実行のオーケストレーション」という、システムアーキテクチャの視点が必要となる。
本稿では、VBScriptをワーカー(実行部隊)として使い、VBAをコントローラー(指揮官)として機能させることで、マルチコアCPUの全リソースを使い切る「擬似非同期・マルチプロセス変換基盤」の構築手法を伝授する。
1. アーキテクチャ設計:Master-Worker パターン
単一のVBAプロセス内で複数の`PowerPoint.Application`を生成し、それを制御しようとする試みは、メモリリークとデッドロックの地獄を招く。COMオブジェクトのライフサイクル管理は極めて繊細であり、一つのプロセスがクラッシュしただけで、制御系まで道連れにするリスクがあるからだ。
我々が採用すべきは、「Master-Worker アーキテクチャ」である。
1. Master (VBA/Excel or PowerPoint):
タスクキュー(変換対象リスト)を管理し、システムのCPUリソース(論理コア数)を監視しながら、ワーカープロセスを非同期で起動・管理する。
2. Worker (VBScript):
引数として「変換対象ファイルパス」を受け取り、独立したプロセスとして`PowerPoint.Application`を立ち上げ、変換を完遂して、自ら潔く終了する。
これにより、プロセス間でのメモリ空間が完全に分離され、万が一一つの変換が失敗しても、システム全体が停止することはない。
2. 実装:高効率ワーカー(VBScript)
まず、実効部隊となるVBScriptを定義する。VBScriptは、GUIを持たず、軽量なプロセスの起動に適している。
' worker_convert.vbs
' 引数: [対象ファイルパス] [出力先フォルダ]
Option Explicit
Dim objArgs, targetFile, outputFolder, objPPT, objPres
Set objArgs = WScript.Arguments
If objArgs.Count < 2 Then
WScript.Quit 1 ' 引数不足は異常終了
End If
targetFile = objArgs(0)
outputFolder = objArgs(1)
On Error Resume Next
' PowerPoint インスタンスの生成(独立プロセス)
Set objPPT = CreateObject("PowerPoint.Application")
' 変換処理の実行
Set objPres = objPPT.Presentations.Open(targetFile, , , 0) ' 0 = msoFalse (読み取り専用)
If Err.Number = 0 Then
' PDFとしてエクスポート
Dim fileName, pdfPath
fileName = CreateObject("Scripting.FileSystemObject").GetBaseName(targetFile)
pdfPath = outputFolder & "\" & fileName & ".pdf"
' 32 = ppSaveAsPDF
objPres.SaveAs pdfPath, 32
objPres.Close
Else
' エラーログの出力(実運用ではファイルへ書き出し)
WScript.Echo "Error processing: " & targetFile & " - " & Err.Description
End If
' ライフサイクルの厳格な管理
' COMオブジェクトを明示的に解放し、プロセスを確実に終了させる
Set objPres = Nothing
objPPT.Quit
Set objPPT = Nothing
If Err.Number <> 0 Then
WScript.Quit 2 ' 実行時エラー
Else
WScript.Quit 0 ' 正常終了
End If
3. 実装:司令塔(VBA Master Controller)
次に、複数のワーカーを並列に叩き起こすコントローラーを実装する。ここでは、`WScript.Shell`の`Run`メソッドを用い、第3引数に`False`を指定することで、プロセスの終了を待たずに次の処理へ進む「非同期起動」を実現する。
' MasterController.bas
Option Explicit
' --------------------------------------------------------------------------------
' 並列変換エンジン
' --------------------------------------------------------------------------------
Public Sub ExecuteParallelConversion()
Dim fso As Object
Dim targetFolder As String, outputFolder As String, workerScript As String
Dim fileList As Collection
Dim i As Long, currentRunning As Integer
Dim maxConcurrency As Integer
Dim shell As Object
Dim cmd As String
' 設定領域
targetFolder = "C:\Data\Input" ' 入力ディレクトリ
outputFolder = "C:\Data\Output" ' 出力ディレクトリ
workerScript = "C:\Scripts\worker_convert.vbs" ' ワーカーのパス
maxConcurrency = 4 ' 同時実行プロセス数 (CPUコア数に合わせて調整)
Set fso = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("WScript.Shell")
Set fileList = New Collection
' 1. タスクキューの構築
Dim file As Object
For Each file In fso.GetFolder(targetFolder).Files
If LCase(fso.GetExtensionName(file.Name)) = "pptx" Then
fileList.Add file.Path
End If
Next
Debug.Print "Total Tasks: " & fileList.Count
' 2. 並列実行ループ
i = 1
Do While i <= fileList.Count Or currentRunning > 0
' 空きスロットがある限り、ワーカーを起動
Do While currentRunning < maxConcurrency And i <= fileList.Count
cmd = "wscript.exe """ & workerScript & """ """ & fileList(i) & """ """ & outputFolder & """"
' Run(cmd, windowStyle, waitOnReturn)
' waitOnReturn = False にすることで、非同期(投げっぱなし)を実現
shell.Run cmd, 0, False
Debug.Print "Started: " & fileList(i)
i = i + 1
currentRunning = currentRunning + 1
Loop
' 3. プロセスの監視(ポーリング)
' 本来はWMI等でプロセス監視を行うのが理想的だが、
' ここでは簡易的にスリープを挟み、実行中のプロセス数を管理する。
' 実運用では WMI (Win32_Process) を用いて正確なプロセス生存確認を行う。
DoEvents
Sleep 1000 ' 1秒待機
' プロセス数の更新(WMIによる実プロセス数の取得)
currentRunning = GetRunningWorkerCount(workerScript)
Debug.Print "Current Running: " & currentRunning & " | Progress: " & i - 1 & "/" & fileList.Count
Loop
MsgBox "All tasks completed.", vbInformation
End Sub
' --------------------------------------------------------------------------------
' WMIを用いた実行中プロセスの正確なカウント
' --------------------------------------------------------------------------------
Private Function GetRunningWorkerCount(scriptPath As String) As Integer
Dim objWMIService As Object
Dim colProcesses As Object
Dim objProcess As Object
Dim count As Integer
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
' wscript.exe かつ コマンドラインに workerScript を含むプロセスを抽出
Set colProcesses = objWMIService.ExecQuery( _
"Select * from Win32_Process Where Name = 'wscript.exe' AND CommandLine LIKE '%" & Replace(scriptPath, "\", "\\") & "%'")
count = colProcesses.Count
GetRunningWorkerCount = count
End Function
' API宣言
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Else
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
End If
4. 極限の知見:アーキテクトが注視すべき3つの要諦
この基盤を実稼働環境に投入する際、単なる実装以上の「考慮」が求められる。
① リソースの飽和とスロットリング
`maxConcurrency`(同時実行数)の設定は、理論上の論理コア数に合わせるのが定石だが、PowerPointはメモリ消費が激しいアプリケーションだ。1プロセスあたり数百MBのメモリを消費する場合、コア数いっぱいに回すと、OS全体のページングが発生し、逆にスループットが低下する。「CPU使用率」と「物理メモリの空き容量」のバランスを見極め、最良のスロットリング値を決定せよ。
② プロセス・ゾンビ化の回避
VBA側でエラーが発生した場合や、強制終了させた場合、`wscript.exe`や`POWERPNT.EXE`がメモリ上に残り続ける「ゾンビプロセス」が発生する。これは、次回の実行時にメモリを圧迫し、システムを不安定にする。
実運用では、`GetRunningWorkerCount` での監視に加え、一定時間経過しても終了しないプロセスを強制終了(`taskkill`)するウォッチドッグ・タイマーの概念を組み込むべきだ。
③ クラウド同期へのパイプライン化
変換後のPDFをクラウド(OneDrive/SharePoint/S3等)へ同期する工程も、このアーキテクチャに組み込める。ワーカーの最後に、`rclone` や `aws s3 sync` といったCLIツールを呼び出す処理を追加すれば、「変換 $\rightarrow$ 検証 $\rightarrow$ 同期」という一連のデータパイプラインが、完全な非同期ストリームとして完成する。
結言
VBAは、単なる「操作の記録」のためのツールではない。Windows APIやWMI、そしてプロセス間連携を理解し、適切に操ることで、レガシーな環境においても、モダンな分散処理エンジンに近いパフォーマンスを引き出すことができる。
エンジニアとしての真価は、与えられた言語の仕様に縛られることではなく、その限界をいかにアーキテクチャの力で突破するかにある。
