【上級】AcadApplicationのCOM参照を完全に解放し、タスクマネージャにAutoCADを残さない終了処理
開発現場でこんな恐怖体験をしたことはないだろうか。
「VBAのマクロが無事に処理を終えて終了したはずなのに、Windowsのタスクマネージャを開くと、背後で『acad.exe』が幽霊のように居座り続けている……」
このゾンビプロセス問題は、AutoCAD VBA開発者が避けて通れない最初の、そして最大の壁だ。放置すればメモリはジワジワと侵食され、やがてPCはフリーズするか、バッチ処理の途中でCOMエラーが連鎖してすべてが崩壊する。
なぜ、正しくコードを書いたつもりでもAutoCADはメモリから消えないのか?
今回は、COMの参照カウンタとVBAのガベージコレクションの裏側を完全に見通し、「絶対にacad.exeを残さない」ための極限のクリーンアップ設計をチーフアーキテクトの私から授けよう。
—
1. なぜ「ゾンビプロセス」は生まれるのか?(COMの構造的欠陥)
VBAから外部アプリケーション(AutoCAD)を操作する場合、私たちはCOM(Component Object Model)という橋を渡って`AcadApplication`オブジェクトを生成している。
ここで重要となるのが、「COM参照カウンタ(Reference Counter)」の概念だ。
VBA側で変数を宣言し、オブジェクトを代入するたびに参照カウントが「+1」され、不要になったら解放して「-1」していく。このカウントが「0」になった瞬間、OSはプロセスを消滅させる。
しかし、以下のアンチパターンを踏むと、参照カウントが0にならず、プロセスが迷子になる。
1. ドットつなぎ(メソッドチェーン)の多用
`ThisDrawing.ModelSpace.AddLine(…)` のように、変数に受けていない一時オブジェクトが無数に生成され、VBAの裏で参照が取り残される。
2. エラーハンドリングの欠如
処理途中で実行時エラー(Runtime Error)が発生した際、クリーンアップ処理(`Set app = Nothing` 等)がスキップされ、スタック上の参照が保持されたままになる。
3. 不完全なQuitメソッドの呼び出し
`Application.Quit` を叩いても、変数側がオブジェクトを握りしめたままだと、AutoCADのプロセスは完全終了のシグナルを受け取れない。
—
2. 堅牢な終了処理の鉄則:3つのステップ
AutoCADを確実に終了させるには、以下の3原則を厳守しなければならない。
- 原則1:必ずエラーハンドラー(`On Error GoTo`)を実装する
予期せぬエラーでも確実に終了処理(Finalizer)へジャンプさせよ。
- 原則2:オブジェクトの解放は「生成と逆の順序」で行う
Document、ModelSpaceなどの子から親へ向かって `Set = Nothing` を伝播させ、最後に `AcadApplication` を解放する。
- 原則3:明示的な `Quit` の後に、徹底的な参照断ちを行う
—
3. 【プロダクションコード】実務で使える完璧なモジュールテンプレート
以下に、実務のバッチ処理や外部連携ツールでそのまま使える、極限まで堅牢性を高めたVBAコードを提示する。エラーが起きようとも、絶対にAutoCADをゾンビ化させない設計だ。
Option Explicit
Public Sub ExecuteAutoCADAutomation()
Dim acadApp As Object
Dim acadDoc As Object
Dim isAppStarted As Boolean
‘ 【原則1】エラーハンドラの設定(例外時も確実にクリーンアップへ誘導)
On Error GoTo ErrorHandler
isAppStarted = False
‘ AutoCADのインスタンスを取得(起動していなければ新規起動、していればアタッチ)
On Error Resume Next
Set acadApp = GetObject(, “AutoCAD.Application”)
If acadApp Is Nothing Then
Set acadApp = CreateObject(“AutoCAD.Application”)
isAppStarted = True
End If
On Error GoTo ErrorHandler ‘ エラー監視を復旧
‘ 可視化の制御(バックグラウンド処理ならFalseにすると高速化するが、今回は目視確認用にTrue)
acadApp.Visible = True
‘ ドキュメントを開く、または新規作成
‘ ※実務ではパスの存在チェック等をここに挟むこと
Set acadDoc = acadApp.Documents.Add()
‘ ==========================================
‘ ここに実際の業務処理を記述
‘ 例:レイヤの作成や図形の描画など
‘ ==========================================
MsgBox “AutoCADの処理が完了しました。”, vbInformation, “成功”
‘ 正常終了時のクリーンアップへ
GoTo CleanUp
ErrorHandler:
‘ 異常発生時のログ出力・ユーザー通知
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “実行時エラー”
CleanUp:
‘ 【原則2 & 3】確実な参照の解放とプロセスの終了
‘ 1. ドキュメントオブジェクトの解放
If Not acadDoc Is Nothing Then
‘ 必要に応じて保存処理
‘ acadDoc.SaveAs “C:\Work\Output.dwg”
Set acadDoc = Nothing
End If
‘ 2. アプリケーションの終了(自分で起動した場合のみQuitを呼ぶ設計も可だが、基本は明示的に終了)
If Not acadApp Is Nothing Then
On Error Resume Next
‘ 変更が保存されていないドキュメントがある場合のダイアログを抑制
acadApp.Documents.Close
acadApp.Quit
On Error GoTo 0
‘ 3. 最重要:アプリケーション変数の完全破棄
Set acadApp = Nothing
End If
‘ ガベージコレクションを促す(VBAの仕様上、即時解放されないケースへの保険)
DoEvents
Exit Sub
End Sub
—
4. チーフアーキテクトからの実践的アドバイス
なぜ `GetObject` と `CreateObject` の併用が必要なのか?
業務ツールにおいて、「すでに立ち上がっているAutoCADのセッションを再利用する」のか、「専用のインスタンスを孤立して立ち上げる」のかは極めて重要だ。上記のコードは、既存セッションがあればそれを掴み、なければ新規起動する最も安全なフォールバックパターンを採用している。
VBAの `Set = Nothing` の限界と `DoEvents`
VBAのメモリ管理はVB.NET(CLR)に比べてプリミティブだ。`Set acadApp = Nothing` を実行しても、背後のCOM RCW(Runtime Callable Wrapper)が即座に破棄されない瞬間がある。
そのため、直後に `DoEvents` を挟むことで、Windowsメッセージキューを処理させ、COMの解放割り込みを確実に実行させることがプロの技だ。
タスクマネージャに残った場合の最終手段
もし、開発中のデバッグミスなどで既にゾンビプロセスが蓄積している場合は、VBA側からではなく、Windowsのコマンドプロセスの力を借りて一掃するのが手っ取り早い。
タスクを強制終了させるVBAスニペットを付記しておこう。
Sub KillZombieAutoCAD()
‘ 悪名高いタスクキルコマンドをVBAから実行し、強制的にゴミ掃除を行う
Shell “cmd.exe /c taskkill /f /im acad.exe”, vbHide
End Sub
(※注意:このコマンドは現在ユーザーが手動で編集しているAutoCADも含めて強制終了させるため、本番運用時は十分に注意して組み込むこと)
—
総括
AutoCAD VBAによる自動化の成否は、「いかにきれいに終わるか(美しき終活)」にかかっている。
動くコードを書くだけなら素人でもできる。しかし、何百回回してもメモリリークせず、タスクマネージャを汚さない「プロフェッショナルなコード」を書くことこそが、私たちエンジニアのプライドだ。
今日からあなたのコードの終了処理を見直し、ゾンビプロセスを完全に駆逐してほしい。
