【実務中級】SendCommandメソッドの光と影:API未対応コマンドをVBAから実行する際の同期制御と注意点
AutoCAD VBAのオブジェクトモデルは洗練されている。しかし、実務の現場において、COM APIが用意されていない古いコマンド、サードパーティ製アドオンの独自コマンド、あるいはダイアログボックスを伴う一連の操作に直面したとき、開発者は壁に突き当たる。
その壁を強引に、しかし確実に突破するための唯一の逃げ道、それが `SendCommand` メソッドだ。
コマンドラインへ直接文字列を流し込むこの手法は、あらゆる操作を自動化できる「万能薬」に見える。だが、その裏側にある非同期実行の罠、タイミングズレ、そしてメモリリークの危険性を理解していない者にとって、それはシステムを崩壊させる「諸刃の剣」に他ならない。
今回は、レガシーアーキテクチャの最前線を知るシニアエンジニア向けに、`SendCommand` のメカニズムの深部と、実務で絶対に破綻しないための同期制御の極意を授ける。
—
1. なぜ `SendCommand` は「影」を持つのか?(非同期の罠)
多くのVBAプログラマが犯す最初の致命的なミスは、以下のようなコードを書くことだ。
‘ 駄目な例:連続してコマンドを送る
ThisDrawing.SendCommand “LINE 0,0 100,100 ”
ThisDrawing.SendCommand “ZOOM E ”
VBAの `SendCommand` は、AutoCADのメイン実行スレッドに対してコマンド文字列を「投げる(ポストする)」だけで制御を即座にVBA側へ返す。つまり、非同期実行(Asynchronous)である。
AutoCAD側が前のコマンド(`LINE`)を処理している最中に、次のコマンド(`ZOOM`)の文字列が割り込むため、コマンドが途中で途切れたり、意図しない順番で実行されたり、最悪の場合はAutoCADがフリーズする。
同期制御の本質
API未対応コマンドを自動化する場合、VBA側で「AutoCADが現在ビジー状態であるか」を監視、あるいは適切なウェイト(待機)を挟むアーキテクチャが不可欠となる。
—
2. Windows APIを活用した極限の同期制御
実務において、単なる `DoEvents` や `Application.Wait` でのタイマー待機は悪手である。マシンのスペックや図面の重さによって処理時間が変動するためだ。
確実な同期をとるためには、Windows APIの `Sleep` を用いつつ、AutoCADのメッセージキューを適切に処理する構造化されたウェイト関数を実装する必要がある。
以下に、実務の現場で耐えうる堅牢な `SendCommand` ラッパー関数の実装を示す。
Option Explicit
‘ Windows APIの宣言:処理を一時停止する
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Private Declare Function SendMessage Lib “user32” Alias “SendMessageA” (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
End If
Private Const WM_USER As Long = &H400
/
- AutoCADのコマンドラインへ安全に文字列を送信し、完了を待機するラッパー
- @param {AcadDocument} doc 対象のドキュメント
- @param {String} cmd 送信するコマンド文字列
- @param {Long} delayMs コマンド後のバッファ待機時間(ms)
/
Public Sub SafeSendCommand(ByVal doc As AcadDocument, ByVal cmd As String, Optional ByVal delayMs As Long = 100)
On Error GoTo ErrorHandler
‘ AutoCADがコマンダーモード(他のコマンド実行中)でないか確認する等、
‘ 必要に応じてアクティブドキュメントのフォーカスを保証
If Not doc Is ActiveDocument Then
doc.Activate
End If
‘ コマンド文字列の末尾に確実にスペース(Enterの代替)を付与
If Right(cmd, 1) <> ” ” And Right(cmd, 1) <> vbCr Then
cmd = cmd & ” ”
End If
‘ コマンドの送信
doc.SendCommand cmd
‘ AutoCADの描画スレッドおよびメッセージキューを解放するためのウェイト
‘ ※重い図面の場合はこの値を調整、またはイベント監視を挟む
DoEvents
Sleep delayMs
DoEvents
Exit Sub
ErrorHandler:
MsgBox “SendCommand実行エラー: ” & Err.Description, vbCritical
End Sub
—
3. LISP連携による `SendCommand` の限界突破
複雑な選択セットの処理や、システム変数の厳密な判定を伴う操作を `SendCommand` 単体で行うのは苦行でしかない。文字列表記の限界があるからだ。
ここで真価を発揮するのが AutoLISPとのハイブリッド連携 である。
VBAからLISP式(S式)を `SendCommand` 経由でインライン実行させることで、VBAのオブジェクトモデルの痒い所に手が届くようになる。
実例:画層を一括で「ONかつロック」にするLISPインライン実行
APIだけではループ処理とトランザクション管理が煩雑になる処理も、LISPを一行送り込むことで一瞬で解決できる。
Public Sub UnlockAndTurnOnLayer(ByVal layerName As String)
Dim lispExpr As String
‘ AutoCAD LISP式を構築
‘ (tblsearch “LAYER” layerName) で存在確認しつつ、DXFグループコードを書き換える
lispExpr = “(progn ” & _
” (setq lay (tblobjname \”LAYER\” \”” & layerName & “\”)) ” & _
” (if lay ” & _
” (progn ” & _
” (setq ent (entget lay)) ” & _
” (setq ent (subst (cons 62 (abs (cdr (assoc 62 ent)))) (assoc 62 ent) ent)) ” & _ // 凍結解除
” (setq ent (subst (cons 70 0) (assoc 70 ent) ent)) ” & _ // ロック解除
” (entmod ent) ” & _
” )” & _
” )” & _
” (princ)” & _
“) ”
‘ 安全なラッパー経由で実行
SafeSendCommand ThisDrawing, lispExpr, 200
End Sub
このアプローチにより、VBAの苦手とする「図面データベースの低レベル操作」をLISPに肩代わりさせ、実行結果の同期も `SendCommand` の完了待機によって担保できる。
—
4. メモリ最適化とオブジェクトのライフサイクル管理
`SendCommand` を多用する大規模なバッチ処理(複数図面の連続処理など)において、最も恐れるべきはメモリリークとCOMオブジェクトの解放漏れである。
AutoCAD VBAでは、`AcadDocument` や `SelectionSet` などのオブジェクト変数を使用後、適切に `Nothing` を代入してメモリを解放しないと、プロセス内にAutoCADのゴーストタスクが残骸として居座り続け、PCのメモリを食いつぶす。
実務で必須のクリーンアップパターン
Public Sub BatchProcessDrawings(ByVal folderPath As String)
Dim fso As Object
Dim targetFile As Object
Dim doc As AcadDocument
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ エラーハンドリングを厳格に行い、異常終了時も確実にオブジェクトを解放する
On Error GoTo CleanUp
For Each targetFile In fso.GetFolder(folderPath).Files
If LCase(fso.GetExtensionName(targetFile.Name)) = “dwg” Then
‘ 図面を開く
Set doc = Documents.Open(targetFile.Path)
‘ — ここでSendCommandやLISP連携を実行 —
SafeSendCommand doc, “_AUDIT _Y ” ‘ 例:監査修復
‘ 変更を保存して閉じる
doc.Close True
‘ 参照の明示的解放(極めて重要)
Set doc = Nothing
End If
5 Next targetFile
CleanUp:
‘ 異常終了時のメモリリークを防ぐ防壁
If Not doc Is Nothing Then
On Error Resume Next
doc.Close False
Set doc = Nothing
End If
Set fso = Nothing
If Err.Number <> 0 Then
MsgBox “バッチ処理中に致命的なエラーが発生しました: ” & Err.Description, vbCritical
End If
End Sub
—
5. チーフアーキテクトからの総括
`SendCommand` は、近代的なAPI設計の観点からは「レガシーで泥臭い手法」に見えるかもしれない。しかし、AutoCADという巨大で歴史のあるCADエンジンの深部を叩くためには、今なおなくてはならない実戦的なカードである。
- 非同期の特性を理解し、ウェイトとメッセージキューの解放を設計に組み込むこと。
- 複雑なロジックはAutoLISPをインラインで流し込んでバイパスすること。
- COMオブジェクトのライフサイクルを徹底的に管理し、メモリリークを根絶すること。
これらを遵守すれば、`SendCommand` はあなたのVBA自動化システムを、圧倒的なパワーと柔軟性を備えたプロフェッショナルなソリューションへと昇華させる最強の武器となる。技術の本質を見極め、コードを支配せよ。
