【実務・中級編】【上級】AutoCADの「ゴーストプロセス」を根絶!AcadApplication.Quit実行時のクリーンアップとメモリ解放の完全ガイド – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:ゴーストプロセスを根絶せよ

AutoCAD VBA、あるいは外部VBA/VB.NETからCOMインターフェースを介したAutoCADの制御において、開発者を最も絶望させる現象が何か知っているか?

それは、「コードの実行が完了し、VBAのIDEが閉じた後も、タスクマネージャーの裏でひそかに生き続け、PCのメモリを喰らい続ける『ゴーストプロセス(acad.exe)』」だ。

このゾンビのようなプロセスは、単にリソースを無駄遣いするだけではない。図面ファイルのロックを保持し続け、次回のバッチ処理で「ファイルが使用中です」という致命的なエラーを引き起こす。バックグラウンドで複数起動しようものなら、デスクトップPCのCPU使用率は跳ね上がり、開発プロジェクト全体を暗礁に乗り上げさせる。

なぜゴーストプロセスは生まれるのか?
そして、どうすればそれを完全に根絶できるのか?

世界最高峰の業務自動化を志すエンジニアたちへ向け、AutoCADのオブジェクトモデルの裏側にあるCOMのライフサイクルと、メモリ解放の極限の知見を授けよう。

1. なぜゴーストプロセスは生まれるのか?(COMの参照カウンタの闇)

VBAから `CreateObject(“AutoCAD.Application”)` あるいは `GetObject` を実行した瞬間、OSのCOM(Component Object Model)層でAutoCADのプロセスが立ち上がり、「参照カウンター(Reference Counter)」が1つインクリメントされる。

その後、`ActiveDocument` や `ModelSpace`、`SelectionSet` といった派生オブジェクトを取得するたびに、COMの内部では親オブジェクトへの参照が連鎖的に発生する。

ここで多くの素人が犯す最大の過ちがこれだ。

‘ 【アンチパターン】これでは絶対にプロセスは消えない
Sub BadExample()
Dim acadApp As Object
Set acadApp = CreateObject(“AutoCAD.Application”)
acadApp.Visible = True

Dim acadDoc As Object
Set acadDoc = acadApp.Documents.Add()

‘ 処理色々…

acadApp.Quit
‘ 変数解放をサボる、あるいは順序が滅茶苦茶
End Sub

VBAの変数スコープを抜けた瞬間に自動でメモリが解放されるとでも思ったら大間違いだ。COMオブジェクトはVBAのガベージコレクションだけでは完全に制御しきれない。親(Application)を解放する前に子(Document等)の参照がメモリ上にわずかでも残っていると、COMの参照カウンターがゼロにならない。結果、「ウィンドウは消えたがプロセス(acad.exe)だけが幽霊のように残る」という最悪のバグが完成するのだ。

2. 堅牢なクリーンアップの鉄則:逆順解放と `Nothing` の儀式

ゴーストプロセスを根絶するための原則はたった一つ。
「取得した順番の【逆順】でオブジェクトを明示的に解放し、最後に `Quit` を叩く」ことだ。

さらに、VBAの変数に `Nothing` を代入するだけでは、COMのポインタが即座に解放される保証はない。確実を期すためには、以下のステップを厳守しなければならない。

1. 末端のオブジェクト(SelectionSet, Layer, Blockなど)の明示的解放
2. ドキュメントオブジェクト(AcadDocument)の解放
3. アプリケーションオブジェクト(AcadApplication)の解放(ここで初めて `.Quit` を検討、あるいは事前に実行)
4. すべての変数への `Set ◯◯ = Nothing` の徹底

3. 【プロダクションコード】ゴーストプロセスを完全駆逐する堅牢設計

実務の現場でそのまま使える、堅牢性・耐障害性を極限まで高めたプロシージャを提示する。エラーハンドリング(`On Error GoTo`)を組み込み、途中で例外が発生しても確実にメモリが掃除される構造にしている点がポイントだ。

Option Explicit

”’

”’ AutoCADを安全に起動・操作し、ゴーストプロセスを残さずに完全終了させる模範ルーチン
”’

