【実務・中級編】【上級プロ向け】COMオブジェクトの参照リークとゾンビプロセスの完全撲滅!巨大なCDRファイルを何千個も安全に連続バッチ処理するメモリ管理設計と例外処理 – CorelDRAW VBA解析バイブル

スポンサーリンク

【上級プロ向け】COMオブジェクトの参照リークとゾンビプロセスの完全撲滅!巨大なCDRファイルを何千個も安全に連続バッチ処理するメモリ管理設計と例外処理

開発プロジェクトの皆さん、こんにちは。チーフアーキテクトです。
CorelDRAW VBAを用いた自動化、特に大量のCDRファイルを対象としたフォーマット変換やバッチ処理において、避けては通れない「メモリリーク」という悪夢。これが原因で、夜中にゾンビプロセスが跋扈し、サーバーがフリーズ、そして貴重な作業時間が溶けていく…そんな経験はありませんか?

本稿では、単なるリファレンスの羅列ではなく、私が長年の開発経験で培ってきた、COMオブジェクトのライフサイクルを深く理解した上でのメモリ管理設計と、堅牢な例外処理について、実務でそのまま使えるプロダクションコード例を交えながら、ロジカルかつシャープに解説していきます。

1. なぜCorelDRAW VBAでメモリリークは発生するのか? – COMオブジェクトの「見えざる罠」

CorelDRAW VBAは、CorelDRAWアプリケーションのCOM (Component Object Model) オブジェクトモデルを利用しています。COMオブジェクトは、その性質上、適切に解放されないと、リソースを占有し続け、メモリリークを引き起こします。特に、CorelDRAWのようなリッチなアプリケーションのオブジェクトは、その内部構造が複雑であるため、開発者が意図せず参照を保持し続けてしまうケースが後を絶ちません。

1.1. 参照カウントの誤解と「オブジェクトへの道」の閉鎖

COMオブジェクトは、内部的に「参照カウント」という仕組みで管理されています。オブジェクトへの参照が一つ増えるごとにカウントが上がり、参照が一つ減るごとにカウントが下がります。カウントがゼロになった時点で、オブジェクトは解放されるはずです。

しかし、VBAのスコープやライフサイクルを理解していないと、以下のような状況で意図せず参照が保持され、カウントがゼロにならないことがあります。

  • グローバル変数やモジュールレベル変数への永続的な参照: オブジェクトを一度変数に代入すると、その変数がスコープを抜けるまで(あるいはプログラムが終了するまで)オブジェクトへの参照は残り続けます。
  • イベントハンドラやコールバック関数での参照: イベントが発生するたびにオブジェクトへの参照が暗黙的に保持されることがあります。
  • コレクションや配列への追加: コレクションや配列にオブジェクトを追加した場合、それらが解放されない限り、中のオブジェクトへの参照も解放されません。

これらの「参照への道」が一つでも開いたままだと、オブジェクトはメモリ上に残り続け、やがてシステム全体のパフォーマンス低下、最終的にはCorelDRAWプロセスのフリーズ、あるいはゾンビプロセス化へと繋がるのです。

1.2. 巨大なCDRファイル処理における「メモリの壁」

単一のCDRファイルを処理するだけなら、多少のメモリリークは目立たないかもしれません。しかし、何千、何万という巨大なCDRファイルを連続してバッチ処理する場合、この問題は指数関数的に増大します。

  • 累積するメモリ使用量: 一つのファイル処理で発生した微細なメモリリークが、数千回繰り返されると、数十GB、数百GBといった途方もないメモリ使用量に達する可能性があります。
  • ガベージコレクションの限界: VBAや.NET Frameworkのガベージコレクタは、COMオブジェクトの参照リークを検知・解消することができません。これは、COMオブジェクトがVBAや.NETの管理外で動作する部分があるためです。
  • リソース枯渇によるクラッシュ: メモリが枯渇すると、CorelDRAW自体はもちろん、OS全体が不安定になり、予期せぬクラッシュやハングアップを引き起こします。

2. ゾンビプロセス撲滅! – 厳格なオブジェクト参照破棄設計

この問題を根本から解決するためには、「オブジェクトへの道」を徹底的に閉ざす、厳格な参照破棄設計が不可欠です。これは、単に`Set obj = Nothing`を闇雲に書くこととは異なります。オブジェクトのライフサイクルを意識し、責任を持って解放する設計思想が求められます。

2.1. プロシージャレベルでの「潔癖な」オブジェクト解放

最も基本的かつ重要なのは、各プロシージャ(SubまたはFunction)の終了時に、そこで使用されたCOMオブジェクトへの参照を確実に解放することです。

NGコード例:解放漏れのリスク

‘ これは非推奨!解放漏れのリスクが高い
Sub ProcessFile_Bad(filePath As String)
Dim corelApp As CorelDRAW.Application
Dim doc As CorelDRAW.Document
Dim pdfSettings As PDFSettings ‘ 例として

Set corelApp = Application ‘ 既にApplicationオブジェクトは存在しているはず
Set doc = corelApp.OpenDocument(filePath)

‘ … ドキュメント処理 …

‘ ここで解放を忘れると、ループ処理でメモリリーク発生!
‘ Set doc = Nothing
‘ Set pdfSettings = Nothing
End Sub

このコードでは、`doc`や`pdfSettings`がプロシージャを抜けても解放されません。もしこのプロシージャがループで何千回も呼び出されたら、悲惨な結果を招きます。

OKコード例:プロシージャ終了時の確実な解放

