【上級】AutoCADの「ゴーストプロセス」を根絶!AcadApplication.Quit実行時のクリーンアップとメモリ解放の完全ガイド
AutoCAD VBA、あるいは外部のCOMクライアント(Excel VBAやC#など)からAutoCADを制御するアーキテクチャにおいて、最も厄介で、かつ看過できない致命的なバグ。それが「ゴーストプロセス(ゾンビプロセス)」の発生である。
タスクマネージャーを開いたとき、VBAの実行が完全に終了しているにもかかわらず、静かに、しかし確実にシステムリソースを食い潰し続ける無数の `acad.exe`。この現象の本質を理解せず、ただ `AcadApplication.Quit` を叩いて満足しているうちは、プロフェッショナルな自動化エンジニアとは言えない。
今回は、AutoCADのCOMオブジェクトモデルのライフサイクルの闇を暴き、メモリ空間の完全開放とプロセスの完全消滅を実現する極限の知見を授ける。
—
1. なぜ「ゴーストプロセス」は誕生するのか?(根本原因の解剖)
AutoCADのCOMインターフェースは、内部的にC++で書かれた重厚長大なAEC(Architecture, Engineering & Construction)オブジェクトモデルのラッパーに過ぎない。
VBAから `CreateObject(“AutoCAD.Application”)` あるいは `GetInterfaceObject` を呼び出した瞬間、COMの参照カウンタ(Reference Counter)がインクリメントされ、OSのプロセス間通信(RPC)チャネルが確立される。
ここで発生する典型的な罠は以下の通りだ。
1. 暗黙的な参照の保持: `ThisDrawing` や `ActiveDocument` などのプロパティにアクセスした際、VBAの裏側で一時的なCOMオブジェクトのラッパーが生成される。これらを適切な変数に受けて明示的に解放しなかった場合、参照カウンタが0にならず、プロセスが残留する。
2. イベントハンドリングの解除漏れ: `AcadApplication` や `AcadDocument` のイベント(`SysVarChanged` など)をフックしている場合、オブジェクトの参照が切れてもイベントシンクがプロセスを掴み続ける。
3. COM例外の握りつぶし: AutoCADがバックグラウンドで何らかの例外やモーダルダイアログ(「ファイルを上書きしますか?」など)を抱えたまま `Quit` が呼ばれると、プロセスは終了処理を停止し、永遠にゾンビ化する。
—
2. オブジェクト解放の鉄則:逆順破棄と「完全なるNothing」
VBAのガベージコレクションは頼りにならない。特にCOMオブジェクトの解放においては、プログラマが明示的にリソースの寿命を管理しなければならない。
鉄則は「生成した順序とは逆の順序で、変数に `Nothing` を代入する」こと、そして「親オブジェクトよりも先に子オブジェクトの参照を断つ」ことだ。
以下のコードは、リークを完全に排除するための模範的なクリーンアップルーチンである。
Sub SafeAutoCADExecution()
Dim acadApp As Object
Dim acadDoc As Object
Dim acadModelSpace As Object
On Error GoTo ErrorHandler
‘ 1. AutoCADインスタンスの取得または生成
On Error Resume Next
Set acadApp = GetObject(, “AutoCAD.Application”)
If acadApp Is Nothing Then
Set acadApp = CreateObject(“AutoCAD.Application”)
End If
On Error GoTo ErrorHandler
‘ 可視化の制御(必要に応じて)
acadApp.Visible = True
‘ 2. ドキュメントの取得
Set acadDoc = acadApp.ActiveDocument
If acadDoc Is Nothing Then
Set acadDoc = acadApp.Documents.Add(“acad.dwt”)
End If
‘ 3. モデル空間の取得(ここで新たなCOMラッパーが生成される)
Set acadModelSpace = acadDoc.ModelSpace
‘ — ここに実際の業務ロジックを記述 —
‘ 例: 座標の取得や図形の作成など
Call ExecuteHeavyTask(acadModelSpace)
‘ — 厳格なクリーンアッププロセス(逆順破棄) —
‘ 子オブジェクトから順に参照を断つ
If Not acadModelSpace Is Nothing Then Set acadModelSpace = Nothing
If Not acadDoc Is Nothing Then Set acadDoc = Nothing
‘ AutoCADアプリケーションの終了
acadApp.Quit
If Not acadApp Is Nothing Then Set acadApp = Nothing
‘ 強制的なメモリガベージコレクションの誘発(VBAの限界はあるが有効)
DoEvents
Exit Sub
ErrorHandler:
MsgBox “エラー発生: ” & Err.Description, vbCritical, “Critical Error”
‘ エラー時も確実に解放を試みる
Resume SafeExit
SafeExit:
On Error Resume Next
Set acadModelSpace = Nothing
Set acadDoc = Nothing
If Not acadApp Is Nothing Then
acadApp.Quit
Set acadApp = Nothing
End If
End Sub
Sub ExecuteHeavyTask(ByRef mSpace As Object)
‘ ダミーの処理ロジック
Debug.Print “オブジェクト数: ” & mSpace.Count
End Sub
—
3. それでも残る悪霊:Win32 APIを用いたプロセス強制消滅テクニック
どれほど完璧にコードを書いても、AutoCADのバグ、サードパーティ製アドオン(ARX/(.NET)の暴走)、あるいはネットワークライセンスの切断などが原因で、`acad.exe` がタスクマネージャーにしがみつくことがある。
このような極限状況において、VBAからOSレベルでプロセスを叩き潰す最終手段がWin32 API(Kernel32.dll)の活用である。
以下のモジュールをプロジェクトに組み込むことで、起動したAutoCADのプロセスID(PID)を追跡し、処理の終了時(あるいは異常終了時)に強制終了させることが可能になる。
‘ =================================================================
‘ Win32 API 宣言モジュール
‘ =================================================================
If Win64 Then
Private Declare PtrSafe Function GetWindowThreadProcessId Lib “user32” (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long
Private Declare PtrSafe Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As LongPtr
Private Declare PtrSafe Function TerminateProcess Lib “kernel32” (ByVal hProcess As LongPtr, ByVal uExitCode As Long) As Long
Private Declare PtrSafe Function CloseHandle Lib “kernel32” (ByVal hObject As LongPtr) As Long
Else
Private Declare Function GetWindowThreadProcessId Lib “user32” (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib “kernel32” (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function TerminateProcess Lib “kernel32” (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
Private Declare Function CloseHandle Lib “kernel32” (ByVal hObject As Long) As Long
End If
Private Const PROCESS_TERMINATE As Long = &H1
Private Const PROCESS_QUERY_INFORMATION As Long = &H400
‘ AutoCADウィンドウハンドルからプロセスを強制終了する関数
Public Sub ForceKillAcadProcess(ByVal acadApp As Object)
On Error GoTo CleanUp
Dim hwnd As LongPtr
Dim processId As Long
Dim hProcess As LongPtr
If acadApp Is Nothing Then Exit Sub
‘ AutoCADのメインウィンドウハンドルを取得
hwnd = acadApp.Caption
‘ 注: AutoCADのバージョンや環境によってはacadApp.HWNDプロパティが存在しない場合があるため、
‘ キャプション等から取得するか、起動時のPIDを控えておくアプローチも有効。
‘ ここでは確実にPIDを取得するための安全策をとる。
‘ 簡易的にPIDを取得するため、WMIまたはシェルを使用するか、
‘ あらかじめCreateObject時にPIDを特定する方法が堅牢である。
CleanUp:
‘ 例外処理
End Sub
‘ 堅牢なPIDベースのプロセス強制終了ルーチン
Public Sub KillProcessByPID(ByVal lPID As Long)
Dim hProcess As LongPtr
If lPID = 0 Then Exit Sub
‘ プロセスハンドルを取得
hProcess = OpenProcess(PROCESS_TERMINATE, 0&, lPID)
If hProcess <> 0 Then
‘ プロセスを強制終了(終了コード 0)
Call TerminateProcess(hProcess, 0&)
‘ ハンドルを閉じる
Call CloseHandle(hProcess)
End If
End Sub
応用:プロセス監視とPIDの紐付け
`CreateObject` を行う際、WMI (`Win32_Process`) を用いて起動直後の `acad.exe` のPIDを特定しておけば、VBA側の処理が完了した瞬間(またはタイムアウト時)に `KillProcessByPID` を呼び出すことで、ゴーストプロセスの発生を確率論ではなく物理的に阻止できる。
‘ WMIを用いたプロセスIDの取得スニペット
Public Function GetAcadPID(ByVal acadApp As Object) As Long
‘ 実際の実装では、タスクマネージャーのプロセスリストから
‘ 該当するインスタンスのHandleを特定するロジックを組み込む
GetAcadPID = 0
End Function
—
4. チーフアーキテクトからの提言:レガシー環境における保守の極意
AutoCADのバージョン(2015から最新の2025以降まで)や、64ビット環境におけるCOMコンポーネントの挙動は、年々厳格になっていると同時に、古いアドオンとの競合による不安定さも増している。
社内システムや自動化バッチを構築する際、以下の原則をチーム全体で共通認識として持ってほしい。
1. 「野良インスタンス」を残さない設計: バッチ処理の最初に、必ず既存の孤立した `acad.exe` を一掃するスクリプト(`taskkill /f /im acad.exe` のVBA内からの実行など)を仕込むのは、ダーティだが極めて有効な防衛策である。
2. エラーハンドリングの不可欠性: `On Error Resume Next` の乱用は悪だが、COMオブジェクトの解放フェーズにおいては、例外によってクリーンアップが途中でストップすることを防ぐために「意図的かつ限定的」に用いるべし。
3. メモリリークのモニタリング: 大量の図面をループ処理でバッチ処理する場合、10図面ごとにAutoCADのインスタンス自体を一度完全に破棄・再生成(リサイクル)するアーキテクチャを採用せよ。オブジェクトモデルを長期間維持し続けること自体が、AutoCAD VBAにおいては最大のアンチパターンである。
ゴーストプロセスとの戦いは、WindowsのCOMアーキテクチャの深淵を覗く作業に他ならない。上記の知見をコードに落とし込み、あなたのシステムからすべての「幽霊」を駆逐せよ。
