【上級】CorelDRAW VBAのメモリリークとゾンビプロセスを完全撲滅せよ!巨大CDRファイルを安全に連続バッチ処理する極限のメモリ管理術
開発現場のプロジェクトリーダーである私に、よくこんな悲鳴が届く。
> 「数百枚の巨大なCDRファイルをバッチ処理でPDFやIllustrator形式に変換していると、処理が進むにつれてPCのメモリがみるみる枯渇し、最終的にCorelDRAWがフリーズする」
> 「タスクマネージャーを見ると、VBAのマクロが終了しているはずなのに `CorelDRW.exe` がバックグラウンドでゾンビのように居座り続け、CPUを食いつぶしている」
もし貴殿がこのような壁にぶ当たっているなら、残念ながらこれまでのコードは「アマチュアの書き方」を脱しきれていない。
CorelDRAWのCOMオートメーションは強力だが、その裏側ではC++のネイティブメモリとVBAのガベージコレクションが複雑に絡み合っている。特に `Shape` や `ShapeRange`、さらには `Layer` や `Page` といったオブジェクトの参照を適切に解放(Destruct)してやらなければ、COMの参照カウント(Reference Count)が0にならず、メモリリークとゾンビプロセスの温床となる。
今回は、数万点のベクターデータを扱う過酷な現場を生き抜くために、「CorelDRAW VBAのメモリ管理の真実」と、「絶対に落ちない堅牢なバッチ処理アーキテクチャ」を伝授する。
—
1. なぜCorelDRAW VBAはメモリリークを起こすのか?
VBAには一応のガベージコレクション機能があるが、それは「VBA内部の変数」に対してのみ働く。COMオブジェクト(CorelDRAWが提供する外部コンポーネント)のメモリ管理は、開発者が明示的なライフサイクルをコードで担保しなければならない。
悪夢の「暗黙的参照の連鎖」
よくあるアンチパターンを見てみよう。
‘ 【絶対にしてはいけないアンチパターン】
Sub BadExample()
Dim doc As Document
Set doc = CorelDRAW.ActiveDocument
‘ この1行に隠された「恐ろしい罠」に気づけるか?
Dim s As Shape
For Each s in doc.Pages(1).Layers(1).Shapes
‘ 処理…
Next s
End Sub
このコードの何が問題か?
`doc.Pages(1).Layers(1).Shapes` というチェーンを書いた瞬間、VBAの裏側では複数のCOMオブジェクト(Document, Page, Layer, Shapesのコレクション、そして各Shape)が一時的に生成され、参照が保持される。
VBAの `For Each` ループを抜けた後、これらの一時オブジェクトの参照カウントが即座に解放される保証はない。これがループのたびに蓄積し、メモリリークを引き起こす主原因となる。
—
2. ゾンビプロセス撲滅の3大鉄則
巨大なCDRファイルを安全に連続処理するためには、以下の3つの鉄則をコードの隅々にまで行き渡らせる必要がある。
1. すべてのCOMオブジェクト変数は、使い終わったら即座に `Set xxx = Nothing` で解放する。
2. ループ変数の内側で動的なCOM参照を作らない。作る場合は必ずループ内で都度破棄する。
3. 予期せぬエラーが発生しても必ず解放処理を通す「構造化エラーハンドリング(`On Error GoTo` の徹底)」を実装する。
—
3. 【プロダクションコード】巨大CDR連続バッチ処理の完全版
それでは、実務でそのまま使える、極限まで堅牢性を高めたバッチ処理スクリプトを公開しよう。このコードは、指定フォルダ内のすべてのCDRファイルを読み込み、メモリリークやゾンビプロセスを完全に回避しながらPDFにエクスポートするものだ。
Option Explicit
‘ メインエントリーポイント
Sub BatchProcessCDRFiles()
Dim targetDir As String
Dim outputDir As String
Dim fileName As String
Dim cdrApp As CorelDRAW.Application
‘ 処理ディレクトリの設定(環境に合わせて変更してください)
targetDir = “C:\CDR_Source\”
outputDir = “C:\CDR_Output\”
‘ フォルダの存在確認
If Dir(targetDir, vbDirectory) = “” Then
MsgBox “入力ディレクトリが存在しません: ” & targetDir, vbCritical
Exit Sub
End If
‘ CorelDRAWのインスタンスを明示的に取得(新規起動ではなく、安全な制御のため)
‘ ※バックグラウンド実行時は Visible = False にすることも可能だが、
‘ 安定性を重視するなら Visible = True で最小化を推奨
Set cdrApp = CreateObject(“CorelDRAW.Application”)
cdrApp.Optimization = True ‘ 描画・再計算を停止し、パフォーマンスを極限まで引き上げる
cdrApp.EventsEnabled = False ‘ イベントを無効化し、予期せぬトリガーを防ぐ
On Error GoTo GlobalErrorHanlder
fileName = Dir(targetDir & “.cdr”)
Do While fileName <> “”
‘ 1ファイルごとの処理を完全にカプセル化(メモリリークの局所化)
ProcessSingleFile cdrApp, targetDir & fileName, outputDir & Replace(fileName, “.cdr”, “.pdf”)
fileName = Dir() ‘ 次のファイルへ
Loop
CleanUp:
‘ 最後にアプリケーションの状態を復元して安全に終了
If Not cdrApp Is Nothing Then
cdrApp.EventsEnabled = True
cdrApp.Optimization = False
cdrApp.Quit
Set cdrApp = Nothing
End If
MsgBox “すべてのバッチ処理が正常に完了しました。”, vbInformation
Exit Sub
GlobalErrorHanlder:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
‘ —————————————————————–
‘ 1ファイル分の処理(スコープを分離し、メモリリークを防ぐ)
‘ —————————————————————–
Private Sub ProcessSingleFile(ByRef app As CorelDRAW.Application, ByVal inputPath As String, ByVal outputPath As String)
Dim doc As CorelDRAW.Document
Dim sh As CorelDRAW.Shape
Dim sr As CorelDRAW.ShapeRange
On Error GoTo FileErrorHandlers
‘ ドキュメントを開く(UI非表示でメモリ効率を向上)
Set doc = app.OpenDocument(inputPath)
‘ 【例】ページ内の全シェイプに対して何らかの安全な最適化処理を行う場合
‘ ※ShapeRangeを使用する場合は必ず明示的に作成・解放する
Dim p As CorelDRAW.Page
Dim l As CorelDRAW.Layer
For Each p In doc.Pages
For Each l in p.Layers
‘ レイヤーがロックされている、または非表示の場合はスキップ
If Not l.Locked And l.Visible Then
Set sr = l.Shapes.All
If sr.Count > 0 Then
‘ 例:すべてのテキストフレームに対して特定の安全操作を行うなど
‘ ここではオブジェクトの走査・参照の切替テストのみ記述
Dim i As Long
For i = 1 To sr.Count
Set sh = sr(i)
‘ シェイプに対する何らかの処理…
Set sh = Nothing ‘ ループ変数ごとの確実な解放
5 Next i
End If
Set sr = Nothing ‘ コレクションの解放
End If
Set l = Nothing
Next l
Set p = Nothing
Next p
‘ PDFとしてエクスポート
Dim expParams As StructExportOptions
Set expParams = New StructExportOptions
expParams.Resolution = 300
doc.PublishToPDF outputPath, cdrPDFStandard
‘ ドキュメントを閉じる(変更を保存せずに閉じる場合は False)
doc.Close
Set doc = Nothing
Exit Sub
FileErrorHandlers:
‘ 個別ファイルの破損などでエラーが出ても、バッチ全体を止めずにログに残して次へ進む設計
Debug.Print “Error processing file: ” & inputPath & ” | Description: ” & Err.Description
‘ エラー時の確実なオブジェクト破棄
If Not doc Is Nothing Then
doc.Close False
Set doc = Nothing
End If
Set sh = Nothing
Set sr = Nothing
Set p = Nothing
Set l = Nothing
Resume Next
End Sub
—
4. プロジェクトリーダーが教える、コードの急所と設計思想
上記のコードには、プロフェッショナルであれば絶対に押さえておくべき「3つのキモ」が組み込まれている。
① `Optimization = True` と `EventsEnabled = False` の組み合わせ
巨大なCDRファイルをバッチ処理する際、CorelDRAWはファイルを開くたびに画面を描画し、UIイベントを処理しようとする。これがパフォーマンスを著しく低下させ、メモリの断片化を招く主因だ。処理の最初にこれらを無効化し、終了時に必ず復元する。この一手間で処理速度が数倍〜十数倍に跳ね上がる。
② 処理の「関数分離(スコープの限定)」
ファイルごとの処理を別プロシージャ(`ProcessSingleFile`)に切り出している点に注目してほしい。
VBAでは、プロシージャが終了した瞬間に、その内部で宣言されたローカル変数の参照カウントが強制的にクリアされる特性がある。これにより、ファイル単位でのガベージコレクションが確実に働き、次のファイルの処理へメモリを持ち越さない堅牢なアーキテクチャが完成する。
③ エラーハンドリングにおける「二重のセーフティネット」
ファイルが破損していたり、未知の不正なオブジェクトが含まれているCDRファイルに当たったとき、スクリプトが途中で止まってはバッチ処理の意味がない。
個別のファイル処理でエラーが発生した場合(`FileErrorHandlers`)、エラーをキャッチして `Resume Next` で次のファイルへ処理を継続しつつ、その瞬間に確保されていたCOM参照を確実に `Nothing` で潰す。この執念こそが、無人稼働する自動化システムの信頼性を担保する。
—
最後に:プロのエンジニアとしての誇り
「動けばいいや」で作られたVBAマクロは、開発者の手元を離れた瞬間に「時限爆弾」と化す。
CorelDRAW VBAを真に掌握するということは、APIのメソッドを知ることではなく、「WindowsのCOMアーキテクチャとメモリのライフサイクルを完全にコントロールする」ことに他ならない。
この知見をあなたのプロジェクトに導入し、バックグラウンドに残るゾンビプロセスを根絶やしにしてほしい。ビジネスの現場で真に信頼される自動化システムは、こうした細部への徹底的なこだわりからしか生まれないのだから。
