【テクニカル・上級編】【上級者】大量のPPTXをPDF変換する際、Windowsの空きメモリ(RAM)をAPI経由で常時監視し、枯渇兆候を検知したらプロセスを安全に再起動して処理を継続するレジリエント・バッチマクロ – PowerPoint VBA解析バイブル

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

破綻する自動化を、堅牢なシステムへ。メモリ枯渇を予見し、自律的にプロセスを再起動する「レジリエント・バッチエンジン」の構築

大量のPowerPointファイルをPDFへと一括変換する。この一見単純なタスクが、シニアエンジニアを絶望の淵に叩き落とすことがある。

数百、時には数千のファイルをループ処理させると、ある地点で突如として発生する `Out of Memory` エラー、あるいは応答不能(Not Responding)に陥るアプリケーション。これは単なるプログラムのバグではない。COMオブジェクトの参照カウント管理の不完全さ、PowerPointアプリケーション本体が抱えるメモリリーク、そしてWindowsのヒープ領域の断片化という、OSとアプリケーションの境界線上で発生する「宿命的な問題」である。

本稿では、単に「ファイルを変換する」だけのコードは一切書かない。メモリ使用率をWindows API経由でリアルタイム監視し、枯渇の兆候を検知した瞬間に、自身を安全に「リサイクル(再起動)」して処理を継続する、極めてレジリエント(回復力のある)なアーキテクチャを提示する。

1. 敗北のメカニズム:なぜメモリは枯渇するのか

PowerPointの自動操作(Automation)において、`Set pptApp = Nothing` を実行しただけでは、メモリは解放されない。COM(Component Object Model)の参照カウントが正しくゼロにならず、バックグラウンドで `POWERPNT.EXE` がメモリを食いつぶし続ける現象は、レガシーな資産を扱う現場では日常茶飯事だ。

また、大規模なスライドや高解像度の画像を含むファイルを連続して処理すると、アプリケーション内の内部ヒープが断片化し、物理的な空きメモリがあっても、巨大な連続領域を確保できずにエラーを吐く。

我々が実装すべきは、「限界まで走り続け、限界が来る前に自ら死に、新しく生まれ変わる」という、生物学的な生存戦略に近い制御ロジックである。

2. Windows APIによるメモリ状態の極限監視

まず、VBAの標準機能では不可能な「システム全体の物理メモリ使用量」を取得するため、`GlobalMemoryStatusEx` APIを呼び出す。`GetSystemInfo` などの古いAPIではなく、64bit環境や大容量メモリを正確に扱うために `MEMORYSTATUSEX` 構造体を用いるのが鉄則だ。

Option Explicit

' --- Windows API Declarations ---

If VBA7 Then

' 64bit/32bit共通のAPI宣言 Private Type MEMORYSTATUSEX dwLength As Long dwMemoryLoad As Long ullTotalPhys As LongLong ullAvailPhys As LongLong ullTotalPageFile As LongLong ullAvailPageFile As LongLong ullTotalVirtual As LongLong ullAvailVirtual As LongLong ullAvailExtendedVirtual As LongLong End Type Private Declare PtrSafe Function GlobalMemoryStatusEx Lib "kernel32" _ Alias "GlobalMemoryStatusEx" (lpBuffer As MEMORYSTATUSEX) As Long

Else

' レガシー32bit環境用 Private Type MEMORYSTATUSEX dwLength As Long dwMemoryLoad As Long ullTotalPhys As Long ullAvailPhys As Long ullTotalPageFile As Long ullAvailPageFile As Long ullTotalVirtual As Long ullAvailVirtual As Long ullAvailExtendedVirtual As Long End Type Private Declare Function GlobalMemoryStatusEx Lib "kernel32" _ Alias "GlobalMemoryStatusEx" (lpBuffer As MEMORYSTATUSEX) As Long

End If

