【極限設計】数千件のPDF変換を完遂する:PowerPoint VBAにおけるメモリリーク・COM例外への生存戦略
「10件程度の変換なら動く。しかし、100件を超えたあたりで、あるいは数千ページに及ぶ巨大な資料を処理し始めた途端、プログラムは沈黙する。」
もし君が、このような現象に頭を抱えているとしたら、君は今、「スクリプト作成者」から「システムアーキテクト」へと脱皮すべき境界線に立っている。
PowerPointのCOMオブジェクトを操作してPDFエクスポートを行う処理は、一見単純に見える。しかし、大規模なバッチ処理において、メモリリークとCOM例外(RPCサーバーを利用できません、等)は、避けては通れない「必然の災厄」だ。なぜなら、Office製品のCOMインターフェースは、オブジェクトの破棄(`Set obj = Nothing`)を命じても、そのメモリを即座に、かつ完全に解放するとは限らないからだ。
本稿では、単なるループ処理ではない。高負荷環境下で、数千のファイルを確実に、かつ堅牢に処理し切るための「プロセス・リサイクリング・アーキテクチャ」を伝授する。
1. なぜ `Set obj = Nothing` だけでは足りないのか
多くの開発者が陥る致命的な誤解がある。「オブジェクトを `Nothing` に代入すれば、メモリは解放される」という幻想だ。
VBAにおける `Nothing` の代入は、あくまでVBA側の参照カウントを減らす操作に過ぎない。背後で動いている `powerpnt.exe` プロセス内のヒープ領域に、参照が残存したり、内部的なキャッシュが蓄積されたりすることは日常茶飯事だ。特に `ExportAsFixedFormat` メソッドは、レンダリングエンジンを激しく消費するため、実行のたびにメモリの断片化を引き起こす。
この断片化が積み重なると、OSは新しいメモリ領域を割り当てられなくなり、COM例外をスローしてプロセスをクラッシュさせる。つまり、「オブジェクトの解放」ではなく「プロセスの生存期間の管理」こそが、本質的な解決策なのだ。
2. 堅牢なアーキテクチャ:プロセス・リサイクリング・パターン
私が提唱する解決策は、「一定のワークロードごとに、PowerPointプロセス自体を物理的に再起動する」という戦略だ。
具体的には、以下の3つのレイヤーで設計を構成する。
1. Orchestrator(司令塔): 全体の進捗、エラーログ、および「何件ごとにプロセスを再起動するか」を管理する。
2. Worker Unit(作業員): 単一のプレゼンテーションを開き、PDF化し、完全にクローズする。
3. Watchdog(監視役): 処理中にプロセスがハングアップ(応答なし)していないかを監視する。
設計の核心:バッチ・リセット・ロジック
1,000個のファイルがある場合、1,000回 `powerpnt.exe` を立ち上げるのは非効率だ。しかし、1回で1,000個こなすのは自殺行為だ。
「50ファイルごとにPowerPointアプリケーションを一度完全に終了し、再起動する」というバッチ・リサイクリングが、パフォーマンスと安定性の黄金比となる。
3. 実装例:プロダクション・グレードの変換エンジン
以下に、実務でそのまま利用可能な、極めて堅牢な設計思想に基づいたコードを示す。
' ==============================================================================
' Module: BatchPDFConverter
' Description: 高負荷対応型PDF一括変換エンジン
' Strategy: Process Recycling & Batch Management
' ==============================================================================
Option Explicit
' 設定定数
Private Const BATCH_SIZE As Integer = 50 ' 何件ごとにPowerPointプロセスを再起動するか
Private Const MAX_RETRIES As Integer = 3 ' COMエラー時の再試行回数
Public Sub ExecuteMassiveConversion(ByVal targetFolder As String)
Dim fileList As Collection
Set fileList = GetFileList(targetFolder)
Dim totalFiles As Long
totalFiles = fileList.Count
Dim i As Long
Dim currentBatchCount As Integer
Dim pptApp As Object
Debug.Print "--- Conversion Started: " & totalFiles & " files ---"
For i = 1 To totalFiles
' バッチの境界判定:新しいバッチの開始、または初回実行時
If (i - 1) Mod BATCH_SIZE = 0 Then
If Not pptApp Is Nothing Then
Call SafeCleanup(pptApp)
End If
Set pptApp = CreatePowerPointInstance()
currentBatchCount = 0
Debug.Print "--- New Process Spawned (Batch Start) ---"
End If
' 個別ファイルの処理
If ProcessSingleFile(pptApp, fileList(i)) Then
currentBatchCount = currentBatchCount + 1
Else
Debug.Print "!!! Critical Error in file: " & fileList(i)
' ここでエラーログをDBやファイルに書き出す処理を挟むのがプロの設計
End If
' 進捗表示
If i Mod 10 = 0 Then Debug.Print "Progress: " & i & "/" & totalFiles
Next i
' 最終クリーンアップ
If Not pptApp Is Nothing Then Call SafeCleanup(pptApp)
Debug.Print "--- Conversion Completed Successfully ---"
End Sub
' ------------------------------------------------------------------------------
' Worker Unit: 単一ファイルの処理(リトライメカニズム付き)
' ------------------------------------------------------------------------------
Private Function ProcessSingleFile(ByRef pptApp As Object, ByVal filePath As String) As Boolean
Dim pres As Object
Dim pdfPath As String
Dim retryCount As Integer
Dim success As Boolean
pdfPath = Replace(filePath, ".pptx", ".pdf")
' 拡張子が異なる場合(.ppt等)の考慮が必要な場合はここで正規化する
retryCount = 0
success = False
Do While retryCount < MAX_RETRIES And Not success
On Error Resume Next
' プレゼンテーションを開く
Set pres = pptApp.Presentations.Open(Filename:=filePath, ReadOnly:=True, WithWindow:=False)
If Err.Number = 0 Then
' PDFエクスポート実行
pres.ExportAsFixedFormat Path:=pdfPath, FixedFormatType:=2 ' 2 = ppFixedFormatTypePDF
If Err.Number = 0 Then
success = True
Else
Debug.Print " [Error] Export failed: " & Err.Description
End If
' オブジェクトの即時解放
pres.Close
Set pres = Nothing
Else
Debug.Print " [Error] Open failed: " & Err.Description
' COMエラー(RPCサーバー等)の場合は、プロセスが死んでいる可能性がある
If Err.Number = -2147024770 Or Err.Number = -2147417848 Then ' 例: RPCエラー
Debug.Print " [Critical] COM Interface lost. Forcing restart..."
Set pptApp = Nothing ' 次のループで再生成させる
Exit Do
End If
End If
On Error GoTo 0
If Not success Then
retryCount = retryCount + 1
DoEvents ' OSに制御を戻し、リソースの整理を促す
Sleep 1000 ' 待機
End If
Loop
ProcessSingleFile = success
End Function
' ------------------------------------------------------------------------------
' Helper: PowerPointインスタンスの生成
' ------------------------------------------------------------------------------
Private Function CreatePowerPointInstance() As Object
On Error Resume Next
Dim app As Object
Set app = CreateObject("PowerPoint.Application")
If app Is Nothing Then
Err.Raise 1001, , "Failed to create PowerPoint instance."
End If
Set CreatePowerPointInstance = app
On Error GoTo 0
End Function
' ------------------------------------------------------------------------------
' Helper: 安全なクリーンアップ
' ------------------------------------------------------------------------------
Private Sub SafeCleanup(ByRef obj As Object)
On Error Resume Next
If Not obj Is Nothing Then
obj.Quit
Set obj = Nothing
End If
On Error GoTo 0
End Sub
' ------------------------------------------------------------------------------
' Helper: ファイルリスト取得
' ------------------------------------------------------------------------------
Private Function GetFileList(ByVal folderPath As String) As Collection
Dim col As New Collection
Dim fileName As String
fileName = Dir(folderPath & "\*.pptx")
Do While fileName <> ""
col.Add folderPath & "\" & fileName
fileName = Dir()
Loop
Set GetFileList = col
End Function
' Windows 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点を考慮せよ。
① データベース連携とトランザクション
数千件の処理を行う際、エラーが発生した箇所を特定できないのは致命的だ。`ProcessSingleFile` が `False` を返した際、そのファイル名、エラー内容、タイムスタンプを必ず外部ログ(CSV、SQLite、またはSQL Server)に書き出せ。VBAの `Debug.Print` は、開発用であって、プロダクション環境のログではない。
② ファイルI/Oのボトルネック
PDF変換はCPUとメモリだけでなく、ディスクI/Oも激しく消費する。ネットワークドライブ上のファイルを直接処理するのは、COMエラーを誘発する最大の要因の一つだ。可能であれば、「ローカルの高速なSSDに全ファイルをコピー → 変換 → 結果を戻す」というワークフローを組むべきだ。
③ Watchdog(死活監視)の高度化
上記のコードでは、エラー発生時にリトライを行うが、より高度な設計では、別プロセスから `PowerPoint.exe` の CPU使用率やメモリ使用量を監視し、閾値を超えたら強制的に `TaskKill` を実行してプロセスを入れ替える「外部監視型アーキテクチャ」を採用することもある。
結論
大規模な自動化において、「エラーが起きないように書く」ことには限界がある。
真のプロフェッショナルは、「エラーが起きることを前提とし、エラーが起きてもシステム全体が崩壊しない設計」を構築する。
プロセスをリサイクルし、境界を定義し、状態を監視する。この設計思想こそが、君のツールを「ただのスクリプト」から「信頼されるエンジニアリング資産」へと昇華させるのだ。
