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

スポンサーリンク

【上級者向け】数千ファイルのPPTX連続PDF変換を完走させる!Windowsメモリ監視×PowerPoint自動再起動のレジリエント・アーキテクチャ

こんにちは。大規模なドキュメント自動化プロジェクトをいくつも渡り歩いてきたシニア・アーキテクトの私だ。

業務で数千ファイルにおよぶPowerPoint(PPTX)をPDFへ一括変換するタスクに直面したことはないだろうか?
「夜間にバッチを回し、朝出社したらフリーズしていた」「途中でメモリが枯渇し、ExcelやPowerPointごとOSが巻き込まれて落ちた」。そんな悪夢のような経験を持つ開発者は少なくないはずだ。

なぜ、PowerPointの大量変換はこれほどまでに不安定なのか。そして、真にプロフェッショナルなエンジニアはこの課題にどう立ち向かうべきか。今回は、Windowsのメモリ(RAM)をAPI経由で常時監視し、枯渇の兆候を検知したら安全にプロセスをリフレッシュして処理を完走させる「レジリエント・バッチマクロ」の全貌を授けよう。

—

1. なぜ素直なVBAループでは数千ファイルの変換に耐えられないのか?

多くのプログラマブルでないエンジニアは、次のような単純なコードを書く。

‘ 【アンチパターン】絶対に真似してはいけないコード
For Each f In files
Set pptApp = CreateObject(“PowerPoint.Application”)
Set pres = pptApp.Presentations.Open(f.Path)
pres.SaveAs targetPath, ppSaveAsPDF
pres.Close
pptApp.Quit
Next f

一見、毎回プロセスを閉じているので安全に見えるかもしれない。しかし、COMオブジェクトの生成と破棄のオーバーヘッドは凄まじく、何よりPowerPointのOLE/COM実装には根深いメモリリークの傾向がある。特に複雑なシェイプ、埋め込みフォント、高解像度画像を大量に含むスライドを処理すると、単一プロセスの生存期間が長くなるにつれて、OSのコミットチャージがジワジワと上昇し、やがて `Out of Memory` エラー、あるいは沈黙のフリーズを引き起こす。

では、プロセスを1ファイルごとに起動・終了させればよいか? それではパフォーマンスが極端に低下し、スループットが実用に耐えなくなる。

導き出すべきアーキテクチャ:動的リフレッシュ(JITプロセス・ローテーション)

私たちが目指すべきは、「一定数の処理、またはメモリ使用量の閾値を超えた段階で、安全に現在のPowerPointインスタンスを廃棄(Quit)し、クリーンな状態で再起動してバッチを継続する」というレジリエント(回復力のある)な設計だ。

さらに、VBA単体ではWindowsの正確な空きメモリ量を測ることはできないため、Windows API(Kernel32.dll)を叩いて物理メモリの逼迫度をライブで監視する。

—

2. プロダクションコード:レジリエント・PDF変換エンジン

以下のコードは、エラーハンドリング、メモリ監視API、およびプロセス・ローテーションのロジックを完璧に統合したプロダクションコードである。そのままコピペして利用可能だ。

標準モジュール (`modResilientConverter.vba`)

Option Explicit

‘ ==============================================================================
‘ Windows API Declarations (Memory Status)
‘ ==============================================================================
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As Currency
ullAvailPhys As Currency
ullTotalPageFile As Currency
ullAvailPageFile As Currency
ullTotalVirtual As Currency
ullAvailVirtual As Currency
ullAvailExtendedVirtual As Currency
End Type

If VBA7 Then
Private Declare PtrSafe Sub GlobalMemoryStatusEx Lib “kernel32” (ByRef lpBuffer As MEMORYSTATUSEX)
Else
Private Declare Sub GlobalMemoryStatusEx Lib “kernel32” (ByRef lpBuffer As MEMORYSTATUSEX)
End If

‘ ==============================================================================
‘ Constants & Thresholds
‘ ==============================================================================
Private Const MEMORY_LOAD_THRESHOLD As Long = 85 ‘ 物理メモリ使用率が85%を超えたら退避
Private Const BATCH_CYCLE_LIMIT As Long = 50 ‘ 50ファイル処理ごとに強制リフレッシュ

Public Sub ExecuteResilientPdfBatch()
Dim sourceDir As String
Dim targetDir As String
Dim fso As Object
Dim folder As Object
Dim file As Object

‘ フォルダパスの設定(環境に合わせて変更してください)
sourceDir = “C:\PresentationSource\”
targetDir = “C:\PresentationOutput\”

Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(sourceDir) Or Not fso.FolderExists(targetDir) then
MsgBox “指定されたディレクトリが存在しません。”, vbCritical
Exit Sub
End If

Set folder = fso.GetFolder(sourceDir)

Dim pptApp As Object
Dim processedCount As Long
processedCount = 0

‘ 初期インスタンスの生成
Set pptApp = CreatePowerPointInstance()

Dim startTime As Double
startTime = Timer

On Error GoTo ErrorHandler

