【実務・中級編】【上級プロ向け】COMオブジェクトの参照リークとゾンビプロセスの完全撲滅!堅牢なVBAエラーハンドリング設計 – CorelDRAW VBA解析バイブル

スポンサーリンク

【上級プロ向け】COMオブジェクトの参照リークとゾンビプロセスの完全撲滅!堅牢なVBAエラーハンドリング設計

CorelDRAWを使った大量のCDRファイルのバッチ処理、あるいは外部システム連携による自動ファイル変換。開発初期は順調に見えても、いざ本番環境で数百・数千ファイルの連続処理を回すと、決まって次のような壁にぶつかる。

  • 「処理が進むにつれてメモリ消費量が右肩上がりに増え、やがてフリーズする」
  • 「タスクマネージャーを見ると、VBAを終了したはずなのに `CorelDRW.exe` のゾンビプロセスが背後で居座り続けている」
  • 「ファイルを開いたままクラッシュし、共有違反エラー(Error 70)が頻発する」

これらは偶然のバグではない。VBAとCorelDRAWのCOM(Component Object Model)アーキテクチャの仕様、そしてオブジェクトのライフサイクルを無視した「素人コード」がもたらす必然の結末だ。

今回は、CorelDRAW VBAを極め、現場のシステムを完全に掌握したいエンジニアに向けて、メモリリークとゾンビプロセスを根絶し、24時間止まらない堅牢なバッチ処理システムを構築する設計思想と実践コードを伝授する。

1. なぜCorelDRAW VBAでメモリリークとゾンビが起きるのか?

VBAは一見するとガベージコレクション(GC)を備えたモダンな言語に見えるかもしれない。しかし、アプリケーション間連携(Automation)において、背後で動くCorelDRAWは完全に別個のCOMサーバーである。

参照カウンタの罠

VBAから `CreateObject(“CorelDRAW.Application”)` や `ActiveDocument` を叩いた瞬間、CorelDRAW側のCOMオブジェクトの参照カウンター(Reference Counter)がインクリメントされる。

問題は、以下のような「一見正しそうに見えて最悪なコード」にある。

‘ 【アンチパターン】絶対にやってはいけない書き方
Sub BadBatchProcess()
Dim cdrApp As Object
Set cdrApp = CreateObject(“CorelDRAW.Application.24”)

‘ ドキュメントを開いて何かする
Dim doc As Object
Set doc = cdrApp.OpenDocument(“C:\Data\sample.cdr”)
doc.Export “C:\Data\sample.jpg”, cdrJPEG

‘ ここで処理が終わったつもり……?
cdrApp.Quit
‘ 変数解放をサボっている!
End Sub

このコードを実行すると、以下の現象が起きる。
1. `doc` や `cdrApp` が保持していたCOM参照が、プロシージャを抜けてもメモリ空間や背後のプロセスに残り続ける。
2. VBA側がメモリの解放タイミングをを見失い、CorelDRAWのプロセス(`CorelDRW.exe`)がタスクに残存する。
3. これがループ内で何回も繰り返されると、OSのメモリ枯渇(OOM)またはCOMオブジェクトの上限に達し、ツールが沈黙する。

2. ゾンビ撲滅の3大鉄則

プロの業務自動化エンジニアが守るべき鉄則は以下の3つだ。

1. 暗黙のオブジェクト生成(ドット繋ぎ)の絶対禁止

  • `cdrApp.ActiveDocument.ActiveLayer.CreateArtisticText…` のようにドットで繋いでオブジェクトを取得すると、VBAが裏側で一時的な参照(隠しポインタ)を生成し、絶対にコードから解放できなくなる。すべてのオブジェクトは明示的に変数に格納せよ。

2. 逆順での厳格な `Nothing` 代入

  • 生成したオブジェクトは、取得した順とは逆の順序で `Set xxx = Nothing` を実行し、最後にアプリケーション本体を閉じる。

3. トランザクション&エラーハンドリング(`On Error GoTo`)の徹底

  • 途中で予期せぬエラー(ファイル破損、メモリ不足など)が発生しても、必ず終了処理(Cleanup)ブロックへジャンプする構造を強制する。これにより、エラー時でも確実にプロセスが死なないようにする。

3. 【プロダクションコード】堅牢なバッチ処理エンジン実装例

以下のコードは、指定フォルダ内のすべてのCDRファイルをJPEGに高精度で変換し、エラーが発生しても確実にプロセスを回収する、実戦投入レベルのテンプレートだ。

Option Explicit

‘ メインエントリーポイント
Sub ProductionBatchConverter()
Dim targetDir As String
Dim exportDir As String
targetDir = “C:\CDR_Input\”
exportDir = “C:\CDR_Output\”

‘ 実行時間の計測開始
Dim startTime As Double
startTime = Timer

Debug.Print “=== バッチ処理開始 ===”

‘ ファイルループ処理の実行
ExecuteBatchProcess targetDir, exportDir

Debug.Print “=== バッチ処理完了 (総所要時間: ” & Format(Timer – startTime, “0.00”) & ” 秒) ===”
End Sub

‘ バッチ処理コアロジック
Private Sub ExecuteBatchProcess(ByVal inDir As String, ByVal outDir As String)
Dim cdrApp As Object
Dim doc As Object
Dim fileName As String
Dim successCount As Long
Dim errorCount As Long

