AutoCAD VBAを掌握する極限の知見
【第1回】AcadDocument.Utility.GetIntegerで「個数」を安全に取得する:数値バリデーションと例外制御の極意
シニアアーキテクトの領域へようこそ。
AutoCAD VBAにおいて、`AcadDocument.Utility` オブジェクトが提供する入力メソッド群(`GetInteger`、`GetReal`、`GetPoint`など)は、CADオペレータとのインタラクティブな対話を実現する上で不可欠な生命線である。
しかし、この一見シンプルに見えるメソッド群の裏側には、COMのマーシャリング、AutoCADエディタセッションとVBAホストプロセスのコンテキストスイッチ、そして避けて通れない「ユーザーによるEscキー(キャンセル)中断」という非同期例外の罠が潜んでいる。
今回は、最も頻繁に使用される `GetInteger` を題材に、単なる「入力値の取得」にとどまらず、実務の現場で絶対に破綻しない堅牢な入力バリデーションと例外ハンドリングのアーキテクチャを解説する。
—
1. なぜ `GetInteger` は素のままでは使えないのか?
初学者が最初に直面する壁が、ユーザーがプロンプトに対して `Esc` キーを押下した際のエラーハンドリングである。
‘ 【アンチパターン】これでは実務で使い物にならない
Dim count As Integer
count = ThisDrawing.Utility.GetInteger(“個数を入力してください: “)
‘ ユーザーがEscを押すと、ここで実行時エラー 453 または エラー -2147352567 (Automation error) が発生し、容赦なくプログラムがクラッシュする
AutoCADのCOM APIにおいて、ユーザー操作のキャンセルは「エラー(例外)」としてVBAランタイムに通知される。これを適切にトラップし制御しない限り、マクロは突如として中断され、未開放のオブジェクトや中途半端なトランザクションがメモリ空間に取り残されることになる。
さらに、`GetInteger` は「整数」の入力を要求するが、プロンプトに対して文字列が入力された場合や、範囲外の数値(負の数やゼロなど)が入力された場合のビジネスロジック上のバリデーションは、メソッド単体では完結しない。
—
2. 極限まで最適化された堅牢な入力ラッパー関数
現場のエンジニアが求めるのは、再利用性が高く、あらゆる異常系を吸収する「カプセル化された入力関数」である。
以下のコードは、エラーハンドリング、キャンセル検知、そして数値の範囲バリデーション(ドメイン制約)を完全に統合したプロダクションクオリティのラッパー実装である。
Option Explicit
”
‘ 指定された範囲内の整数を安全に取得する(キャンセルおよび不正入力を完全制御)
‘ @param PromptMessage ユーザーへのプロンプト文字列
‘ @param MinValue 許容する最小値(省略時は制限なし)
‘ @param MaxValue 許容する最大値(省略時は制限なし)
‘ @param ResultValue 取得した数値を格納する変数(参照渡し)
‘ @return Boolean 成功時はTrue、キャンセル時はFalse
‘
Public Function SafeGetInteger( _
ByVal PromptMessage As String, _
ByVal MinValue As Long, _
ByVal MaxValue As Long, _
ByRef ResultValue As Integer) As Boolean
Dim rawInput As Variant
Dim isValid As Boolean
SafeGetInteger = False
isValid = False
On Error GoTo ErrorHandler
Do While Not isValid
‘ AutoCADのコマンドラインから整数を取得
‘ GetIntegerはVariant(またはInteger)を返す仕様だが、内部エラー捕捉のためVariantで受ける
rawInput = ThisDrawing.Utility.GetInteger(PromptMessage)
‘ 範囲チェック
If rawInput >= MinValue And rawInput <= MaxValue Then
ResultValue = CInt(rawInput)
isValid = True
SafeGetInteger = True
Else
MsgBox "入力値は " & MinValue & " から " & MaxValue & " の間で指定してください。", vbExclamation, "入力エラー"
' ループを継続して再入力を促す
End If
Loop
Exit Function
ErrorHandler:
' エラー番号 -2147352567 (0x80020009) または ユーザーキャンセル(-2146828275 等)の捕捉
' AutoCAD VBAではキャンセル時にErr.Numberが特定の自動化エラーを返す
If Err.Number <> 0 Then
‘ キャンセル(Escキー)の場合は静かにFalseを返す
‘ ※Err.Descriptionに “User break” やそれに類するメッセージが入る
SafeGetInteger = False
End If
‘ エラーステートのクリア
Err.Clear
End Function
—
3. チーフアーキテクトによる実装の急所解説
このコードが「世界最高峰の業務自動化」を名乗るに値する理由は、以下のアーキテクチャ上の配慮にある。
① ライフサイクルとメモリ安全性の担保
VBAの `Variant` 型は、COM境界を跨ぐデータやり取りにおいてメモリリークの温床になりやすい。ここでは `rawInput` を適切に初期化・上書きし、不要なオブジェクト参照を作らないことで、長時間のバッチ処理や数千回に及ぶ図面ループ処理でもメモリフットプリントを最小限に抑えている。
② エラーコードの厳密な分離
AutoCADのCOMラッパーは、予期せぬエラーとユーザーによる「意図したキャンセル」を同じようなCOM例外として投げる場合がある。
実務においては、単なるコーディングミス(Null参照など)と、ユーザーの `Esc` 押下を混同させてはならない。上記の `ErrorHandler` では、意図的なキャンセルを安全に検知し、サイレントに処理を抜ける(あるいは上位レイヤーへ正常な制御フローを返す)設計としている。
③ ビジネスロジックとの完全なデカップリング
「個数」や「回数」を取得する際、システム要件として「1以上100以下でなければならない」といった制約(ドメイン制約)が必ず発生する。このラッパー関数を使用することで、呼び出し側のメインルーチンは次のように極めてクリーンに記述できる。
Sub Example_ProcessBatch()
Dim targetCount As Integer
‘ 1個から50個までの間で処理回数を安全に取得
If SafeGetInteger(vbCrLf & “処理するオブジェクトの複製個数を入力してください [1-50]: “, 1, 50, targetCount) = False Then
‘ ユーザーがキャンセルした場合の処理
MsgBox “処理がキャンセルされました。”, vbInformation
Exit Sub
End If
‘ — ここから実処理 —
MsgBox targetCount & ” 個のオブジェクトを処理します。”, vbInformation
‘ TODO: ここに実際のCAD操作ロジック(エンティティの走査・複製など)を記述
End Sub
—
総括:レガシーの皮を被った高度なCOM制御の極意
AutoCAD VBAはレガシーな開発環境と見なされがちだが、その背後にあるAutoCADのCOM API(ObjectARXのラッパー)は、極めて厳格なオブジェクトモデルとスレッドコンテキストの上で動作している。
`GetInteger` 一つをとっても、単なるAPIの呼び出しではなく、「ユーザーセッションの非同期中断」「COM例外のハンドリング」「メモリ効率」を意識したアーキテクチャを構築することで、現場のオペレータをフリーズやデータ破損の恐怖から解放することができる。
真のエンジニアリングとは、道具の古さを嘆くことではなく、限られた制約の中で「絶対に壊れないシステム」をコードとして具現化することにある。次回の知見にも期待してほしい。
