【テクニカル・上級編】【上級者】マルチスレッド風の非同期タスク制御をVBScriptとのプロセス連携で擬似的に実現し、マルチコアCPUをフル活用して数百ファイルのPPTXを並列かつ超高速にPDFエクスポート・クラウド同期するバッチ基盤 – PowerPoint VBA解析バイブル

スポンサーリンク
スポンサーリンク

単一スレッドの呪縛を解く:マルチプロセス・オーケストレーションによる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、そしてプロセス間連携を理解し、適切に操ることで、レガシーな環境においても、モダンな分散処理エンジンに近いパフォーマンスを引き出すことができる。

エンジニアとしての真価は、与えられた言語の仕様に縛られることではなく、その限界をいかにアーキテクチャの力で突破するかにある。

タイトルとURLをコピーしました