【テクニカル・上級編】【上級プロフェッショナル向け】AutoCADの「マルチインスタンス」を跨いだドキュメント転送と同期保存のアーキテクチャ – AutoCAD VBA解析バイブル

スポンサーリンク
スポンサーリンク

幽霊プロセスを飼い慣らす:AutoCADマルチインスタンス間におけるドキュメント転送と同期保存の極限アーキテクチャ

多くのエンジニアは、AutoCAD VBAを単なる「作図の自動化ツール」と考えている。しかし、エンタープライズレベルの設計環境において、AutoCADは単一のアプリケーションではない。複数の`acad.exe`がメモリを食い合い、COMインターフェースが複雑に絡み合う「分散プロセスの集合体」である。

特に、意図的にマルチインスタンス(複数のAutoCADプロセス)を起動させている環境で、特定の図面を別のインスタンスへ転送し、整合性を保ったまま同期保存させるというタスクは、標準的なリファレンスを読んでいるだけでは到達できない領域だ。

ここでは、`GetObject`の限界を超え、Windows APIを用いてプロセスを特定し、COMオブジェクトのライフサイクルを完全に制御することで、堅牢なドキュメント転送を実現するアーキテクチャを解説する。

1. `GetObject`の欺瞞とマルチインスタンスの壁

通常、VBAでAutoCADを操作する場合、`GetObject(, “AutoCAD.Application”)` を使用する。しかし、これは「OSが認識している最初に見つかったインスタンス」を返すに過ぎない。

複数のAutoCADが起動している場合、どのインスタンスがどの図面を開いているか、そしてどのプロセスに命令を飛ばすべきかを制御する手段が標準機能には存在しない。この不確実性を放置したまま実装を行うと、意図しないインスタンスで図面が開き、ユーザーの作業を破壊するか、あるいはメモリ上に「幽霊プロセス(ゾンビプロセス)」を残す結果となる。

真の制御に必要なアプローチ

特定のインスタンスを掌握するためには、COMレイヤーの上位にあるWindows APIを用いて、ウィンドウハンドル(HWND)からプロセスID(PID)を特定し、そこからCOMオブジェクトへ逆引きするルートを構築する必要がある。

2. 極限の実装:プロセス特定とインスタンス・バインド

以下のコードは、ウィンドウタイトルから特定のAutoCADインスタンスを特定し、そのアプリケーションオブジェクトを捕捉するためのコアロジックである。

Option Explicit

' --- Windows API 定義 ---
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
    ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr

Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" ( _
    ByVal hWnd As LongPtr, ByRef lpdwProcessId As Long) As Long

' 特定の図面名を持つAutoCADインスタンスを捕捉するクラス
Public Class AutoCADInstanceManager
    
    ' 指定した図面名が開かれているAutoCADインスタンスを返す
    Public Function GetInstanceByDrawingName(ByVal targetDrawingName As String) As AcadApplication
        Dim hWnd As LongPtr
        Dim pid As Long
        Dim app As AcadApplication
        
        ' AutoCADのウィンドウタイトルは通常 "図面名 [AutoCAD]" の形式
        ' 完全一致ではなく、部分一致での探索が必要なため、
        ' 実際にはEnumWindowsで全ウィンドウを走査するのが定石だが、
        ' ここでは簡略化のためFindWindowの概念を示す
        hWnd = FindWindow("AutoCAD", targetDrawingName & " - AutoCAD")
        
        If hWnd = 0 Then
            Err.Raise vbObjectError + 513, "InstanceManager", "指定された図面を持つインスタンスが見つかりません。"
        End If
        
        ' ウィンドウハンドルからプロセスIDを取得
        GetWindowThreadProcessId hWnd, pid
        
        ' ここが重要:Running Object Table (ROT) から
        ' 特定のPIDに紐づくCOMオブジェクトを抽出する処理が必要となる。
        ' VBAのGetObjectは不十分なため、実務では外部DLLまたは
        ' 独自のレジストリキーを介したハンドシェイクを実装する。
        
        On Error Resume Next
        Set app = GetObject(, "AutoCAD.Application") 
        ' ※実際には、ここでPIDの一致を確認するラッパー関数を介在させる
        On Error GoTo 0
        
        Set GetInstanceByDrawingName = app
    End Function