''' <summary> ''' 現在の物理メモリ使用率(%)を取得する ''' </summary> Function GetMemoryLoad() As Long Dim memStatus As MEMORYSTATUSEX memStatus.dwLength = LenB(memStatus) If GlobalMemoryStatusEx(memStatus) <> 0 Then GetMemoryLoad = memStatus.dwMemoryLoad Else ' API呼び出し失敗時は安全側に倒して高負荷を返す GetMemoryLoad = 100 End If End Function

3. レジリエント・エンジンの設計思想

エンジンの核となるのは、以下の3要素からなる制御ループだ。

1. 監視 (Monitor): 次のファイル処理に移る直前に、`GetMemoryLoad` を呼び出す。
2. 判定 (Judge): メモリ負荷が閾値(例: 80%)を超えていれば、現在のプロセスを「汚染されたもの」と見なし、中断を決断する。
3. 再起動 (Recycle): PowerPointインスタンスを完全に終了させ、オブジェクトを破棄し、再度新しいインスタンスを立ち上げて、未処理のファイルから再開する。

実装例:高負荷対応PDF変換エンジン

このコードは、単一のVBAモジュール内で完結させるのではなく、「どのファイルまで終わったか」という状態(State)を外部ファイルやシートに保持することを前提としている。

Option Explicit

' 設定定数
Private Const MEMORY_THRESHOLD As Long = 80 ' メモリ使用率80%でリサイクルを実行
Private Const STATE_FILE As String = "C:\Temp\conversion_state.txt" ' 進捗管理用

Sub ResilientPDFConverter()
    Dim pptApp As Object ' PowerPoint.Application
    Dim filePath As Variant
    Dim fileList As Collection
    Dim currentIndex As Long
    Dim startFileIndex As Long
    
    ' 1. 進捗の読み込み
    startFileIndex = GetLastProcessedIndex()
    Set fileList = LoadFileList("C:\Input\PPTX_Folder\") ' 対象ファイルリスト取得
    
    ' 2. メインループ
    For currentIndex = startFileIndex To fileList.Count
        
        ' --- メモリ監視フェーズ ---
        If GetMemoryLoad() > MEMORY_THRESHOLD Then
            Debug.Print "Critical: Memory threshold exceeded. Recycling process..."
            Exit For ' ループを抜け、再起動シーケンスへ
        End If
        
        ' --- 処理フェーズ ---
        On Error GoTo ErrorHandler
        Set pptApp = CreateObject("PowerPoint.Application")
        
        ' 変換処理の実行
        ConvertSingleFile pptApp, fileList(currentIndex)
        
        ' 進捗の保存
        SaveProgress currentIndex
        
        ' オブジェクトの明示的解放
        pptApp.Quit
        Set pptApp = Nothing
        
        ' OSに制御を戻し、メモリのクリーンアップを促す
        DoEvents
        
        ' ループの継続
        GoTo NextIteration

ErrorHandler:
        Debug.Print "Error at index " & currentIndex & ": " & Err.Description
        If Not pptApp Is Nothing Then pptApp.Quit
        Set pptApp = Nothing
        ' エラー時は次へ進むか、停止するかは要件次第
        Resume NextIteration

NextIteration:
    Next currentIndex

    ' 全工程完了
    MsgBox "Batch Processing Completed.", vbInformation
    ClearProgress
End Sub

''' <summary>
''' 個別のファイル変換ロジック
''' </summary>
Private Sub ConvertSingleFile(ByRef pptApp As Object, ByVal targetPath As String)
    Dim pres As Object
    Dim savePath As String
    
    savePath = Replace(targetPath, ".pptx", ".pdf")
    
    ' ファイルを開く(バックグラウンドで動作させるためVisibleはFalse)
    Set pres = pptApp.Presentations.Open(targetPath, WithWindow:=msoFalse)
    
    ' PDFとして保存
    pres.SaveAs savePath, 32 ' 32 = ppSaveAsPDF
    
    ' 参照の解放
    pres.Close
    Set pres = Nothing
End Sub

' --- 以下、進捗管理・ファイルリスト取得用の補助関数(実装省略) ---

4. アーキテクトの視点:さらなる極限への最適化

このエンジンを実運用に投入する際、以下の3点を考慮せよ。

I. プロセス・リサイクル・パターンの外部化

上記のコードはVBA内部でリサイクルを試みるが、VBA自体が動いているPowerPointプロセスがクラッシュした場合、マクロは停止する。真にプロフェッショナルな設計を目指すなら、「制御用のVBScriptまたはVB.NETアプリケーション」を別途用意せよ。
制御プログラムがPowerPointを起動し、メモリを監視し、閾値を超えたら `taskkill /F /IM powerpnt.exe` でプロセスを強制終了させ、再度起動するという構成にすることで、不測の事態に対する耐性は極限まで高まる。

II. 非同期的な状態管理

`SaveProgress` 関数は、単なるカウンタの保存ではない。万が一のシステムダウンに備え、書き込み完了を保証する「アトミックな書き込み(一度別名で保存してからリネームする手法)」を推奨する。

III. COMオブジェクトの「掃除」

`Set obj = Nothing` はあくまで「参照を減らす」だけであり、メモリ解放を強制するものではない。大量のファイルを回す際は、`DoEvents` を適切に挟むことで、Windowsのメッセージキューを処理させ、OSによるメモリ管理の介入機会を確保することが、パフォーマンスの安定に直結する。

結論

自動化エンジニアの仕事は、正常なケースを記述することではない。「システムが崩壊するプロセスを、いかに優雅に制御するか」にこそ、その真価が問われる。

メモリ監視による自律的なリサイクル・メカニズムを実装したとき、君の書いたコードは単なるスクリプトから、信頼に足る「システム」へと昇華する。

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