【テクニカル・上級編】Document.Close呼び出し時のSaveChangesパラメータ制御とオブジェクト変数の確実な解放手法 – CorelDRAW VBA解析バイブル

スポンサーリンク

CorelDRAW VBAを掌握する極限の知見:大量ファイル処理におけるメモリリークの根絶とDocument.Closeの鉄則

CorelDRAW VBAを用いた自動化において、単発のスクリプト作成と、数百・数千点におよぶプロダクション環境でのバッチ処理とでは、求められるアーキテクチャの思考が根本から異なる。

特に、深夜に走る無人のバッチ処理で「突然のVBAランタイムエラー(メモリ不足)」や「CorelDRAWプロセスがタスクマネージャーに残骸として居座り続ける現象」に直面したシニアエンジニアは少なくないはずだ。

本稿では、CorelDRAWオブジェクトモデルのライフサイクル、とりわけ `Document.Close` メソッドの挙動と、VBAランタイムにおけるオブジェクト変数の確実な解放(Nothingクリア)に焦点を当て、極限まで安定稼働する自動化システムの設計思想を解説する。

1. CorelDRAWオブジェクトモデルの闇:なぜメモリリークが発生するのか

VBAのガベージコレクション(GC)は、.NETのそれとは異なり、基本的に参照カウント方式をとっている。オブジェクト変数がスコープを抜けるか、明示的に `Nothing` が代入されたタイミングで参照カウントがデクリメントされ、ゼロになった時点でCOMオブジェクトが解放される。

しかし、CorelDRAWのCOMコンポーネント(VBAホスト)は、以下のような悪名高い挙動を示す。

  • 暗黙的な参照の保持: `ActiveDocument` や `ActivePage` などのグローバルショートカットプロパティを使用すると、CorelDRAWの内部エンジンがオブジェクトへの参照を裏で保持し続ける。
  • COMラッパーのリーク: `Application.OpenDocument` が返す `Document` オブジェクトを適切に破棄せず、変数スコープ外へ遷移させると、C++層のメモリが解放されずに蓄積する。
  • モーダルダイアログやプロンプトの誤作動: バッチ処理中に変更が保存されていないドキュメントを閉じようとした際、VBA側で `SaveChanges` パラメータを省略すると、CorelDRAWはUIスレッドをブロックし、バックグラウンド処理が完全停止する。

これらを防ぐには、「グローバルプロパティに頼らない」「ドキュメントは必ず明示的に閉じる」「変数は逆順で `Nothing` にする」という鉄則を遵守しなければならない。

2. Document.Close と SaveChanges の厳密な制御

ドキュメントを閉じる際、最もやってはいけないのが `ActiveDocument.Close` のような曖昧な呼び出しだ。必ずターゲットとなる `Document` オブジェクトの参照を保持し、そのメソッドとして呼び出す必要がある。

さらに、`Close` メソッドのシグネチャを正確に理解しなければならない。

expression.Close

※注: CorelDRAWのバージョンによっては `Close` メソッド自体に引数を取らない仕様のものや、`Application.ActiveDocument.Close` の挙動が不安定なケースがある。そのため、変更破棄を確実にコントロールするには、閉じる前に `Saved` プロパティを操作するのがプロの常道である。

変更破棄と確実なクローズのパターン

バッチ処理で既存ファイルを「読み取り専用」として開き、加工して別名保存するか、あるいは無変更で次々に閉じていく場合、意図しない「変更を保存しますか?」のダイアログを出させないことが最優先となる。

‘ 変更破棄して確実に閉じるためのイディオム
doc.Saved = True ‘ 「変更は保存済み」と強制的に見なすことで、Close時のダイアログを抑制
doc.Close

このワンクッションを入れることで、CorelDRAWのCOMエンジンがUIインタラクション待ちでフリーズするリスクを完全に排除できる。

3. 【実践】メモリリークを根絶するバッチ処理アーキテクチャ

以下に、数千ファイルの処理に耐えうる、実務レベルの堅牢なサブプロシージャを示す。エラーハンドリング、オブジェクトの明示的解放、そしてガベージコレクションの誘導が完璧に組み込まれた構造だ。