For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Name)) = “pptx” Or LCase(fso.GetExtensionName(file.Name)) = “ppt” Then

‘ — 【極限の知見】プロセス・ローテーション判定 —
If NeedsProcessRefresh(processedCount) Then
Debug.Print “–> メモリ負荷または処理上限に到達。PowerPointプロセスを安全にリフレッシュします。”
SafeQuitPowerPoint pptApp
Set pptApp = CreatePowerPointInstance()
processedCount = 0
End If

‘ 変換実行
Dim targetPdfPath As String
targetPdfPath = targetDir & fso.GetBaseName(file.Name) & “.pdf”

ConvertSinglePresentation pptApp, file.Path, targetPdfPath
processedCount = processedCount + 1

Debug.Print “[” & processedCount & “] 変換成功: ” & file.Name
End If
Next file

‘ クリーンアップ
SafeQuitPowerPoint pptApp
MsgBox “全ファイルのPDF変換が正常に完了しました。\n処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, vbInformation
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
On Error Resume Next
SafeQuitPowerPoint pptApp
End Sub

‘ ==============================================================================
‘ Core Helper Functions
‘ ==============================================================================

Private Function CreatePowerPointInstance() As Object
Dim app As Object
Set app = CreateObject(“PowerPoint.Application”)
‘ バックグラウンドで極力リソースを食わない設定
app.Visible = msoTrue ‘ Falseにすると一部のトランジションやエクスポートでCOMエラーになるためTrue推奨
Set CreatePowerPointInstance = app
End Function

Private Sub SafeQuitPowerPoint(ByRef app As Object)
On Error Resume Next
If Not app Is Nothing Then
app.Quit
End If
Set app = Nothing
‘ ガベージコレクションとCOMプロセスの完全消滅を促す
DoEvents
End Sub

Private Function NeedsProcessRefresh(ByVal currentCount As Long) As Boolean
‘ 1. 処理ファイル数による制限チェック
If currentCount >= BATCH_CYCLE_LIMIT Then
NeedsProcessRefresh = True
Exit Function
End If

‘ 2. Windows APIによる物理メモリ使用率(%)の常時監視
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = LenB(memStatus)
GlobalMemoryStatusEx memStatus

If memStatus.dwMemoryLoad >= MEMORY_LOAD_THRESHOLD Then
Debug.Print “WARNING: メモリ使用率が閾値を超過しました (” & memStatus.dwMemoryLoad & “%)”
NeedsProcessRefresh = True
Exit Function
End If

NeedsProcessRefresh = False
End Function

Private Sub ConvertSinglePresentation(ByRef app As Object, ByVal srcPath As String, ByVal destPath As String)
Dim pres As Object
Const ppSaveAsPDF = 32

‘ 読み取り専用かつウィンドウ非表示で開くことで描画負荷を排除
Set pres = app.Presentations.Open(srcPath, ReadOnly:=True, WithWindow:=msoFalse)

‘ PDF保存 (ppSaveAsPDF = 32)
pres.SaveAs destPath, ppSaveAsPDF

pres.Close
Set pres = Nothing
End Sub

—

3. アーキテクチャの解説:なぜこの設計が「落ちない」のか?

① `GlobalMemoryStatusEx` によるOSレベルの監視

VBAの組み込み関数ではメモリ状況を知る術はない。ここでは `kernel32.dll` の `GlobalMemoryStatusEx` を呼び出し、OS全体で現在どれだけのメモリが圧迫されているか(`dwMemoryLoad`)をパーセンテージで直接取得している。これにより、メモリリークが起きているプロセスが暴走してOSをクラッシュさせる前に、自発的に身を引く(プロセスを再起動する)ことが可能になる。

② あえて `Visible = msoTrue` にする理由

「バックグラウンドで処理するなら `Visible = msoFalse` が正義」と思われがちだが、PowerPointのCOMオートメーションにおいて、完全なヘッドレス(非表示)モードは、一部のフォントレンダリングやSmartArtの描画においてハングアップを引き起こすリスクが歴史的に高い。実務では `Visible = msoTrue` のまま、最小化あるいは画面の背後に配置して安全性を担保するのがプロの選択だ。

③ 適切なオブジェクトのスコープと `DoEvents`

ループ内で生成・破棄されるオブジェクトがメモリ上に残留するのを防ぐため、`Set pres = Nothing` を徹底し、プロセス再起動の際には `DoEvents` を挟んでOSのCOMコンテキスト切り替えを確実に待機させている。

—

総括

業務自動化における真の敵は、コードの書き方そのものではなく、「長期間稼働するシステムにおけるリソースの有限性」だ。

今回紹介した「メモリ監視型レジリエント・バッチマクロ」の考え方は、PowerPointだけでなく、ExcelやWord、あるいは外部APIを大量に叩くようなあらゆるVBAソリューションに応用できる。
あなたの手元の自動化スクリプトにこの堅牢な守りを組み込み、深夜のシステム停止アラートから解放されるエンジニアリングを手に入れてほしい。

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