End Class

3. ドキュメント転送と同期保存の同期プロトコル

インスタンスを特定した後、データを安全に転送し保存するための「同期プロトコル」を設計しなければならない。単純に `SaveAs` を呼ぶだけでは、ファイルロック(`.dwl` / `.dwl2`)の競合が発生し、保存失敗やデータの破損を招く。

原子的な保存(Atomic Save)のフロー

1. 排他制御の確保: 保存先ファイルへの書き込み権限を確認し、他インスタンスが掴んでいないかチェックする。
2. メモリ上のクリーンアップ: `Purge` および `Audit` を強制的に実行し、不整合なデータを排除する。
3. 一時ファイルへの書き出し: 直接上書きせず、一度 `.tmp` ファイルとして保存し、OSレベルの `MoveFile` APIで置換する。これにより、保存中のクラッシュによるファイル消失を防ぐ。

' --- 同期保存の極限実装 ---
Public Sub SynchronizedSave(ByRef targetApp As AcadApplication, ByVal fileName As String)
    Dim doc As AcadDocument
    Set doc = targetApp.ActiveDocument
    
    On Error GoTo ErrorHandler
    
    ' 1. データベースの整合性確保 (Audit)
    ' VBAからはSendCommandを介して内部コマンドを叩くのが最も確実
    doc.SendCommand "-AUDIT" & vbCr & "Y" & vbCr
    
    ' 2. 不要なオブジェクトの完全排除 (Purge)
    doc.SendCommand "-PURGE" & vbCr & "A" & vbCr & "*" & vbCr & "N" & vbCr
    
    ' 3. 保存処理 (SaveAs)
    ' ファイルバージョン互換性を担保するため、SaveAsの引数を厳密に制御
    ' 2018形式(ACAD2018)など、環境に合わせた定数を指定
    doc.SaveAs fileName, ac2018_dwg
    
    Debug.Print "Instance " & targetApp.Application.Caption & " synchronized successfully."

CleanUp:
    ' --- オブジェクトのライフサイクル管理 ---
    ' ここを怠ると、AutoCADプロセスがメモリに残り続け、サーバーのリソースを食いつぶす
    Set doc = Nothing
    Set targetApp = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "Critical Error during Sync Save: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

4. メモリ最適化と「ゾンビプロセス」の殲滅

AutoCAD VBAにおいて、最も忌むべきはCOM参照の循環である。
`AcadApplication` $\rightarrow$ `AcadDocument` $\rightarrow$ `AcadEntity` と参照を辿った後、適切に `Nothing` を代入しなかった場合、AutoCADを閉じても `acad.exe` がタスクマネージャーに残り続ける。

チーフアーキテクトの鉄則

  • 明示的な解放: `Set obj = Nothing` は単なる作法ではなく、メモリ管理の「命令」である。
  • 逆順解放: 参照した順番の逆から解放せよ。Entity $\rightarrow$ Document $\rightarrow$ Application。
  • イベントハンドラの切断: `WithEvents` を使用している場合は、インスタンス破棄前に必ずイベント接続を解除せよ。

5. レガシー環境への配慮と今後の展望

現在のAutoCADは `.NET API` への移行が進んでいるが、依然として多くの現場でVBAが基幹システムとして生き残っている。その理由は、Excelとの親和性と、配布の容易さにある。

しかし、真に堅牢なシステムを構築したいのであれば、VBAを「UIおよびオーケストレーター」として利用し、重いプロセス制御やIPC(プロセス間通信)は C# / .NET の外部DLL に委譲し、それをVBAから呼ぶハイブリッドアーキテクチャを推奨する。

結論

マルチインスタンスを跨ぐ制御とは、AutoCADというアプリケーションを操作することではなく、Windows OS上でのプロセス管理を操作することに他ならない。

APIによるウィンドウハンドルの捕捉、原子的なファイル操作、そして執拗なまでのオブジェクト解放。この三位一体が揃って初めて、プロフェッショナルと呼べる自動化システムが完成する。

退屈なリファレンスの実装で満足せず、OSの深淵に潜り込め。そこにこそ、真の最適化がある。

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