‘ 堅牢なオブジェクト解放(プロシージャレベル)
Sub ProcessFile_Good(filePath As String)
Dim corelApp As CorelDRAW.Application
Dim doc As CorelDRAW.Document
Dim pdfSettings As PDFSettings ‘ 例として

On Error GoTo ErrorHandler ‘ 例外処理を必ず設定!

‘ Applicationオブジェクトはグローバルで取得・管理されることが多いが、
‘ ここでは例として、もし新規に取得した場合の解放を想定
‘ 実際には、Applicationオブジェクトへの参照はグローバルで管理し、
‘ プログラム終了時に一度だけ解放するのが一般的。
Set corelApp = Application

‘ ドキュメントを開く
Set doc = corelApp.OpenDocument(filePath)

‘ PDFエクスポート設定の取得(例)
Set pdfSettings = corelApp.CreatePDFSettings()

‘ — ここでドキュメントに対する具体的な処理を実行 —
‘ 例:レイヤーの操作、オブジェクトの追加・削除、テキストの置換など
‘ 重要なのは、この処理の中で新しいCOMオブジェクトを生成した場合も、
‘ それを適切に解放すること。

‘ — 処理完了 —

CleanExit:
‘ ★★★ 参照を「意識的に」Nothingに設定 ★★★
‘ この順番が重要! 依存関係を考慮する。
‘ 例:Documentオブジェクトが、Applicationオブジェクトへの参照を
‘ 内部に持っている可能性があるため、Documentを先に解放。
Set pdfSettings = Nothing
Set doc = Nothing
‘ corelApp はグローバルで管理されている場合はここで解放しない

Exit Sub ‘ 正常終了時はErrorHandlerをスキップ

ErrorHandler:
‘ ★★★ 例外処理ブロックでの解放 ★★★
‘ エラーが発生した場合でも、必ずここで解放を行う
‘ これにより、エラー発生時もメモリリークを防ぐ
Debug.Print “エラー発生: ” & Err.Number & ” – ” & Err.Description
Resume CleanExit ‘ Cleanup処理(解放)へ移行して終了
End Sub

【解説】

  • `On Error GoTo ErrorHandler`: 例外処理は、メモリリーク防止の要です。エラーが発生しても、必ず解放処理に進むようにします。
  • `Set obj = Nothing`: これがCOMオブジェクトへの参照を明示的に破棄する最も直接的な方法です。
  • 解放の順序: オブジェクト間の依存関係を考慮して解放順序を決定します。一般的には、より上位のオブジェクト(例: `Application`)よりも、それが保持している下位のオブジェクト(例: `Document`)から先に解放するのが安全です。
  • `Resume CleanExit`: エラーハンドラからクリーンアップ処理(解放処理)へジャンプさせ、その後正常終了させることで、エラー時でも解放を保証します。

2.2. コレクション・配列からの「確実な」オブジェクト排除

コレクションや配列に格納したオブジェクトも、同様に注意が必要です。

‘ 堅牢なコレクションからのオブジェクト解放
Sub ProcessCollection(collection As Collection)
Dim obj As Variant ‘ コレクションの要素はVariant型で受け取るのが一般的
Dim i As Long

On Error GoTo ErrorHandler

‘ ★★★ コレクション内のオブジェクトを一つずつ解放 ★★★
‘ Removeメソッドで削除するだけでは、オブジェクト自体は解放されない場合がある。
‘ 明示的にNothingにするのが最も安全。
For i = collection.Count To 1 Step -1
Set obj = collection.Item(i) ‘ オブジェクトへの参照を取得
‘ obj.Dispose ‘ もしDisposeメソッドがあれば呼び出す(VBAでは稀)
Set obj = Nothing ‘ ★★★ オブジェクトへの参照を破棄 ★★★
collection.Remove i ‘ コレクションから要素を削除
Next i

CleanExit:
Exit Sub

ErrorHandler:
Debug.Print “Collection処理エラー: ” & Err.Number & ” – ” & Err.Description
‘ エラー時でも、可能であれば残りの要素を解放する試み
If i > 0 Then
For i = i To 1 Step -1
On Error Resume Next ‘ エラーを無視して継続
Set obj = collection.Item(i)
Set obj = Nothing
collection.Remove i
On Error GoTo 0
Next i
End If
Resume CleanExit
End Sub

【解説】

  • `collection.Remove i` だけでは、コレクションから要素が削除されるだけで、その要素が参照していたCOMオブジェクト自体が解放されるとは限りません。
  • 必ず、`Set obj = collection.Item(i)` で取得したオブジェクトに対して `Set obj = Nothing` を実行し、参照を破棄してから `collection.Remove i` を実行します。

2.3. グローバルスコープでのCOMオブジェクト管理の落とし穴

`Application`オブジェクトなど、プログラム全体で共有されるCOMオブジェクトは、グローバル変数やモジュールレベル変数で管理することが多いです。この場合、解放のタイミングが重要になります。

‘ — basMain.bas —
Public g_corelApp As CorelDRAW.Application
Public g_isAppInitialized As Boolean

Sub InitializeCorelDRAW()
On Error GoTo ErrorHandler
If Not g_isAppInitialized Then
Set g_corelApp = New CorelDRAW.Application ‘ 新規にCorelDRAWを起動する場合
‘ g_corelApp.Visible = True ‘ 必要に応じて表示
g_isAppInitialized = True
Debug.Print “CorelDRAW Application初期化完了”
End If
Exit Sub

ErrorHandler:
Debug.Print “CorelDRAW初期化エラー: ” & Err.Number & ” – ” & Err.Description
g_isAppInitialized = False
Set g_corelApp = Nothing
End Sub

