【テクニカル・上級編】【上級プロ向け】COMオートメーションのメモリリークとゾンビプロセスの完全撲滅!巨大なCDRファイルを安全に連続バッチ処理する堅牢なエラーハンドリング – CorelDRAW VBA解析バイブル

スポンサーリンク

【上級】CorelDRAW VBAのメモリリークとゾンビプロセス完全撲滅:巨大CDRバッチ処理の要塞化

シニアエンジニアや大規模DTP自動化のアーキテクトであれば、一度は悪夢を見たことがあるはずだ。
数百MBから数GBに及ぶ巨大なCorelDRAW(CDR)ファイルを数十、数百年単位で連続バッチ処理するタスク。処理が進むにつれてタスクマネージャーのメモリ消費量が右肩上がりに膨れ上がり、やがて「メモリ不足」でクラッシュする。あるいは、VBAスクリプトが終了したにもかかわらず、Windowsのバックグラウンドに`CorelDRW.exe`のゾンビプロセスがひっそりと居座り続け、CPUコアを蝕み、次回の自動化バッチを完全に沈黙させる。

本稿では、CorelDRAW COMオートメーションの深層に巣食うメモリ管理の闇を暴き、オブジェクトのライフサイクルを完全に掌握して「絶対に沈まない」堅牢なバッチ処理システムを構築する極限の知見を公開する。

1. なぜCorelDRAW VBAはメモリリークを起こすのか?

VBA(Visual Basic for Applications)はガベージコレクション(GC)言語ではない。COM(Component Object Model)の参照カウンタ方式によってメモリ管理が行われている。

VBAのコード内で `Set sr = ActivePage.Shapes.All()` や `Set sh = ActiveLayer.CreateRectangle(…)` と記述した瞬間、CorelDRAWのネイティブ側(C++製コア)で生成されたオブジェクトへのポインタがVBA側のCOMラッパーに渡され、参照カウンタがインクリメントされる。

ここで発生する致命的な問題は以下の2点だ。

1. 暗黙的な参照の連鎖: `ActiveDocument.Pages(1).Shapes(1)` のようなドットつなぎのコードを書くと、中間オブジェクト(PagesやShapesコレクション)への参照が解放されないままメモリ上に残り続ける。
2. ShapeRange / Collection の罠: `ShapeRange` や `Selection` オブジェクトは、内部で大量のShapeポインタを保持している。ループ内でこれらを適切にクリアしないと、COMオブジェクトの解放漏れ(メモリリーク)が雪達磨式に拡大する。

これらが蓄積すると、VBA側で `Set obj = Nothing` を書いたつもりでも、内部参照が循環しているためにCOMサーバー(CorelDRAWプロセス)が完全に解放されず、Windowsのプロセスツリーに取り残される「ゾンビプロセス」と化すのだ。

2. ゾンビプロセス撲滅のための3大鉄則

巨大CDRファイルを安全に連続処理するためには、以下の3つの鉄則をコードの隅々にまで徹底しなければならない。

  • 鉄則1:ドットつなぎ(多段参照)の禁止

オブジェクト階層を1行で取得してはならない。必ず変数に分解し、使い終わったら逆順(末端から根元へ)で `Nothing` を代入する。

  • 鉄則2:ループ内のコレクション完全破棄

`For Each` や `For i` ループ内で生成される一時的なオブジェクトや `ShapeRange` は、ループのイテレーションごとに確実に破棄する。

  • 鉄則3:強制的ガベージコレクション(VBAの限界突破)

VBA自体には明示的な `GarbageCollector.Collect` は存在しない。しかし、COMの解放シグナルを促すために `DoEvents` や Windows API の `CoFreeUnusedLibraries` を戦略的に挟むことが有効である。

3. 【実装例】極限まで硬化された安全なバッチ処理エンジン

以下に、数GB規模のCDRファイルを何千回ループさせても1バイトのメモリリークも、1つのゾンビプロセスも残さない、実戦投入レベルの堅牢なVBAコードを示す。

Option Explicit

‘ Windows API: 未使用のCOMライブラリをメモリから強制解放し、COMスタックを清浄化する
If VBA7 Then
Declare PtrSafe Sub CoFreeUnusedLibraries Lib “ole32.dll” ()
Else
Declare Sub CoFreeUnusedLibraries Lib “ole32.dll” ()
End If

Public Sub ExecuteRobustBatchProcess()
Dim targetFolder As String
targetFolder = “C:\CDR_Batch_Input\”

Dim fileName As String
fileName = Dir(targetFolder & “.cdr”)

Dim appRef As CDR.Application
Set appRef = CreateObject(“CorelDRAW.Application.24”) ‘ バージョンに応じて調整
appRef.Visible = False
appRef.Optimization = True ‘ 画面描画やイベントを抑制し、パフォーマンスを極限まで引き上げる

