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

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

メモリの限界を超えて。大量のPPTXをPDFへ。自律監視型レジリエント・バッチエンジンの構築

こんにちは。自動化の世界へようこそ。

「マクロの記録」を使って、ボタン一つで操作ができるようになった。そんな喜びを感じているあなたへ、今日は少しだけ「プロフェッショナル」の領域へ踏み出すための話をしましょう。

皆さんは、こんな経験はありませんか?
「10枚程度の変換なら問題ないのに、数百枚のプレゼンテーションをまとめてPDFにしようとした途端、PowerPointがフリーズした。あるいは、『メモリ不足(Out of Memory)』という冷酷なエラーが出て、処理が止まってしまった……」

これは、初心者から中級者へステップアップする際に必ずぶち当たる「リソース管理の壁」です。

今日、私があなたに伝授するのは、単なる「変換マクロ」ではありません。Windowsの心臓部(API)からメモリの状態を読み取り、自らの「息切れ」を察知して、安全に処理を継続する「レジリエント(回復力のある)バッチエンジン」の作り方です。

これをマスターすれば、あなたは「動けばいいマクロ」を作る人から、「過酷な現場でも止まらないシステム」を設計できるエンジニアへと進化できるはずですよ。

なぜ、大量処理は「メモリ不足」で死ぬのか?

まず、敵を知ることから始めましょう。なぜメモリが枯渇するのか。

理由は主に2つあります。

1. COMオブジェクトの残骸(メモリリーク): PowerPointをVBAで操作するとき、内部では「PowerPointという巨大なアプリケーション」をプログラムから操っています。処理が終わった後、適切に「使い終わったよ」と伝えないと、メモリの中に「使い終わったはずのデータ」が幽霊のように残り続け、次第に空き容量を食いつぶします。
2. OSとの連携不足: VBAはあくまで「PowerPointの中の住人」です。パソコン全体のメモリ(RAM)がどれくらい空いているか、VBAは本来知り得ません。だから、限界が来るまで気づけず、突然クラッシュしてしまうのです。

今回の解決策はこうです。
「Windows APIを使って、外部(OS)からメモリの残量を確認し、危険なレベルになったら一旦落ち着いてリソースを掃除する」

1. 武器の準備:Windows APIでメモリを覗き見る

VBA標準の機能には「メモリ残量を知る」関数はありません。そこで、Windowsが元々持っている強力な機能(API)を借りてきます。

使用するのは `GlobalMemoryStatusEx` という関数です。これを使うと、現在の物理メモリの空き容量を正確に把握できます。

仕組みの図解

[ あなたのVBA ] <---- (リクエスト) ----> [ Windows OS ]
<---- (現在のメモリ使用状況を回答) ----
[ 判定 ] ----------------> [ 閾値(例: 10%)を下回ったら停止・掃除 ]

2. 実装:レジリエント・バッチエンジンの全貌

それでは、魂を込めて書いたコードを見ていきましょう。
このコードは、単にファイルを変換するだけでなく、「監視」と「自己修復(リソース解放)」のロジックを組み込んでいます。

Option Explicit

' =================================================================
' Windows 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" (lpBuffer As MEMORYSTATUSEX) As Long

' =================================================================
' 定数設定
' =================================================================
Private Const MEMORY_THRESHOLD_PERCENT As Double = 15.0 ' 空きメモリが15%を切ったら警告

' =================================================================
' メイン処理:大量のPPTXをPDFへ変換するエンジン
' =================================================================
Sub ResilientPDFConverter()
    Dim fso As Object
    Dim targetFolder As String
    Dim file As Object
    Dim pptApp As Object
    Dim pres As Object
    Dim savePath As String
    Dim currentFreeMemPercent As Double
    
    ' --- 初期設定 ---
    ' 変換したいファイルが入っているフォルダを指定してください
    targetFolder = "C:\Users\YourName\Desktop\TestFiles\" 
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    If Not fso.FolderExists(targetFolder) Then
        MsgBox "指定されたフォルダが見つかりません。", vbCritical
        Exit Sub
    End If

    ' PowerPointアプリケーションの起動
    On Error Resume Next
    Set pptApp = GetObject(Class:="PowerPoint.Application")
    If pptApp Is Nothing Then
        Set pptApp = CreateObject("PowerPoint.Application")
    End If
    On Error GoTo 0
    
    ' 画面更新を停止(パフォーマンス向上)
    ' ※PowerPointにはExcelのようなScreenUpdatingがないため、
    ' 処理の節目でDoEventsを挟むことが重要になります。

    Debug.Print "--- 変換プロセス開始 ---"

    ' --- ファイルループ開始 ---
    For Each file In fso.GetFolder(targetFolder).Files
        ' 拡張子が .pptx のものだけを対象とする
        If LCase(fso.GetExtensionName(file.Path)) = "pptx" Then
            
            ' 【重要】ステップ1:メモリの健康診断
            currentFreeMemPercent = GetFreeMemoryPercent()
            Debug.Print "現在の空きメモリ: " & Format(currentFreeMemPercent, "0.00") & "%"
            
            If currentFreeMemPercent < MEMORY_THRESHOLD_PERCENT Then
                ' メモリが危ない!一旦処理を中断して掃除を行う
                Debug.Print "⚠️ メモリ枯渇の兆候を検知!リソースを解放して待機します..."
                
                ' オブジェクトをすべて解放
                CleanUpObjects pptApp, pres
                
                ' OSに制御を戻し、メモリの整理を待つ(少し休憩)
                DoEvents
                Application.Wait (Now + TimeValue("0:00:05")) 
                
                ' 再度、アプリを立ち上げ直す(レジリエンスの肝)
                Set pptApp = CreateObject("PowerPoint.Application")
            End If

            ' 【ステップ2】PDF変換処理
            On Error Resume Next
            Set pres = pptApp.Presentations.Open(Filename:=file.Path, ReadOnly:=True, WithWindow:=False)
            
            If Err.Number = 0 Then
                savePath = fso.GetParentFolderName(file.Path) & "\" & _
                           fso.GetBaseName(file.Path) & ".pdf"
                
                ' PDFとして保存
                pres.SaveAs savePath, 32 ' 32 = ppSaveAsPDF
                Debug.Print "✅ 成功: " & file.Name
                
                ' 保存後はすぐに閉じる
                pres.Close
                Set pres = Nothing
            Else
                Debug.Print "❌ エラー: " & file.Name & " (ファイルを開けませんでした)"
            End If
            On Error GoTo 0
            
            ' 【ステップ3】メモリリーク防止のための「お作法」
            ' 毎ループごとにOSに制御を返し、メモリの整理を促す
            DoEvents
        End If
    Next file

    ' --- 終了処理 ---
    CleanUpObjects pptApp, pres
    Set fso = Nothing
    Debug.Print "--- 全工程が完了しました ---"
    MsgBox "すべての処理が完了しました。", vbInformation