Sub ReleaseCorelDRAW()
On Error Resume Next ‘ エラーを無視して処理を継続
If g_isAppInitialized Then
If Not g_corelApp Is Nothing Then
‘ ★★★ Applicationオブジェクトの解放 ★★★
‘ CorelDRAWを終了させる場合は Quitメソッドを呼び出す
g_corelApp.Quit
Set g_corelApp = Nothing
Debug.Print “CorelDRAW Application解放完了”
End If
g_isAppInitialized = False
End If
On Error GoTo 0
End Sub

‘ — basBatchProcess.bas —
Sub BatchConvertAll()
Dim CDRFiles As Variant
Dim i As Long
Dim sourceFilePath As String
Dim outputFolderPath As String

‘ ファイルリストの取得 (例)
CDRFiles = Application.FileDialog(msoFileDialogFilePicker).Show()

If CDRFiles = -1 Then ‘ OKが押された場合
outputFolderPath = Application.FileDialog(msoFileDialogFolderPicker).Show()
If outputFolderPath <> “” Then
For i = 1 To CDRFiles.Count
sourceFilePath = CDRFiles(i)
‘ ★★★ 各ファイル処理の前に、CorelDRAWアプリが初期化されているか確認 ★★★
If Not g_isAppInitialized Then
InitializeCorelDRAW
If Not g_isAppInitialized Then
MsgBox “CorelDRAWの初期化に失敗しました。処理を中断します。”, vbCritical
Exit Sub
End If
End If

‘ ★★★ 個別ファイル処理 ★★★
‘ ProcessFile_Good サブルーチン内で、
‘ 開いたドキュメントは必ず解放される設計になっていることを想定
ProcessFile_Good sourceFilePath, outputFolderPath ‘ 仮に引数を追加

‘ ★★★ オプション:メモリ解放のためのGC実行 ★★★
‘ VBAには直接的なGC実行コマンドはないが、
‘ COMオブジェクトの解放は、参照がNothingにされた時点で
‘ (COMの仕組みにより)行われる。
‘ もし、非常にメモリを大量に消費する処理を繰り返す場合は、
‘ 一時的にループを抜けて、VBAの標準的な解放処理を待つ
‘ (例:DoEventsを挟む、またはApplication.Waitなどで一時停止)
‘ ことで、メモリが解放される機会を与えることも考えられる。
‘ ただし、これは直接的なメモリ解放を保証するものではない。
DoEvents ‘ UIの応答性を保ちつつ、バックグラウンドで解放処理を促す

Next i
Else
MsgBox “出力フォルダが選択されませんでした。”, vbExclamation
End If
Else
MsgBox “ファイルが選択されませんでした。”, vbExclamation
End If

‘ ★★★ 全処理終了後に、CorelDRAWアプリを解放 ★★★
ReleaseCorelDRAW
End Sub

‘ — basDocumentProcessor.bas —
Sub ProcessFile_Good(sourceFilePath As String, outputFolderPath As String)
Dim doc As CorelDRAW.Document
Dim pdfSettings As PDFSettings
Dim exportPath As String

On Error GoTo ErrorHandler

‘ ★★★ g_corelApp はグローバルで初期化済みであることを前提 ★★★
If g_corelApp Is Nothing Then
Err.Raise 1001, “ProcessFile_Good”, “CorelDRAW Applicationオブジェクトが有効ではありません。”
End If

‘ ドキュメントを開く
Set doc = g_corelApp.OpenDocument(sourceFilePath)

‘ PDFエクスポート設定の取得(例)
Set pdfSettings = g_corelApp.CreatePDFSettings()
‘ pdfSettings のプロパティ設定(例)
‘ pdfSettings.ExportDocRange = cdrExportAllPages
‘ pdfSettings.Compatibility = cdrPDFX1_2001
‘ pdfSettings.ColorProfile = “sRGB IEC61966-2.1”

‘ 出力ファイルパスの生成
exportPath = outputFolderPath & “\” & Left(Dir(sourceFilePath), InStrRev(Dir(sourceFilePath), “.”) – 1) & “.pdf”

‘ PDFとしてエクスポート
doc.ExportAsFile exportPath, pdfSettings, cdrPDF

Debug.Print “エクスポート完了: ” & exportPath

CleanExit:
‘ ★★★ ドキュメント関連オブジェクトの解放 ★★★
Set pdfSettings = Nothing
If Not doc Is Nothing Then
‘ ★★★ ドキュメントを閉じる ★★★
‘ SaveChanges:=False で変更を保存せずに閉じる
doc.Close SaveChanges:=False
Set doc = Nothing
End If
Exit Sub

ErrorHandler:
Debug.Print “ファイル処理エラー: ” & Err.Number & ” – ” & Err.Description & ” (ファイル: ” & sourceFilePath & “)”
‘ エラー時でもクリーンアップ処理へ
Resume CleanExit
End Sub

【解説】

  • `InitializeCorelDRAW` と `ReleaseCorelDRAW` で、CorelDRAWアプリケーションのライフサイクルを管理します。
  • `BatchConvertAll` では、ループの各イテレーションの前に `g_isAppInitialized` を確認し、必要なら初期化します。
  • `ProcessFile_Good` では、グローバルな `g_corelApp` が利用可能であることを前提とし、開いたドキュメント (`doc`) は必ず `Close` メソッドで閉じ、その後 `Set doc = Nothing` で参照を解放します。
  • `DoEvents` は、UIの応答性を保ちつつ、バックグラウンドでCOMオブジェクトの解放処理が実行されるのを促す効果があります。大量処理では、定期的に挟むことで、システム全体の安定性を向上させることができます。