Sub ExecuteAutoCADAutomationSafe()
Dim acadApp As Object
Dim acadDoc As Object
Dim isAppCreated As New Boolean

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”)
isAppCreated = True
End If
On Error GoTo ErrorHandler ‘ エラー監視を復帰

‘ 可視化(必要に応じて)
acadApp.Visible = True

‘ 2. 図面の新規作成またはオープン
Set acadDoc = acadApp.Documents.Add(“acadiso.dwt”)

‘ — ここに実際の業務処理を記述 —
MsgBox “AutoCADの処理を実行中…”, vbInformation, “Automation”
‘ ———————————-

‘ 3. 正常終了時のクリーンアップ手順(生成した逆順で攻める)

‘ ① ドキュメントを保存せずに閉じる場合(必要に応じて SaveAs を挟む)
If Not acadDoc Is Nothing Then
‘ acadDoc.Close False ‘ 変更を破棄して閉じる場合
Set acadDoc = Nothing ‘ 内存参照を即座に断つ
End If

‘ ② アプリケーションの終了
If Not acadApp Is Nothing Then
‘ 自前で起動したインスタンスの場合のみ Quit を呼ぶ判断もアリ
acadApp.Quit
Set acadApp = Nothing
End If

MsgBox “プロセスは正常にクリーンアップされました。”, vbInformation
Exit Sub

ErrorHandler:
‘ 4. 異常終了時(エラー発生時)の強制クリーンアップ
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”

‘ 逆順解放の徹底
On Error Resume Next
If Not acadDoc Is Nothing Then Set acadDoc = Nothing
If Not acadApp Is Nothing Then
acadApp.Quit
Set acadApp = Nothing
End If
On Error GoTo 0

End Sub

4. 最終防衛ライン:Win32 APIを用いたプロセス強制終了(力技と知恵)

どれほどコードを美しく書いても、AutoCADの内部アドイン(VBAマクロやARXアプリケーション)のバグや、COM自体のフリーズによって `acad.exe` が沈黙を拒絶することが稀にある。

そんなときの「最終防衛ライン」として、Win32 APIを駆使して背後で居座るプロセスを強制刈り取りするテクニックを仕込んでおこう。

以下のコードを標準モジュールの最上部に記述する。

If VBA7 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
‘ 32bit環境互換(必要に応じて)
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

Const PROCESS_TERMINATE As Long = &H1

そして、オブジェクトのウィンドウハンドル(`HWND`)からプロセスIDを割り出し、容赦なくプロセスをキルする関数を実装する。

”’

”’ AcadApplicationのウィンドウハンドルからプロセスを強制終了する
”’

Public Sub ForceKillAcadProcess(ByVal acadApp As Object)
On Error Resume Next
If acadApp Is Nothing Then Exit Sub

Dim hwnd As LongPtr
hwnd = acadApp.hWnd

If hwnd <> 0 Then
Dim processId As Long
GetWindowThreadProcessId hwnd, processId

If processId <> 0 Then
Dim hProcess As LongPtr
hProcess = OpenProcess(PROCESS_TERMINATE, 0&, processId)
If hProcess <> 0 Then
TerminateProcess hProcess, 0&
CloseHandle hProcess
End If
End If
End If
On Error GoTo 0
End Sub

これを通常の `.Quit` や変数解放のフェーズで「どうあがいてもゾンビ化する環境」の保険として組み込んでおけば、社内のユーザーから「またAutoCADが固まった・裏で動き続けて次に進めない」というクレームが来ることは二度となくなる。

結言:プロフェッショナルのコードとは

動くだけのコードは、素人でも書ける。
しかし、「異常系を踏んでもリソースを汚さず、何回バッチを回してもOSを完璧な状態で保ち続けるコード」こそが、プロフェッショナルエンジニアの手によるプロダクションコードだ。

AutoCAD VBAにおけるオブジェクトのライフサイクル管理は、ハードウェアの制約とOSのCOM仕様に向き合うシビアな領域である。今回伝授した「逆順での `Nothing` 解放」と「Win32 APIによる最終防衛」の思想をあなたのソリューションに組み込み、ゴーストプロセスを完全に駆逐せよ。

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