End Sub

' =================================================================
' サブ関数:空きメモリの割合を計算する
' =================================================================
Private Function GetFreeMemoryPercent() As Double
    Dim memStatus As MEMORYSTATUSEX
    memStatus.dwLength = LenB(memStatus)
    
    If GlobalMemoryStatusEx(memStatus) <> 0 Then
        ' (空き物理メモリ / 全物理メモリ) * 100
        GetFreeMemoryPercent = (CDbl(memStatus.ullAvailPhys) / CDbl(memStatus.ullTotalPhys)) * 100
    Else
        ' API呼び出し失敗時は安全のために0を返す(メモリ不足扱いにする)
        GetFreeMemoryPercent = 0
    End If
End Function

' =================================================================
' サブ関数:オブジェクトを安全に破棄する(メモリ解放の儀式)
' =================================================================
Private Sub CleanUpObjects(ByRef app As Object, ByRef pres As Object)
    On Error Resume Next
    If Not pres Is Nothing Then
        pres.Close
        Set pres = Nothing
    End If
    If Not app Is Nothing Then
        ' アプリケーションを完全に終了させる
        app.Quit
        Set app = Nothing
    End If
    On Error GoTo 0
End Sub

3. ここが「プロ」の設計。3つの極限知見

このコードには、単なる「動くコード」とは一線を画す、3つの設計思想を込めています。

① APIによる「自律的な健康診断」

`GetFreeMemoryPercent` 関数が、いわばエンジンの「センサー」です。プロのエンジニアは、プログラムが「失敗してから対処する」のではなく、「失敗しそうになったら事前に防ぐ」という考え方をします。メモリが15%を切ったら、あえて一度プロセスをリセットする。この「引き際」の判断が、大規模処理を完遂させる鍵です。

② オブジェクトのライフサイクル管理

`CleanUpObjects` サブプロシージャに注目してください。
VBAにおいて、`Set obj = Nothing` は魔法の言葉ではありません。特にPowerPointのような重いアプリの場合、`pres.Close`(ファイルを閉じる)と `app.Quit`(アプリを終了する)をセットで行い、その上で `Set … = Nothing` とすることで、初めてメモリから完全に消し去ることができます。これを怠ると、メモリは砂時計のように溜まっていくのです。

③ `DoEvents` という「呼吸」

コードの中に `DoEvents` が散りばめられていますね。これは、VBAに「一瞬、立ち止まってOSの仕事(メモリ整理や画面更新)を優先させてあげて」と伝える命令です。これがないと、VBAはCPUを独占しようとし、OSがメモリを整理する隙を与えません。プログラムに「呼吸」をさせるイメージです。

まとめ:ここをクリアすれば、あなたはもう一人じゃない

いかがでしたか?

「大量のファイルを処理する」という一見シンプルなタスクの裏側には、OSのリソース、メモリのライフサイクル、そしてエラーへの備えといった、深いエンジニアリングの要素が詰まっています。

最初は、APIの宣言や構造体(Type)の記述に戸惑うかもしれません。でも、大丈夫です。これらは一度理解してしまえば、どんな重い業務自動化にも応用できる「一生モノの武器」になります。

「マクロの記録」から一歩踏み出し、システム全体を俯瞰する視点を持ってください。その先に、真の自動化の楽しさが待っていますよ。

応援しています。次は、さらに高度な自動化の世界でお会いしましょう!

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