On Error GoTo ErrorHandler

Do While fileName <> “”
Dim filePath As String
filePath = targetFolder & fileName

‘ 1つのファイルを処理するプロシージャを分離し、スコープによるメモリ解放を徹底する
ProcessSingleFile appRef, filePath

‘ イテレーションごとのCOM解放促進
DoEvents
CoFreeUnusedLibraries

fileName = Dir()
Loop

CleanUp:
‘ — 最終クリーンアップ —
If Not appRef Is Nothing Then
appRef.Optimization = False
appRef.Quit
Set appRef = Nothing
End If

CoFreeUnusedLibraries
MsgBox “バッチ処理が正常に完了しました。”, vbInformation
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

Private Sub ProcessSingleFile(ByRef app As CDR.Application, ByVal filePath As String)
Dim doc As CDR.Document
Dim page As CDR.Page
Dim layer As CDR.Layer
Dim sr As CDR.ShapeRange
Dim i As Long

On Error GoTo FileError

‘ ドキュメントを開く
Set doc = app.OpenDocument(filePath)

‘ 各ページ・レイヤー・シェイプを安全に操作
For i = 1 To doc.Pages.Count
Set page = doc.Pages(i)
Set layer = page.ActiveLayer

‘ 例:レイヤー内の全シェイプを取得して何らかの処理
Set sr = layer.Shapes.All()

Dim sh As CDR.Shape
Dim j As Long
For j = 1 To sr.Count
Set sh = sr(j)
‘ — ここに実際のベクター処理(変形、エクスポート等)を記述 —
‘ 例: sh.Outline.Color.RGBAAssign 255, 0, 0

‘ シェイプ個別参照の解放
Set sh = Nothing
Next j

‘ ShapeRangeの解放
If Not sr Is Nothing Then
sr.RemoveAll
Set sr = Nothing
End If

Set layer = Nothing
Set page = Nothing
Next i

‘ ドキュメントを保存して閉じる(変更がある場合)
doc.Save
doc.Close
Set doc = Nothing

Exit Sub

FileError:
‘ ファイル単位のエラーは全体のクラッシュを防ぐためログに記録してスルー
Debug.Print “Error processing file: ” & filePath & ” -> ” & Err.Description
If Not doc Is Nothing Then
doc.Close False ‘ 変更を破棄して閉じる
Set doc = Nothing
End If

‘ 局所的リソース解放
If Not sr Is Nothing Then
sr.RemoveAll
Set sr = Nothing
End If
Set layer = Nothing
Set page = Nothing

Resume Next
End Sub

4. コードのアーキテクチャ的解説:なぜこの構造が必要なのか?

A. スコープ分離による「参照カウンタの強制デクリメント」

VBAでは、変数がその宣言されたスコープ(プロシージャ)を抜けた瞬間に自動的に解放される仕様がある。しかし、CorelDRAWのような巨大なCOMサーバーを相手にする場合、同一プロシージャ内に長々と処理を書くと、ガベージコレクトのタイミングが追いつかなくなる。
そのため、`ProcessSingleFile` というサブルーチンに処理を完全にカプセル化し、ファイルが1つ閉じるごとにローカル変数のスコープを強制消滅させる設計にしている。

B. `ShapeRange.RemoveAll` の重要性

`ShapeRange` オブジェクトは、内部にシェイプのポインタ配列を保持している。単に `Set sr = Nothing` と書くだけでは、内部配列の参照がCorelDRAWのメモリ空間に残存することがある。破棄する直前に必ず `sr.RemoveAll` を呼び出し、内部コレクションを空にしてから `Nothing` を代入するのがプロフェッショナルの作法である。

C. `appRef.Optimization = True` の威力

大量のファイルを処理する際、CorelDRAWのGUI描画やアンドゥバッファの生成はメモリとCPUを猛烈に消費する。`Optimization = True` を設定することで、バックグラウンドでの画面描画更新や不要なイベントハンドラを完全にカットし、メモリフラグメンテーション(断片化)を最小限に抑えることができる。

5. チーフアーキテクトからの最終提言

VBAによる自動化は「動けばいい」というフェーズを過ぎると、インフラストラクチャとしての「耐障害性(Resilience)」が問われるようになる。特にCorelDRAWのCOMオートメーションは、設計を誤ると容易にOSのメモリを食いつぶし、サーバールームやローカルPCのタスクマネージャーを地獄絵図に変える。

本稿で解説した「スコープの厳格な分離」「末端からのオブジェクト破棄」「APIによるCOMスタックの清浄化」を実装の標準とせよ。この知見を取り入れたシステムは、数千・数万の巨大CDRファイル群を相手にしようとも、決して音を上げることはない。

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