【上級プロ向け】COMオブジェクトの参照リークとゾンビプロセスの完全撲滅!巨大なCDRファイルを安全に連続バッチ処理する堅牢なエラーハンドリング
CorelDRAW VBAにおける自動化開発において、数件のファイル処理であれば「動けば正義」で済むかもしれない。しかし、対象が「数千件の巨大CDRファイルの連続バッチ変換」となれば話は別だ。
現場のエンジニアから「バッチの途中でCorelDRAWがフリーズする」「タスクマネージャーに `CorelDRW.exe` のゾンビプロセスが無限に増殖し、メモリが枯渇する」「200ファイル目あたりで突然 `Automation error` が発生して止まる」といった悲鳴交わりの相談をよく受ける。
原因は明白だ。VBA特有のCOMオブジェクトのライフサイクル管理の甘さと、例外発生時のクリーンアップ不足である。
今回は、CorelDRAW VBAを極限まで知り尽くしたアーキテクチャの視点から、メモリリークとゾンビプロセスを完全に撲滅し、数千ファイルのバッチ処理を完遂するための「要塞級」のエラーハンドリングとコード設計を伝授する。
—
1. なぜCorelDRAW VBAのバッチ処理は破綻するのか?
「ドットつなぎ」のCOM参照がリークを生む
VBAで最もやりがちな悪臭放つコード(Anti-Pattern)がこれだ。
‘ 【絶対に行ってはいけないアンチパターン】
Dim s As Shape
Set s = CorelDRAW.Application.ActiveDocument.ActivePage.CreateRectangle(0, 0, 100, 100)
この1行には、`Application`、`ActiveDocument`、`ActivePage`、`Shape` という4つのCOMオブジェクトが暗黙的に生成されている。
VBAのランタイムは賢いように見えて、これらドット(`.`)で繋がれた一時的なオブジェクトの参照カウンタを完全には解放してくれない。結果、ガベージコレクションが追いつかず、VBAの背後でCorelDRAWのプロセスがメモリリークを起こす。
ゾンビプロセスの正体
エラーが発生した際、`On Error Resume Next` で強引に処理を継続させたり、適切な `Document.Close` や `Application.Quit` を行わずにVBAが終了したりすると、CorelDRAWのCOMサーバーインスタンスがOS上に浮遊し続ける。これが「ゾンビプロセス」だ。
これが蓄積すると、次のバッチ起動時にIPC(プロセス間通信)の限界やライセンス競合を引き起こし、システム全体が沈黙する。
—
2. 堅牢なバッチ処理アーキテクチャの3大原則
巨大CDRファイルを安全に連続処理するためには、以下の設計思想をコードに落とし込む必要がある。
1. オブジェクト変数の完全な局所化と明示的破棄(`Nothing`代入)
ループ内で生成する `Document` や `ShapeRange` などの重いオブジェクトは、スコープを最小限にし、処理の終了時に必ず `Set xxx = Nothing` で参照を断つ。
2. 二重の例外トラップ(メイン制御とプロセス制御の分離)
1つのファイルの破損や想定外のエラーでバッチ全体を止めないよう、ファイルごとのエラーハンドリングを徹底する。
3. Applicationインスタンスの使い回し禁止、または厳格なライフサイクル管理
メモリフラグメンテーションを防ぐため、長時間のバッチでは一定数処理したらCorelDRAWを再起動する設計も視野に入れる。
—
3. 【プロダクションコード】完全防護型CDRバッチコンバーター
以下のコードは、指定フォルダ内のすべてのCDRファイルを読み込み、PDFまたは別形式へ安全に変換しながら、メモリリークとゾンビプロセスを完全に排除する実戦用のモジュールである。
Option Explicit
‘ —————————————————————–
‘ メイン・バッチ処理エントリーポイント
‘ —————————————————————–
Sub ExecuteRobustBatchProcess()
Dim targetDir As String
Dim exportDir As String
Dim fso As Object
Dim folder As Object
Dim file As Object
Dim cdrApp As Object
‘ パスの設定(環境に合わせて変更してください)
targetDir = “C:\CDR_Input\”
exportDir = “C:\CDR_Output\”
‘ FSOの初期化
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(targetDir) Then
MsgBox “入力フォルダが存在しません: ” & targetDir, vbCritical
Exit Sub
End If
If Not fso.FolderExists(exportDir) Then
fso.CreateFolder (exportDir)
End If
‘ CorelDRAWのインスタンスを「外部プロセス」として安全に起動
‘ ※既定のインスタンスを掴むのではなく、専用インスタンスを生成して確実に破棄する
On Error Resume Next
Set cdrApp = CreateObject(“CorelDRAW.Application.24”) ‘ バージョンに応じて調整 (例: 24 = 2022)
If cdrApp Is Nothing Then
Set cdrApp = CreateObject(“CorelDRAW.Application”)
End If
On Error GoTo 0
If cdrApp Is Nothing Then
MsgBox “CorelDRAWの起動に失敗しました。”, vbCritical
Exit Sub
End If
‘ バッチ処理中の画面描画とイベントを抑制してパフォーマンスを極限まで高める
cdrApp.Optimization = True
cdrApp.EventsEnabled = False
Dim fileCount As Long
Dim successCount As Long
fileCount = 0
successCount = 0
‘ ファイルループ
Set folder = fso.GetFolder(targetDir)
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Name)) = “cdr” Then
fileCount = fileCount + 1
‘ 1ファイルごとの処理(エラーフックを完全に独立させる)
If ProcessSingleFile(cdrApp, file.Path, exportDir) Then
successCount = successCount + 1
End If
End If
Next file
‘ 終了処理:最適化の解除とアプリケーションの終了
On Error Resume Next
cdrApp.EventsEnabled = True
cdrApp.Optimization = False
cdrApp.Quit
On Error GoTo 0
‘ 参照の完全解放(ゾンビ撲滅のキモ)
Set cdrApp = Nothing
Set folder = Nothing
Set fso = Nothing
MsgBox “バッチ処理が完了しました。” & vbCrLf & _
“処理ファイル数: ” & fileCount & vbCrLf & _
“成功: ” & successCount & ” / 失敗: ” & (fileCount – successCount), vbInformation
End Sub
‘ —————————————————————–
‘ 単一ファイル処理(スコープを分離し、ローカルオブジェクトを確実に破棄)
‘ —————————————————————–
Private Function ProcessSingleFile(ByRef cdrApp As Object, ByVal filePath As String, ByVal exportDir As String) As Boolean
Dim doc As Object
Dim exportPath As String
Dim fso As Object
ProcessSingleFile = False
Set fso = CreateObject(“Scripting.FileSystemObject”)
exportPath = exportDir & fso.GetBaseName(filePath) & “.pdf”
Set fso = Nothing
‘ 個別ファイルのエラーハンドリング
On Error GoTo ErrorHandler
‘ ドキュメントを開く(余計なUI表示を抑制するため Invisible オプションを活用)
‘ ※CorelDRAWのバージョン仕様によりOpenExやOpenを使用
Set doc = cdrApp.OpenDocument(filePath)
If Not doc Is Nothing Then
‘ — ここに実際のベクター加工・変換ロジックを記述 —
‘ 例: PDFとしてエクスポート
Dim expFilter As Object
Set expFilter = doc.PublishToPDF(exportPath)
‘ エクスポートフィルターの解放
Set expFilter = Nothing
‘ ドキュメントを保存せずに閉じる(変更を破棄)
doc.Close
Set doc = Nothing
ProcessSingleFile = True
End If
Exit Function
ErrorHandler:
‘ 異常発生時のログ出力とクリーンアップ
Debug.Print “Error processing file: ” & filePath & ” | Description: ” & Err.Description
‘ 失敗時もドキュメントが開いていれば強制的に閉じる
On Error Resume Next
If Not doc Is Nothing Then
doc.Close
Set doc = Nothing
End If
On Error GoTo 0
ProcessSingleFile = False
End Function
—
4. チーフアーキテクトが解説するコードの急所
1. `CreateObject(“CorelDRAW.Application”)` の独立運用
既存のCorelDRAWウィンドウにアタッチする `GetObject` ではなく、常に `CreateObject` で専用のCOMインスタンスを立ち上げ、処理が終わったら `cdrApp.Quit` と `Set cdrApp = Nothing` で完全にプロセスを消滅させる。これにより、他の作業への影響やメモリリークの累積を防ぐ。
2. `Optimization = True` と `EventsEnabled = False` の絶対遵守
大量のオブジェクトを操作する際、画面の再描画やUIイベントの監視が有効になっていると、パフォーマンスが数十分の一に落ちるだけでなく、メモリのフラグメンテーションを引き起こす。処理の最初に無効化し、終了時に必ず復元すること。
3. 多重の安全ネット(`ErrorHandler` 内でのクリーンアップ)
ファイルが破損している、あるいはパスワード保護されている等の理由で `OpenDocument` が失敗した場合、`doc` オブジェクトは不完全な状態で宙に浮く。
エラーハンドラ内で `If Not doc Is Nothing Then doc.Close` を挟むことで、COMオブジェクトのゾンビ化を防ぎ、次のファイルの処理へ安全に移行できる。
—
5. 運用時のベストプラクティス:超巨大バッチへの備え
もし処理するCDRファイルが「5,000件以上」に及ぶ場合、いかに上記のようにコードを書いても、CorelDRAWの内部エンジンに微小なメモリリークが蓄積することがある。
その場合のプロフェッショナルな回避策として、「VBAから外部のPowerShellやVBScript、あるいはC#製コンソールアプリを呼び出し、100ファイル処理するごとにCorelDRAWプロセスを丸ごと再起動する(プロセス・アイソレーション)」という設計アプローチも頭に入れておいてほしい。
VBA単体で完結させる場合でも、今回提供した「オブジェクトの局所化」「明示的 `Nothing`」「完全なエラーハンドリング」の3点を守れば、夜間バッチを朝まで一度も止めることなく走り切る、極めて堅牢な自動化基盤が手に入るはずだ。現場の信頼を勝ち取るエンジニアリングを、ぜひ実装してほしい。
