【テクニカル・上級編】【上級プロ向け】COMオブジェクトの参照リークを完全撲滅!巨大なCDRファイルを安全に連続バッチ処理 – CorelDRAW VBA解析バイブル

スポンサーリンク

CorelDRAW VBAを掌握する:数千ファイルのバッチ処理における「メモリリーク完全撲滅」の極意

CorelDRAWのVBA開発において、小規模なスクリプトを書くことと、数千単位のファイルを連続バッチ処理することは、別次元の難易度を持つ。多くのエンジニアが「なぜか300枚目で落ちる」「メモリ使用量が右肩上がりで止まらない」という壁に突き当たる。

これはVBAの仕様と、CorelDRAWという巨大なCOMサーバーの生存戦略が噛み合っていないために起こる「必然」だ。本稿では、VBAのガベージコレクション(GC)に頼らず、システムを完走させるための「オブジェクトのライフサイクル制御」の神髄を伝授する。

1. なぜVBAの `Set obj = Nothing` だけでは不十分なのか

VBAは、COMオブジェクトの参照カウントを管理しているが、CorelDRAWのCOMサーバーは非常に複雑な階層構造を持っている。特に `Document` や `Layer`、`ShapeRange` をループ内で生成・破棄する際、VBAの内部的な参照カウンターが同期ズレを起こし、メモリ上に「ゾンビオブジェクト」が蓄積される。

陥りがちなアンチパターン

‘ 危険なループ処理の典型
For Each f In files
Set doc = Application.Open(f)
‘ 何らかの処理
doc.SaveAs …
doc.Close
‘ Set doc = Nothing をしても、メモリ解放のタイミングはランタイム任せ
Next

このコードでは、`Application` オブジェクトが保持する参照キャッシュがクリアされず、メモリリークが加速する。

2. 究極のメモリ管理:強制解放とCOMスタックのクリア

安定したバッチ処理を実現するための「3つの絶対鉄則」を提示する。

鉄則一:子オブジェクトの連鎖を断ち切る

`ShapeRange` や `Selection` をループ内で生成すると、それらはメモリの深い位置に居座る。これらは必ずスコープの終了時、あるいはループの反復ごとに「手動で無効化」しなくてはならない。

鉄則二:GCの発生を待たない

VBAでは `DoEvents` を挟むことで、Windowsメッセージキューを処理し、OSレベルでのリソース解放を促すことができる。これは泥臭いが、長期間稼働するシステムでは必須のテクニックだ。

鉄則三:Windows APIによるメモリ強制回収

VBAの `Set = Nothing` はあくまで「参照カウントを減らす」だけだ。空きメモリを物理的に回収するには、Windows APIの `SetProcessWorkingSetSize` を呼び出し、プロセスに割り当てられたワーキングセットをトリミングする必要がある。

3. 実装:メモリを制御する「完全バッチ処理エンジン」の雛形

以下は、数千ファイルの連続処理を想定した堅牢なコードの実装例である。

‘ Windows API: メモリの強制解放用
If VBA7 Then
Private Declare PtrSafe Function SetProcessWorkingSetSize Lib “kernel32” (ByVal hProcess As LongPtr, ByVal dwMinimumWorkingSetSize As LongPtr, ByVal dwMaximumWorkingSetSize As LongPtr) As Long
Private Declare PtrSafe Function GetCurrentProcess Lib “kernel32” () As LongPtr
Else
Private Declare Function SetProcessWorkingSetSize Lib “kernel32” (ByVal hProcess As Long, ByVal dwMinimumWorkingSetSize As Long, ByVal dwMaximumWorkingSetSize As Long) As Long
Private Declare Function GetCurrentProcess Lib “kernel32” () As Long
End If

Public Sub RobustBatchProcess(fileList As Collection)
Dim doc As Document
Dim i As Long

For i = 1 To fileList.Count
‘ 1. ファイルを開く
Set doc = Application.Open(fileList(i))

‘ 2. 業務処理を実行
Call ProcessDocument(doc)

‘ 3. 明示的なクローズ
doc.Close
Set doc = Nothing

‘ 4. インターフェースの更新とメモリ解放
DoEvents
Call ForceMemoryCleanup

‘ ログ出力など…
Next i
End Sub

Private Sub ForceMemoryCleanup()
‘ メモリのワーキングセットを最小化してOSに返還させる
Dim hProc As LongPtr
hProc = GetCurrentProcess()
Call SetProcessWorkingSetSize(hProc, -1, -1)
End Sub

4. チーフアーキテクトからの助言

私がこれまで見てきた「数千枚規模のバッチ処理」における最大の敵は、「オブジェクトの再利用」と「例外ハンドリングの欠如」である。

1. オブジェクトの再利用を避ける: `Application.ActiveDocument` を頼りにしないこと。必ず `Open` メソッドの戻り値として取得した `Document` オブジェクトを厳格に管理する。
2. COM例外をトラップする: `On Error Resume Next` を乱用してはならない。CorelDRAWが特定のファイルでハングした際、VBA側でタイムアウトを検知し、タスクキル&プロセス再起動を行う「ウォッチドッグ・パターン」を実装すべきだ。
3. レガシー環境の保守: 古いCorelDRAWバージョンでは、`Document.Close` 後にオブジェクトが完全に消滅しないケースが多々ある。その場合は、`Sleep` APIを使い、クローズ後に数ミリ秒の待機時間を設けるだけで、劇的に安定性が向上する。

結論

VBAは「書き捨てのスクリプト」ではなく、適切に設計すれば「堅牢なバッチエンジン」になり得る。
メモリをOSに返還し、COMオブジェクトを静かに葬る。この規律を守れる者だけが、終わりのない大量処理を完遂できるのだ。

プロフェッショナルとして、コードの美しさよりも「システムの継続的な生存」に全力を注いでほしい。それが、この過酷な現場を生き抜くための唯一の道である。

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