3. 商用レベルの堅牢性 – 徹底的な例外処理設計

メモリリーク防止と並んで重要なのが、予期せぬエラーから処理を守り、安全に中断・復帰できる例外処理です。CorelDRAW VBAにおけるCOMエラーは、しばしば原因特定が困難なため、多層的かつ詳細なエラーハンドリングが不可欠です。

3.1. `Err`オブジェクトの徹底監視とログ記録

VBAの`Err`オブジェクトは、発生したエラーに関する情報を提供します。これを最大限に活用し、デバッグと監視を容易にします。

Sub ExampleWithErrorHandling(filePath As String)
Dim corelApp As CorelDRAW.Application
Dim doc As CorelDRAW.Document

On Error GoTo ErrorHandler ‘ グローバルなエラーハンドラへジャンプ

‘ — 通常処理 —
Set corelApp = Application
Set doc = corelApp.OpenDocument(filePath)

‘ … ドキュメント処理 …
‘ ここで何らかのエラーが発生する可能性のある処理 …
‘ 例:存在しないレイヤーにアクセスしようとする、不正なフォーマットのファイルを読み込もうとするなど

‘ — 正常終了時のクリーンアップ —
Debug.Print “正常終了: ” & filePath
Set doc = Nothing
Set corelApp = Nothing ‘ 必要であれば
Exit Sub

ErrorHandler:
‘ ★★★ エラー発生時の詳細情報取得 ★★★
Dim errNum As Long
Dim errDesc As String
Dim errSource As String

errNum = Err.Number
errDesc = Err.Description
errSource = Err.Source

‘ ★★★ エラーログの記録 ★★★
‘ ファイルパス、エラー番号、説明、発生元をログファイルやデータベースに記録
LogErrorMessage filePath, errNum, errDesc, errSource

‘ ★★★ オブジェクトの解放(エラー発生時も必ず実行) ★★★
‘ エラーハンドラ内でも、参照しているオブジェクトは解放する
If Not doc Is Nothing Then
On Error Resume Next ‘ 解放中のエラーは無視
doc.Close SaveChanges:=False
Set doc = Nothing
On Error GoTo 0
End If
If Not corelApp Is Nothing Then
On Error Resume Next
‘ corelApp.Quit ‘ Applicationオブジェクトを終了させる場合は慎重に
Set corelApp = Nothing
On Error GoTo 0
End If

‘ ★★★ エラーの再スロー、またはユーザーへの通知 ★★★
‘ 必要に応じて、エラーを再スローして上位で捕捉させるか、
‘ ユーザーに通知して処理を中断する。
‘ MsgBox “エラーが発生しました: ” & errDesc, vbCritical
‘ Err.Raise errNum, errSource, errDesc ‘ エラーを再スロー

Debug.Print “エラー処理完了 (ファイル: ” & filePath & “)”

End Sub

Sub LogErrorMessage(filePath As String, errNum As Long, errDesc As String, errSource As String)
‘ 実際には、ファイル書き込み、データベースINSERTなどの処理を実装
Dim logLine As String
logLine = Now() & vbTab & _
“File: ” & filePath & vbTab & _
“Error #: ” & errNum & vbTab & _
“Description: ” & errDesc & vbTab & _
“Source: ” & errSource
Debug.Print “ログ記録: ” & logLine
‘ 例: Open “C:\Logs\CorelDrawBatch.log” For Append As #1
‘ Print #1, logLine
‘ Close #1
End Sub

【解説】

  • `On Error GoTo ErrorHandler`: プロシージャ全体のエラーハンドリングを設定します。
  • `Err.Number`, `Err.Description`, `Err.Source`: エラーの詳細情報を取得します。`Err.Source`は、エラーを発生させたオブジェクトやプロシージャ名を示すことが多く、デバッグに非常に役立ちます。
  • `LogErrorMessage`: 発生したエラーを、ファイルパス、エラー番号、説明、発生元とともに記録します。これにより、後からどのファイルでどのようなエラーが発生したかを追跡できます。商用システムでは、データベースへの記録も有効です。
  • エラー発生時のオブジェクト解放: `ErrorHandler`ラベル以降でも、`doc`や`corelApp`などのオブジェクトがまだ参照されている場合は、必ず解放処理を行います。`On Error Resume Next` を一時的に使用して、解放処理中に発生する可能性のある二次的なエラーを無視し、確実に解放を進めます。
  • エラーの再スロー (`Err.Raise`): エラーハンドラで処理を完結させるのではなく、`Err.Raise` を使ってエラーを再スローすることで、呼び出し元のプロシージャでさらに上位の例外処理を行うことができます。これにより、処理の失敗をシステム全体に伝達し、適切な対応(例:バッチ処理の中断)を促すことが可能です。

3.2. ファイル・データベース連携時の注意点

