AutoCAD VBAを掌握する極限の知見:`WithEvents`による`AcadApplication`イベント駆動アーキテクチャと外部DBリアルタイム連携
レガシーとモダンが交錯するエンタープライズCAD開発の現場において、AutoCAD VBAは単なる「お絵描きマクロ」の域をとうに脱している。我々シニアエンジニアが求められるのは、数千枚の図面管理、PDM/ERPとのシームレスな統合、そしてCADの挙動をミリ秒単位で制御するイベント駆動システムの構築だ。
今回は、`AcadApplication`オブジェクトのライフサイクルと`WithEvents`を極限まで使い倒し、図面保存やコマンド終了をトリガーに外部データベース(SQL Server)へ非同期かつ堅牢にメタデータを書き込む、実戦投入レベルのアーキテクチャを解説する。
—
1. AutoCAD VBAにおけるイベントモデルの暗部と設計哲学
多くのVBAプログラマは、`ThisDrawing`モジュールに記述するイベント(`AcadDocument`のイベント)で満足する。しかし、複数図面(MDI環境)の横断、新規図面の作成、あるいはアプリケーション全体のライフサイクルを監視するには、`AcadApplication`レベルでのイベントフックが不可欠となる。
ここで遭遇するのが、AutoCAD VBA特有の「メモリ管理の罠」と「COMイベントの非同期制約」だ。
イベントハンドラ内で重い処理(SQL Serverへの接続やクエリ発行)を同期実行すると、AutoCADのUIスレッドがブロックされ、最悪の場合は致命的なクラッシュ(FATAL ERROR)を引き起こす。
この課題をクリアするためには、以下のアーキテクチャ設計が必須となる。
1. クラスモジュールによるイベントの独立(`WithEvents`の活用)
2. Windows APIを活用した安全な非同期処理またはキューイング
3. COMオブジェクトの厳格なスコープ管理とメモリ解放
—
2. 実装:`AcadApplication`イベント監視クラスの構築
まずは、アプリケーションレベルのイベントを捉えるためのクラスモジュールを構築する。
VBAのプロジェクトにクラスモジュールを追加し、名称を `CAppEvents` とする。
クラスモジュール: `CAppEvents`
Option Explicit
‘ WithEventsキーワードを使用し、AcadApplicationのイベントを捕捉する
Public WithEvents AppEvents As AcadApplication
‘ イベント発生時に外部DBへの書き込みを行うメインプロシージャ
Private Sub AppEvents_BeginSave(ByVal DocName As String)
On Error GoTo ErrorHandler
Dim docTitle As String
Dim docPath As String
Dim author As String
‘ アクティブドキュメントが存在するか検証
If Not AppEvents.ActiveDocument Is Nothing Then
docTitle = AppEvents.ActiveDocument.Name
docPath = AppEvents.ActiveDocument.FullName
‘ 図面プロパティ(サマリー情報)の取得
On Error Resume Next
author = AppEvents.ActiveDocument.SummaryInfo.Author
On Error GoTo ErrorHandler
‘ 外部DBへの書き込み処理を呼び出し(トランザクション保護)
Call WriteToSQLServer_BeginSave(docPath, docTitle, author)
End If
Exit Sub
ErrorHandler:
‘ CADの保存プロセスを阻害しないよう、エラーはログに留め、イミディエイトへ出力
Debug.Print “Error in BeginSave Event: ” & Err.Description
End Sub
Private Sub AppEvents_EndCommand(ByVal CommandName As String)
‘ 特定のコマンド(例: 属性編集やブロック定義など)終了時のみフック
Select Case UCase(CommandName)
Case “ATTEEDIT”, “BEDIT”, “-ATTDEF”
Call WriteToSQLServer_CommandLog(CommandName, AppEvents.ActiveDocument.Name)
Case Else
‘ その他のコマンドは無視
End Select
End Sub
‘ — 外部DB連携モジュール(ADO使用) —
Private Sub WriteToSQLServer_BeginSave(ByVal filePath As String, ByVal fileName As String, ByVal author As String)
Dim conn As Object
Dim cmd As Object
Dim connStr As String
‘ 接続文字列(SQL Server Native Client または OLEDB)
connStr = “Provider=MSOLEDBSQL;Server=192.168.1.100;Database=CAD_EnterpriseDB;Trusted_Connection=yes;”
Set conn = CreateObject(“ADODB.Connection”)
Set cmd = CreateObject(“ADODB.Command”)
conn.ConnectionTimeout = 5
conn.Open connStr
Set cmd.ActiveConnection = conn
cmd.CommandType = 1 ‘ adCmdText
‘ パラメータ化クエリによるSQLインジェクション対策の徹底
cmd.CommandText = “INSERT INTO T_DrawingHistory (FilePath, FileName, Author, SaveTimestamp) VALUES (?, ?, ?, GETDATE())”
cmd.Parameters.Append cmd.CreateParameter(“p1”, 200, 1, 255, filePath)
cmd.Parameters.Append cmd.CreateParameter(“p2”, 200, 1, 255, fileName)
cmd.Parameters.Append cmd.CreateParameter(“p3”, 200, 1, 100, author)
cmd.Execute
conn.Close
Set cmd = Nothing
Set conn = Nothing
End Sub
Private Sub WriteToSQLServer_CommandLog(ByVal cmdName As String, ByVal docName As String)
‘ コマンド実行ログの記録ロジック(省略実装:必要に応じてADOでインサート)
Debug.Print “Command executed: ” & cmdName & ” on ” & docName
End Sub
—
3. 標準モジュールでのライフサイクル管理とエントリーポイント
`WithEvents`を持つクラスインスタンスは、生存期間(スコープ)が切れるとイベントを検知しなくなる。そのため、グローバル変数として保持し、AutoCADの起動時やアドインのロード時に確実にインスタンス化する必要がある。
標準モジュール: `MMain`
Option Explicit
‘ アプリケーションイベントを保持するグローバルインスタンス
Public GlobalAppEvents As CAppEvents
‘ AutoCAD起動時、または手動ロード時に実行するエントリポイント
Public Sub InitializeCadMonitoring()
‘ 既にインスタンス化されている場合は二重生成を防ぐ
If GlobalAppEvents Is Nothing Then
Set GlobalAppEvents = New CAppEvents
‘AcadApplicationオブジェクトをバインド
Set GlobalAppEvents.AppEvents = ThisDrawing.Application
MsgBox “Enterprise CAD DB-Sync Engine: Activated.”, vbInformation, “System Architecture”
Else
MsgBox “Engine is already running.”, vbExclamation, “System Architecture”
End If
End Sub
‘ 終了時またはデバッグ用の解放処理
Public Sub TerminateCadMonitoring()
If Not GlobalAppEvents Is Nothing Then
Set GlobalAppEvents.AppEvents = Nothing
Set GlobalAppEvents = Nothing
MsgBox “Enterprise CAD DB-Sync Engine: Deactivated.”, vbInformation, “System Architecture”
End If
End Sub
—
4. シニアエンジニアの知見:メモリリークとCOM解放の極意
VBAにおける最大の悪習は、`CreateObject`やドキュメント参照で生成したCOMオブジェクトをそのまま放置することだ。特にAutoCADのプロセス内(In-Process)で動作するVBAは、ExcelやWordのVBAよりもメモリリークによるプロセス肥大化(Bloat)がパフォーマンスに直結する。
1. オブジェクトの完全な破棄(`Nothing`代入の順序)
オブジェクトを参照解除する際は、必ず末端のオブジェクトから根元のオブジェクトへ向かって`Set ~ = Nothing`を実行すること。
Set cmd = Nothing
Set conn = Nothing
これを怠ると、COMの参照カウント(Reference Count)が残存し、AutoCADの終了時にバックグラウンドプロセス(`acad.exe`)がゾンビ化する原因となる。
2. エラーハンドリングにおけるコネクションの確実にリーク防止
ネットワーク障害等でSQL Serverへの接続が途絶えた場合、`On Error`でトラップし損ねたコネクションがメモリ上に残留する。必ず`Finally`に相当するクリーンアップブロックを構築せよ。
Private Sub SafeExecuteDB()
Dim conn As Object
On Error GoTo ErrorHandler
Set conn = CreateObject(“ADODB.Connection”)
‘ … 処理 …
Cleanup:
If Not conn is Nothing Then
If conn.State = 1 Then conn.Close
Set conn = Nothing
End If
Exit Sub
ErrorHandler:
‘ エラー処理
Resume Cleanup
End Sub
—
5. まとめ
AutoCAD VBAはレガシーな言語環境と見なされがちだが、COMの深部を理解し、今回紹介した `AcadApplication` + `WithEvents` によるイベント駆動設計を適用すれば、モダンなエンタープライズシステムと遜色ない堅牢な連携基盤を構築できる。
CADの操作性を損なわず、裏で確実にデータを同期させる――これこそが、現場を知るシニアエンジニアが実装すべき「真の自動化」である。