successCount = 0
errorCount = 0

‘ 1. CorelDRAWインスタンスの起動(必ず単一インスタンスを制御下に向くようにする)
On Error GoTo InitError
Set cdrApp = CreateObject(“CorelDRAW.Application”)
cdrApp.Optimization = True ‘ 画面描画やイベントを抑制してパフォーマンスを最大化
cdrApp.EventsEnabled = False
On Error GoTo 0

‘ 2. ディレクトリ内のファイル走査
fileName = Dir(inDir & “.cdr”)

Do While fileName <> “”
Dim fullPathIn As String
Dim fullPathOut As String
fullPathIn = inDir & fileName
fullPathOut = outDir & Replace(fileName, “.cdr”, “.jpg”, , , vbTextCompare)

‘ — ファイルごとのトランザクション処理(エラーセーフブロック) —
On Error GoTo FileError

‘ ドキュメントオープン
Set doc = cdrApp.OpenDocument(fullPathIn)

‘ エクスポート処理(例:JPEGエクスポート)
Dim expFilter As Object
Set expFilter = doc.Export(fullPathOut, cdrJPEG, cdrSelection, GetExportOptions())
expFilter.Finish

‘ ドキュメントのクローズ(変更を保存せずに閉じる)
doc.Close
Set doc = Nothing ‘ 即座に参照破棄

successCount = successCount + 1
Debug.Print “[SUCCESS] ” & fileName
GoTo NextFile

FileError:
‘ 個別ファイルのエラーハンドリング(バッチ全体は止めるな)
errorCount = errorCount + 1
Debug.Print “[ERROR] ” & fileName & ” – 理由: ” & Err.Description

‘ 異常終了時のドキュメント確実なクローズ処理
On Error Resume Next
If Not doc Is Nothing Then
doc.Close
Set doc = Nothing
End If
On Error GoTo 0

NextFile:
‘ 次のファイルへ
fileName = Dir()
Loop

‘ 3. 正常終了時のクリーンアップ処理へジャンプ
GoTo CleanUp

InitError:
MsgBox “CorelDRAWの起動に失敗しました。インストール状態を確認してください。” & vbCrLf & Err.Description, vbCritical, “致命的エラー”
Exit Sub

CleanUp:
‘ 4. アプリケーション終了と完全なメモリ解放
On Error Resume Next
If Not cdrApp Is Nothing Then
cdrApp.Optimization = False
cdrApp.EventsEnabled = True
cdrApp.Quit
Set cdrApp = Nothing
End If
On Error GoTo 0

MsgBox “バッチ処理が終了しました。” & vbCrLf & _
“成功: ” & successCount & ” 件” & vbCrLf & _
“失敗: ” & errorCount & ” 件”, vbInformation, “処理完了”
Exit Sub
End Sub

‘ エクスポートオプションの設定ヘルパー
Private Function GetExportOptions() As Object
Dim opt As Object
Set opt = CreateObject(“CorelDRAW.ExportOptions”)
With opt
.Presentation = False
.StructVersion = 1
.SubFormat = 0
.Compression = 15
.Smoothing = 100
End With
Set GetExportOptions = opt
End Function

4. コードのアーキテクチャ解説:なぜこの設計なのか?

① `cdrApp.Optimization = True` と `EventsEnabled = False` の魔力

大量処理時のパフォーマンス低下の最大の原因は、「ドキュメントを開くたびにUIが再描画され、バックグラウンドで不要なイベントリスナーが発火すること」にある。
処理の冒頭でこれらを無効化し、終了時に元に戻すことで、処理速度を最大3倍以上に引き上げつつ、リソースの無駄な消費を防ぐことができる。

② ファイルごとの `On Error GoTo FileError` 隔離

バッチ処理で最も避けるべきなのは、「1つの壊れたCDRファイルのせいで、後続の数百件の処理まで全て止まってしまうこと」だ。
上記コードでは、ファイル単位でエラーをキャッチし、失敗したドキュメントだけを安全に破棄して次のループ(`Dir()`)へ強制的に復帰させる堅牢な構造(サーキットブレーカー的な思想)をとっている。

③ 徹底的な `Nothing` の連鎖

ループの内部、およびプロシージャの出口(`CleanUp`)において、生成したすべてのオブジェクト(`doc`, `expFilter`, `cdrApp`)に対して、明示的に `Set xxx = Nothing` を実行している。
これにより、COMの参照カウンタが確実にゼロになり、OSへメモリが即座に返還されるため、ゾンビプロセスが発生する余地を完全に断つことができる。

5. チーフアーキテクトからの総括

VBAによる業務自動化は、手軽であるゆえに「動けばいいや」という雑なコードが蔓延しやすい。しかし、ひとたびエンタープライズな現場やミッションクリティカルなバッチ処理に組み込んだ途端、こうしたメモリ管理の甘さはシステム全体の信頼性を揺るがす致命傷となる。

今回紹介した「明示的な参照破棄」「スコープを意識したエラーハンドリング」「最適化フラグの適切な制御」は、CorelDRAWに限らず、すべてのCOM連携自動化において普遍的に通用する最高峰のエンジニアリング手法である。

あなたの書くコードを「動くだけのスクリプト」から、「24時間止まらない堅牢なプロダクションシステム」へと昇華させてほしい。

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