バッチ処理では、ファイルパスの扱いや、場合によってはデータベースとの連携が発生します。

  • パスの正規化: ファイルパスは、環境によって区切り文字(`\`か`/`)が異なることがあります。`FileSystemObject` などを使用し、OS標準のパス操作を行うことで、互換性を高めることができます。
  • ファイルロック: 処理対象のファイルが他のプロセスによってロックされている場合、CorelDRAWはファイルを正常に開けません。リトライ処理や、ファイルロックの有無を確認する処理を実装すると、堅牢性が増します。
  • データベースアクセス: データベースへの書き込み処理を伴う場合、トランザクション管理とエラーハンドリングが非常に重要です。DB接続の確立・解放、SQL実行時のエラー処理、デッドロックの回避策などを考慮する必要があります。
  • 出力先フォルダの存在確認: 出力先のフォルダが存在しない場合、エクスポート処理は失敗します。`FileSystemObject` などでフォルダの存在を確認し、なければ作成する処理を組み込みましょう。

‘ ファイルパス操作とフォルダ作成の例
Sub EnsureOutputFolder(folderPath As String)
Dim fso As Object ‘ Scripting.FileSystemObject
On Error Resume Next
Set fso = CreateObject(“Scripting.FileSystemObject”)

If Not fso Is Nothing Then
If Not fso.FolderExists(folderPath) Then
On Error GoTo ErrorHandler ‘ エラーハンドラを設定
fso.CreateFolder folderPath
Debug.Print “フォルダ作成: ” & folderPath
End If
Set fso = Nothing
Exit Sub
Else
MsgBox “FileSystemObjectが作成できませんでした。”, vbCritical
Exit Sub
End If

ErrorHandler:
Debug.Print “フォルダ作成エラー: ” & Err.Number & ” – ” & Err.Description
MsgBox “出力フォルダ ‘” & folderPath & “‘ の作成に失敗しました。”, vbCritical
Set fso = Nothing
Err.Raise Err.Number, Err.Source, Err.Description ‘ エラーを再スロー
End Sub

‘ ファイルロック確認の例(簡易版)
Function IsFileLocked(filePath As String) As Boolean
Dim fso As Object
Dim ts As Object
On Error Resume Next
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.OpenTextFile(filePath, 8, True) ‘ 8 = ForAppending, True = CreateIfNotExist
If Err.Number <> 0 Then
IsFileLocked = True ‘ ロックされているか、アクセス権がない
Else
ts.Close ‘ 開いたらすぐに閉じる
IsFileLocked = False
End If
On Error GoTo 0
Set ts = Nothing
Set fso = Nothing
End Function

4. プロダクションコード例 – コピペで動く、保守性の高いバッチ処理

ここまでの知識を踏まえ、実際にCorelDRAW VBAで巨大なCDRファイルを安全にバッチ処理するための、プロダクションコード例を提示します。このコードは、オブジェクト参照の破棄、厳格な例外処理、そしてファイル連携の考慮を含んでいます。

‘===============================================================================
‘ basBatchCDRProcessor.bas
‘ CorelDRAW VBA – 大量CDRファイルバッチ処理用モジュール
‘ 【機能】指定されたCDRファイルを指定されたフォーマット(例: PDF)に一括変換
‘ 【特徴】COMオブジェクトの参照リーク撲滅、堅牢な例外処理、ファイル連携考慮
‘===============================================================================

Option Explicit

‘ — グローバル変数 —
‘ CorelDRAWアプリケーションオブジェクトへの参照
Public g_corelApp As CorelDRAW.Application
‘ CorelDRAWアプリケーションが初期化済みかどうかのフラグ
Public g_isAppInitialized As Boolean

‘ — 定数 —
Const LOG_FILE_PATH As String = “C:\Logs\CorelDRAW_BatchProcess.log” ‘ ログファイルのパス
Const EXPORT_FORMAT As String = “PDF” ‘ エクスポートフォーマット (例: “PDF”, “EPS”, “AI”)
Const EXPORT_EXTENSION As String = “.pdf” ‘ エクスポートファイルの拡張子

‘===============================================================================
‘ 初期化・解放処理
‘===============================================================================

‘ CorelDRAWアプリケーションを初期化する
Sub InitializeCorelDRAW()
On Error GoTo ErrorHandler

If g_isAppInitialized Then Exit Sub ‘ 既に初期化済み

‘ CorelDRAWアプリケーションを新規に起動
Set g_corelApp = New CorelDRAW.Application
‘ g_corelApp.Visible = True ‘ デバッグ中は表示すると便利

g_isAppInitialized = True
LogMessage “CorelDRAW Application初期化完了 (PID: ” & g_corelApp.ProcessId & “)”
Exit Sub

ErrorHandler:
LogErrorMessage “CorelDRAW初期化”, Err.Number, Err.Description, Err.Source
g_isAppInitialized = False
Set g_corelApp = Nothing
‘ エラーを再スローして、呼び出し元で処理を中断させる
Err.Raise Err.Number, Err.Source, “CorelDRAWの初期化に失敗しました。処理を中断します。”
End Sub

‘ CorelDRAWアプリケーションを解放する
Sub ReleaseCorelDRAW()
On Error Resume Next ‘ 解放中のエラーは無視

If g_isAppInitialized And Not g_corelApp Is Nothing Then
‘ CorelDRAWアプリケーションを終了させる
‘ 注意: QuitメソッドはCorelDRAWを閉じます。
‘ もし、他のユーザーがCorelDRAWを使用している可能性がある場合は、
‘ Quitメソッドの呼び出しは慎重に行うか、コメントアウトしてください。
‘ または、Application.Quitメソッドの代わりに、
‘ 単に参照をNothingにするだけでも、VBAの管理下から離れます。
g_corelApp.Quit
Set g_corelApp = Nothing
g_isAppInitialized = False
LogMessage “CorelDRAW Application解放完了”
End If
On Error GoTo 0
End Sub

‘===============================================================================
‘ メイン処理 – バッチ処理の実行
‘===============================================================================

‘ バッチ処理の開始点
Sub StartBatchProcessing()
Dim filePaths() As String ‘ 処理対象のCDRファイルパスを格納する配列
Dim outputFolderPath As String
Dim i As Long
Dim startTime As Double
Dim endTime As Double
Dim totalFiles As Long
Dim processedFiles As Long
Dim failedFiles As Long

startTime = Timer ‘ 処理開始時間を記録

‘ — 1. CorelDRAWアプリケーションの初期化 —
On Error GoTo ErrorHandler
InitializeCorelDRAW ‘ CorelDRAWを起動または参照を取得

‘ — 2. 処理対象ファイルの選択 —
‘ FileDialogを使ってCDRファイルを選択させる(複数選択可)
filePaths = GetFilePathsFromDialog(“CorelDRAW (.cdr) ファイルを選択してください”, “.cdr”, True)

If UBound(filePaths) < 0 Then MsgBox "処理対象のファイルが選択されませんでした。処理を中止します。", vbExclamation GoTo CleanExit ' 早期終了 End If totalFiles = UBound(filePaths) + 1 ' 選択されたファイルの総数 ' --- 3. 出力フォルダの選択 --- outputFolderPath = GetFolderPathFromDialog("エクスポート先のフォルダを選択してください") If outputFolderPath = "" Then MsgBox "出力フォルダが選択されませんでした。処理を中止します。", vbExclamation GoTo CleanExit ' 早期終了 End If ' --- 4. 出力フォルダの存在確認と作成 --- If Not EnsureFolderExists(outputFolderPath) Then MsgBox "出力フォルダ '" & outputFolderPath & "' が存在せず、作成もできませんでした。処理を中止します。", vbCritical GoTo CleanExit End If ' --- 5. 各ファイルのバッチ処理 --- processedFiles = 0 failedFiles = 0 For i = 0 To UBound(filePaths) Dim currentFilePath As String currentFilePath = filePaths(i) ' DoEventsを挟むことで、UIの応答性を保ち、メモリ解放の機会を与える DoEvents ' --- 個別ファイル処理の実行 --- If ProcessSingleCDRFile(currentFilePath, outputFolderPath) Then processedFiles = processedFiles + 1 LogMessage "処理成功: " & currentFilePath Else failedFiles = failedFiles + 1 LogMessage "処理失敗: " & currentFilePath & " (詳細はログファイルを確認してください)" End If Next i CleanExit: endTime = Timer ' 処理終了時間を記録 ' --- 6. CorelDRAWアプリケーションの解放 --- ReleaseCorelDRAW ' --- 7. 処理結果のサマリー表示 --- Dim summaryMsg As String summaryMsg = "バッチ処理が完了しました。" & vbCrLf & vbCrLf & _ "処理対象ファイル数: " & totalFiles & vbCrLf & _ "成功数: " & processedFiles & vbCrLf & _ "失敗数: " & failedFiles & vbCrLf & _ "処理時間: " & Format(endTime - startTime, "0.00") & " 秒" MsgBox summaryMsg, vbInformation Debug.Print summaryMsg Exit Sub ErrorHandler: ' --- メイン処理中の予期せぬエラーハンドラ --- LogErrorMessage "StartBatchProcessing", Err.Number, Err.Description, Err.Source summaryMsg = "バッチ処理中に予期せぬエラーが発生しました。" & vbCrLf & _ "エラー番号: " & Err.Number & vbCrLf & _ "内容: " & Err.Description & vbCrLf & _ "詳細はログファイル (" & LOG_FILE_PATH & ") を確認してください。" MsgBox summaryMsg, vbCritical GoTo CleanExit ' エラー発生時もクリーンアップ処理へ End Sub '=============================================================================== ' 個別ファイル処理 '=============================================================================== ' 単一のCDRファイルを指定フォーマットでエクスポートする ' 戻り値: True (成功), False (失敗) Private Function ProcessSingleCDRFile(sourceFilePath As String, outputFolderPath As String) As Boolean Dim doc As CorelDRAW.Document Dim pdfSettings As PDFSettings ' エクスポート設定オブジェクト Dim exportFilePath As String Dim fileNameWithoutExt As String Dim fso As Object ' FileSystemObject On Error GoTo ErrorHandler ' --- 0. 事前チェック --- If g_corelApp Is Nothing Then Err.Raise 9999, "ProcessSingleCDRFile", "CorelDRAW Applicationオブジェクトが無効です。" End If ' ファイル名(拡張子なし)を取得 Set fso = CreateObject("Scripting.FileSystemObject") fileNameWithoutExt = fso.GetBaseName(sourceFilePath) Set fso = Nothing ' 出力ファイルパスを生成 exportFilePath = outputFolderPath & "\" & fileNameWithoutExt & EXPORT_EXTENSION ' --- 1. ドキュメントを開く --- LogMessage "ドキュメントを開いています: " & sourceFilePath Set doc = g_corelApp.OpenDocument(sourceFilePath) ' --- 2. エクスポート設定の取得と設定 --- LogMessage "エクスポート設定を取得しています..." Set pdfSettings = g_corelApp.CreatePDFSettings() ' PDF設定オブジェクトを作成 ' --- PDFエクスポート設定のカスタマイズ (例) --- ' 必要に応じて、ここでPDF設定のプロパティを調整してください。 ' 例: pdfSettings.ExportDocRange = cdrExportAllPages ' 例: pdfSettings.Compatibility = cdrPDFX1_2001 ' 例: pdfSettings.ColorProfile = "sRGB IEC61966-2.1" ' 例: pdfSettings.EmbedFonts = True ' 例: pdfSettings.MaintainCMYK = True ' CMYKを維持する場合 ' 例: pdfSettings.JPEGQuality = 90 ' JPEG圧縮品質 ' 例: pdfSettings.DownsampleImages = True ' 画像ダウンサンプリング ' 例: pdfSettings.ImageResolution = 300 ' 画像解像度 (dpi) ' --- 3. ファイルのエクスポート --- LogMessage "エクスポートを実行しています: " & exportFilePath doc.ExportAsFile exportFilePath, pdfSettings, GetExportFormatConstant(EXPORT_FORMAT) ' --- 4. 正常終了時のクリーンアップ --- LogMessage "エクスポート成功: " & exportFilePath ProcessSingleCDRFile = True ' 成功フラグを立てる CleanExit: ' --- ★★★ オブジェクトの解放処理 ★★★ --- ' エラー発生時、または正常終了時でも、必ず実行される Set pdfSettings = Nothing ' PDF設定オブジェクトを解放 If Not doc Is Nothing Then On Error Resume Next ' ドキュメントを閉じる際のエラーは無視 doc.Close SaveChanges:=False ' 変更を保存せずにドキュメントを閉じる If Err.Number <> 0 Then
LogErrorMessage “ドキュメントクローズ”, Err.Number, Err.Description, Err.Source & ” (ファイル: ” & sourceFilePath & “)”
End If
On Error GoTo 0
Set doc = Nothing ‘ ドキュメントオブジェクトへの参照を解放
End If
Exit Function ‘ ここで終了

ErrorHandler:
‘ — エラーハンドラ —
ProcessSingleCDRFile = False ‘ 失敗フラグを立てる
LogErrorMessage “ProcessSingleCDRFile”, Err.Number, Err.Description, Err.Source & ” (ファイル: ” & sourceFilePath & “)”
Resume CleanExit ‘ クリーンアップ処理へ移行
End Function

‘===============================================================================
‘ ヘルパー関数 (ファイル選択、フォルダ選択、ログ出力など)
‘===============================================================================

‘ FileDialogでファイルパスの配列を取得する
‘ multiSelect: Trueで複数選択可、Falseで単一選択
Private Function GetFilePathsFromDialog(ByVal title As String, Optional ByVal filter As String = “.”, Optional ByVal multiSelect As Boolean = False) As Variant
Dim fd As Object ‘ FileDialog
Dim selectedFiles() As String
Dim i As Long
Dim fso As Object

On Error GoTo ErrorHandler

Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.Title = title
.Filter = filter
.AllowMultiSelect = multiSelect
If .Show = -1 Then ‘ OKが押された場合
If multiSelect Then
‘ 複数選択の場合
Dim numSelected As Long
numSelected = .SelectedItems.Count
If numSelected > 0 Then
ReDim selectedFiles(0 To numSelected – 1)
For i = 1 To numSelected
selectedFiles(i – 1) = .SelectedItems(i)
Next i
GetFilePathsFromDialog = selectedFiles
Else
‘ 選択されたファイルがない場合 (空の配列を返す)
ReDim selectedFiles(-1)
GetFilePathsFromDialog = selectedFiles
End If
Else
‘ 単一選択の場合
ReDim selectedFiles(0)
selectedFiles(0) = .SelectedItems(1)
GetFilePathsFromDialog = selectedFiles
End If
Else
‘ キャンセルされた場合 (空の配列を返す)
ReDim selectedFiles(-1)
GetFilePathsFromDialog = selectedFiles
End If
End With

Set fd = Nothing
Exit Function

ErrorHandler:
LogErrorMessage “GetFilePathsFromDialog”, Err.Number, Err.Description, Err.Source & ” (Title: ” & title & “)”
ReDim selectedFiles(-1) ‘ エラー時は空の配列を返す
GetFilePathsFromDialog = selectedFiles
Set fd = Nothing
End Function

‘ FileDialogでフォルダパスを取得する
Private Function GetFolderPathFromDialog(ByVal title As String) As String
Dim fd As Object ‘ FileDialog
Dim selectedFolder As String

On Error GoTo ErrorHandler

Set fd = Application.FileDialog(msoFileDialogFolderPicker)
With fd
.Title = title
If .Show = -1 Then ‘ OKが押された場合
selectedFolder = .SelectedItems(1)
Else
selectedFolder = “” ‘ キャンセルされた場合
End If
End With

GetFolderPathFromDialog = selectedFolder
Set fd = Nothing
Exit Function

ErrorHandler:
LogErrorMessage “GetFolderPathFromDialog”, Err.Number, Err.Description, Err.Source & ” (Title: ” & title & “)”
GetFolderPathFromDialog = “” ‘ エラー時は空文字を返す
Set fd = Nothing
End Function

‘ 指定されたフォルダが存在するか確認し、なければ作成する
Private Function EnsureFolderExists(ByVal folderPath As String) As Boolean
Dim fso As Object
EnsureFolderExists = False ‘ デフォルトは失敗

On Error Resume Next ‘ エラーを無視して処理を進める
Set fso = CreateObject(“Scripting.FileSystemObject”)

If Not fso Is Nothing Then
If Not fso.FolderExists(folderPath) Then
On Error GoTo ErrorHandler ‘ エラーハンドラを設定
fso.CreateFolder folderPath
LogMessage “フォルダ作成: ” & folderPath
End If
EnsureFolderExists = True ‘ フォルダが存在するか作成できた
Else
LogMessage “FileSystemObjectの作成に失敗しました。”
End If

Set fso = Nothing
Exit Function

ErrorHandler:
LogErrorMessage “EnsureFolderExists”, Err.Number, Err.Description, Err.Source & ” (Folder: ” & folderPath & “)”
EnsureFolderExists = False ‘ エラー発生時は失敗
Set fso = Nothing
End Function

‘ エクスポートフォーマット文字列に対応するCorelDRAW定数を取得する
Private Function GetExportFormatConstant(formatName As String) As Long
Select Case UCase$(formatName)
Case “PDF”
GetExportFormatConstant = cdrPDF
Case “EPS”
GetExportFormatConstant = cdrEPS
Case “AI”
GetExportFormatConstant = cdrAI
Case “SVG”
GetExportFormatConstant = cdrSVG
‘ 他のフォーマットも必要に応じて追加
Case Else
Err.Raise 10001, “GetExportFormatConstant”, “未対応のエクスポートフォーマットです: ” & formatName
End Select
End Function

‘ ログメッセージをデバッグウィンドウとログファイルに書き込む
Public Sub LogMessage(message As String)
Dim fso As Object
Dim ts As Object

‘ デバッグウィンドウに表示
Debug.Print Now() & ” – ” & message

‘ ログファイルに追記
On Error Resume Next
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso Is Nothing Then
‘ ログファイルが存在しない場合は作成
If Not fso.FileExists(LOG_FILE_PATH) Then
Set ts = fso.CreateTextFile(LOG_FILE_PATH, True) ‘ True: Overwrite
Else
Set ts = fso.OpenTextFile(LOG_FILE_PATH, 8, True) ‘ 8: ForAppending
End If

If Not ts Is Nothing Then
ts.WriteLine Now() & ” – ” & message
ts.Close
End If
Set fso = Nothing
End If
On Error GoTo 0
End Sub

‘ エラーメッセージをデバッグウィンドウとログファイルに書き込む
Public Sub LogErrorMessage(source As String, errNum As Long, errDesc As String, errSource As String)
Dim logMessage As String
logMessage = “ERROR: Source='” & source & “‘, Number=” & errNum & “, Description='” & errDesc & “‘, Err.Source='” & errSource & “‘”

‘ デバッグウィンドウに表示 (赤字で強調)
Debug.Print “” & Now() & ” – ” & logMessage & “” ‘ HTMLタグはVBAのDebug.Printでは無視されます。実際にはログファイルに記録する際にフォーマットを考慮します。

‘ ログファイルに追記
Dim fso As Object
Dim ts As Object
On Error Resume Next
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso Is Nothing Then
If Not fso.FileExists(LOG_FILE_PATH) Then
Set ts = fso.CreateTextFile(LOG_FILE_PATH, True)
Else
Set ts = fso.OpenTextFile(LOG_FILE_PATH, 8, True) ‘ 8: ForAppending
End If

If Not ts Is Nothing Then
ts.WriteLine Now() & ” – ” & logMessage
ts.Close
End If
Set fso = Nothing
End If
On Error GoTo 0
End Sub

コードの実行方法

1. CorelDRAWを開き、VBAエディタ(Alt+F11)を開きます。
2. 「挿入」メニューから「標準モジュール」を選択し、上記のコードを貼り付けます。
3. `basMain.bas` と `basBatchProcess.bas` のように、モジュールを分割して保存することを推奨します。
4. `basBatchCDRProcessor.bas` モジュール内の `StartBatchProcessing` サブルーチンを実行します。
5. ファイル選択ダイアログ、フォルダ選択ダイアログが表示されるので、指示に従って操作してください。
6. 処理の進捗と結果は、VBAエディタの「イミディエイトウィンドウ」(Ctrl+G)と、指定されたログファイル(`C:\Logs\CorelDRAW_BatchProcess.log`)に出力されます。

【このコードのポイント】

  • モジュール分割: 機能ごとにモジュールを分割し、保守性と可読性を向上させています。
  • グローバル管理: `g_corelApp` と `g_isAppInitialized` でCorelDRAWアプリケーションのライフサイクルを一元管理します。
  • エラーハンドリングの徹底: 各サブルーチン・関数に `On Error GoTo ErrorHandler` を設定し、エラー発生時には必ずクリーンアップ処理(オブジェクト解放)を行うように設計しています。
  • ログ出力: `LogMessage` と `LogErrorMessage` 関数により、処理の進捗とエラーの詳細を記録し、デバッグや監視を容易にします。
  • `DoEvents` の活用: 大量処理のループ内で `DoEvents` を挟むことで、UIの応答性を維持し、バックグラウンドでのCOMオブジェクト解放を促します。
  • `FileSystemObject` の利用: パス操作やフォルダ作成に `FileSystemObject` を使用し、OS間の互換性を高めています。
  • `Set obj = Nothing` の適用: 各オブジェクトのスコープ終了時、またはエラー発生時のクリーンアップ処理において、明示的に `Set obj = Nothing` を実行し、参照を破棄しています。
  • ドキュメントのクローズ: `doc.Close SaveChanges:=False` で、開いたドキュメントを保存せずに閉じ、リソースを解放しています。

5. まとめ – 信頼性の高い自動化のために

CorelDRAW VBAにおけるCOMオブジェクトの参照リークとメモリ管理は、一見すると複雑で厄介な問題に思えるかもしれません。しかし、COMオブジェクトのライフサイクルを深く理解し、プロシージャレベルでの厳格な参照破棄、そして商用レベルの例外処理を設計に組み込むことで、これらの問題を完全に撲滅することが可能です。

今回ご紹介した設計思想とコード例は、単にバグを減らすだけでなく、「いつ、どこで、何が起きたのか」を明確に追跡できる、信頼性の高い自動化ツールの基盤となります。

大規模なバッチ処理は、開発者のスキルと設計思想が試される領域です。本稿が、皆さんの開発プロジェクトにおける業務効率化を、より堅牢かつ安全に進めるための一助となれば幸いです。

不明な点があれば、いつでも質問してください。開発プロジェクトの成功を、心より応援しています。

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