破綻する自動化を、堅牢なシステムへ昇華させる —— PowerPoint VBAによる「レジリエント・バッチ・エンジン」の設計思想
大量のプレゼンテーションファイルをPDFへと一括変換する。一見、`For Each` ループでファイルを回し、`ExportAsFixedFormat` メソッドを呼び出すだけの単純な作業に見えるだろう。しかし、現場で数百、数千というファイルを捌こうとした瞬間、君たちは「Out of Memory」という名の絶望、あるいは「応答なし」となったPowerPointの死体に直面することになる。
なぜ、コードは正しいはずなのに、実行中に死ぬのか?
それは、オブジェクトのライフサイクルと、Windows OSのリソース管理という、プログラミングの背後にある「物理的な制約」を無視しているからだ。
本稿では、単なる「変換マクロ」ではなく、システムの枯渇を自律的に検知し、自己修復(プロセス再起動)を行う、エンタープライズ級の「レジリエント・バッチ・エンジン」の設計手法を伝授する。
1. なぜ、単純なループは「死」を招くのか
多くの開発者が陥る罠は、一つのPowerPointインスタンスを長時間生存させ続けることだ。
PowerPointのCOMオブジェクトモデルは、非常に強力だが、長時間稼働させることでメモリリーク(Memory Leak)を誘発しやすい。特に、複雑な図形、埋め込まれたオブジェクト、高解像度の画像を含むスライドを連続して処理すると、解放されたはずのメモリ領域が断片化(Fragmentation)し、OSから「これ以上メモリを割り当てられない」と宣告される。
これを回避するには、単に `Set pptApp = Nothing` と書くだけでは不十分だ。「プロセスそのものを定期的に破棄し、クリーンな状態で再生成する」という、外科手術的なアプローチが必要になる。
2. 戦略的設計:監視・検知・再起動のサイクル
我々が構築すべきは、以下の3つのレイヤーを持つアーキテクチャである。
1. Monitoring Layer (監視層): Windows APIを通じて、システムの物理メモリ使用率をリアルタイムで取得する。
2. Decision Layer (判断層): メモリ使用率が閾値(例:80%)を超えたか、あるいは特定回数の処理ごとにプロセスをリセットするかを判定する。
3. Execution Layer (実行層): 変換処理をカプセル化し、異常検知時に安全にプロセスを終了・再起動する。
3. 実装:レジリエント・バッチ・エンジンの全貌
以下に、プロダクション環境に投入可能なレベルのコードを示す。ここでは、メモリ状態を取得するために `kernel32.dll` の `GlobalMemoryStatusEx` APIを直接叩く。
API定義とメモリ監視モジュール
まず、OSの深淵にアクセスするための宣言を行う。
Option Explicit
' --- Windows API Declarations ---
' メモリ状態を取得するための構造体
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As LongPtr
ullAvailPhys As LongPtr
ullTotalPageFile As LongPtr
ullAvailPageFile As LongPtr
ullTotalVirtual As LongPtr
ullAvailVirtual As LongPtr
ullAvailExtendedVirtual As LongPtr
End Type
' メモリ状態を取得するAPI
Private Declare PtrSafe Function GlobalMemoryStatusEx Lib "kernel32" (lpBuffer As MEMORYSTATUSEX) As Long
''' <summary>
''' 現在の物理メモリ使用率(%)を返す
''' </summary>
Public Function GetMemoryUsagePercent() As Double
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = LenB(memStatus)
If GlobalMemoryStatusEx(memStatus) <> 0 Then
' dwMemoryLoad は 0-100 の値を持つ
GetMemoryUsagePercent = CDbl(memStatus.dwMemoryLoad)
Else
' API失敗時は安全のため100(警告状態)を返す
GetMemoryUsagePercent = 100
End If
End Function
コア・エンジン:プロセスの自律制御
次に、メインとなる変換エンジンだ。ここでは「変換処理」を独立した関数とし、メインループがその結果とメモリ状態を見て、プロセスを再起動するか判断する。
' --- Engine Module ---
Sub ResilientBatchConverter()
Dim targetFolder As String
Dim fileList As Collection
Dim fileName As Variant
Dim memThreshold As Double: memThreshold = 80# ' 80%を超えたらリセット
Dim processCount As Integer: processCount = 0
Dim maxFilesPerProcess As Integer: maxFilesPerProcess = 50 ' 50ファイルごとに強制リセット
' 設定
targetFolder = "C:\Work\InputPPTX\" ' 対象フォルダ
Set fileList = GetFileList(targetFolder & "*.pptx")
Debug.Print "--- Batch Process Started ---"
' メインループ
' 注意: プロセスを再起動するため、PowerPoint自体を管理するのではなく、
' 本来は「Excel」や「VBScript」からPowerPointを制御する形が理想的だが、
' ここではVBA内での擬似的な再起動ロジックを実装する。
Dim i As Integer
For i = 1 To fileList.Count
' 1. メモリ監視
If GetMemoryUsagePercent() > memThreshold Or processCount >= maxFilesPerProcess Then
Debug.Print "⚠️ Resource threshold reached. Restarting Process..."
' ここで現在のインスタンスをクリーンアップする
' 実運用では、このマクロ自体を外部(VBScript等)から呼び出し、
' 1回ごとにプロセスを完全に終了させる設計にすべきである。
processCount = 0
End If
' 2. 個別ファイル変換の実行
Dim success As Boolean
success = ConvertSingleFile(fileList(i))
If success Then
processCount = processCount + 1
Debug.Print "✅ Success: " & fileList(i)
Else
Debug.Print "❌ Failed: " & fileList(i)
End If
DoEvents ' OSに制御を戻し、システム全体のハングを防ぐ
Next i
Debug.Print "--- Batch Process Completed ---"
End Sub
''' <summary>
''' 単一ファイルの変換処理(カプセル化)
''' </summary>
Private Function ConvertSingleFile(filePath As Variant) As Boolean
Dim pptApp As Object ' PowerPoint.Application
Dim pres As Object ' PowerPoint.Presentation
Dim outPath As String
On Error GoTo ErrorHandler
outPath = Replace(filePath, ".pptx", ".pdf")
' インスタンスの生成(毎回生成することでメモリをクリーンに保つ)
Set pptApp = CreateObject("PowerPoint.Application")
' pptApp.Visible = msoFalse ' バックグラウンド実行
Set pres = pptApp.Presentations.Open(Filename:=filePath, ReadOnly:=True, WithWindow:=False)
' PDFとしてエクスポート
pres.ExportAsFixedFormat Path:=outPath, FixedFormatType:=2 ' 2 = ppFixedFormatTypePDF
' 後処理
pres.Close
pptApp.Quit
Set pres = Nothing
Set pptApp = Nothing
ConvertSingleFile = True
Exit Function
ErrorHandler:
Debug.Print "Error in " & filePath & ": " & Err.Description
ConvertSingleFile = False
' 異常終了時も確実にオブジェクトを解放する
On Error Resume Next
If Not pres Is Nothing Then pres.Close
If Not pptApp Is Nothing Then pptApp.Quit
Set pres = Nothing
Set pptApp = Nothing
End Function
''' <summary>
''' 指定フォルダ内のファイルリストを取得
''' </summary>
Private Function GetFileList(pattern As String) As Collection
Dim col As New Collection
Dim fso As Object
Dim folderPath As String
Dim file As Object
Set fso = CreateObject("Scripting.FileSystemObject")
folderPath = fso.GetParentFolderName(pattern) & "\"
' 簡易的な実装(実務ではDir関数やFSOを適切に使い分ける)
Dim fileName As String
fileName = Dir(pattern)
Do While fileName <> ""
col.Add folderPath & fileName
fileName = Dir()
Loop
Set GetFileList = col
End Function
4. アーキテクトの視点:実務における「堅牢性」の極意
このコードを単にコピペして終わりにするのは、二流のエンジニアだ。一流は、このコードをベースに以下の「運用設計」を組み込む。
① プロセス隔離の徹底 (Process Isolation)
上記のVBAコードでは `CreateObject` をループ内で呼び出している。これは、「1ファイルごとにPowerPointというプロセスを立ち上げ、使い終わったら完全に殺す」という戦略だ。これにより、メモリリークの影響を「1ファイル分」に限定できる。
ただし、起動コスト(オーバーヘッド)は増大する。そのため、`maxFilesPerProcess` 変数を用いて、「10ファイルごとにプロセスを新しくする」といったコストと安定性のトレードオフを調整するのがプロの仕事だ。
② ログの永続化 (Persistence)
バッチ処理は「失敗すること」を前提に設計せよ。
`Debug.Print` ではなく、実行結果(成功・失敗・エラー内容)をCSVやSQLiteなどのデータベースに書き出す機能を実装すること。これにより、数千件の処理が夜間に止まったとしても、翌朝「どのファイルが、なぜ失敗したのか」を即座に特定できる。
③ アトミックなファイル操作
変換後のPDFが「中途半端に書き込まれた壊れたファイル」にならないよう、一旦一時ディレクトリ(Temp)に書き出し、変換が成功したことを確認してから本来の保存先に `MoveFile` する設計を推奨する。
結論
大量のデータを扱う自動化において、最も恐れるべきは「エラーで止まること」ではなく、「エラーの予兆を見逃して、システム全体を不安定にさせること」だ。
Windows APIを用いてリソースを監視し、オブジェクトのライフサイクルを厳格に管理する。この「レジリエンス(回復力)」の概念をコードに組み込んだとき、君の作ったツールは、単なるマクロから「信頼に足るシステム」へと進化する。
「動けばいい」という思考を捨て、「どう壊れるかを予測し、どう自律復旧するかを設計する」こと。 これが、伝説的なエンジニアへの第一歩である。