Option Explicit

Sub BatchProcessCorelDrawFiles()
Dim targetFolder As String
Dim fileName As String
Dim appRef As CorelDRAW.Application
Dim docRef As CorelDRAW.Document

targetFolder = “C:\Data\CorelDrawInput\”

‘ Applicationインスタンスの取得(複数起動を防ぎ、同一プロセス内で完結させる)
Set appRef = New CorelDRAW.Application

‘ バッチ処理中の画面描画とイベントを抑制してパフォーマンスを極限まで引き上げる
appRef.Optimization = True
appRef.EventsEnabled = False

On Error GoTo ErrorHandler

fileName = Dir(targetFolder & “.cdr”)

Do While fileName <> “”
‘ 1. ドキュメントを開く
Set docRef = appRef.OpenDocument(targetFolder & fileName)

‘ — [ここに実際の処理ロジックを記述] —
‘ 例: 全ページのオブジェクト数をログに出力する処理など
Call ProcessDocument(docRef)
‘ ————————————

‘ 2. 変更を破棄して閉じる準備(今回は読込専用処理の前提)
docRef.Saved = True

‘ 3. ドキュメントの明示的なクローズ
docRef.Close

‘ 4. 即座にオブジェクト変数を破棄
Set docRef = Nothing

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

CleanUp:
‘ 終了処理:最適化フラグを戻す
If Not appRef Is Nothing Then
appRef.Optimization = False
appRef.EventsEnabled = True
Set appRef = Nothing
End If

‘ VBAのメモリ解放を強制(完全ではないが、COM解放のトリガーとなる)
DoEvents
Exit Sub

ErrorHandler:
‘ 異常系:ログ出力やイミディエイトウィンドウへの通知
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “Critical Error”

‘ エラー発生時もオブジェクトが残っていれば確実に解放
If Not docRef Is Nothing Then
On Error Resume Next
docRef.Saved = True
docRef.Close
Set docRef = Nothing
On Error GoTo 0
End If

Resume CleanUp
End Sub

Private Sub ProcessDocument(ByRef doc As CorelDRAW.Document)
‘ ドキュメントに対する具体的な操作をカプセル化
Dim pageCount As Long
pageCount = doc.Pages.Count

‘ デバッグ出力(本番ではファイル出力等に置き換えること)
Debug.Print doc.Name & ” : Page Count = ” & pageCount
End Sub

4. チーフアーキテクトからの実践的助言:生き残るコードを書くために

1. `DoEvents` の戦略的配置
数千ファイルのループを回す際、`DoEvents` を適度な頻度(例: 50ファイルに1回)で挟むことを推奨する。これにより、Windowsのメッセージキューが処理され、OSレベルでのメモリ解放やプロセス間のコンテキストスイッチが正常に行われるため、CorelDRAWの突然の強制終了(Crash)率が劇的に下がる。

2. 変数のスコープと解放の順序
複数のオブジェクト(`Application`, `Document`, `Layer`, `ShapeRange` 等)を扱う場合、生成した順序とは逆の順序で `Nothing` を代入するのがC++ / COMプログラミングの定石である。
`Shape` $\rightarrow$ `Layer` $\rightarrow$ `Document` $\rightarrow$ `Application` の順で確実に断ち切ること。

3. レガシー環境におけるVBAの限界と外部連携
もしCorelDRAWのバージョンが古く(例: X7や2018など)、どれだけコードを最適化してもメモリリークが防げない場合は、VBA単体で完結させることを諦め、VB.NET(COM Interop)による外部プロセスからの制御へ移行することを強く推奨する。
外部プロセスから `Process.Kill()` やガベージコレクタ(`GC.Collect()`)を明示的に叩くことで、CorelDRAWの肥大化したプロセスごとメモリ空間を完全に消し去るアーキテクチャの方が、長期的な保守性においては遥かに優れている。

自動化とは、コードを書くことではなく「システムを長時間安定稼働させること」である。本稿の知見が、あなたのデスクトップオートメーションを極限の安定領域へと導くことを願う。

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