AutoCAD VBAを掌握する極限の知見:64bit環境におけるAcadApplicationのAPI宣言とメモリ管理
レガシーな32bit環境で構築されたAutoCAD VBAシステムを、現代の64bit環境へ移行する際、避けて通れない最大の壁が「ポインタサイズの不整合」と「メモリ管理のパラダイムシフト」である。
本稿では、単なる構文の置き換えに留まらず、Windows OSのメモリアーキテクチャとAutoCADのプロセスモデルを深く理解したシニアエンジニアに向け、`PtrSafe` と `LongPtr` の本質、そしてクラッシュを根絶するためのAPI呼び出しの極意を解説する。
—
1. 64bit VBAにおけるAPI宣言のパラダイムシフト
32bitから64bitへの移行において、最も致命的な誤解は「すべての `Long` を `LongPtr` に置き換えればよい」という安易なアプローチだ。
Windows APIにおいて、メモリ上のアドレスやハンドル(HWND, HANDLEなど)を指す型は、32bit環境では32bit(4バイト)、64bit環境では64bit(8バイト)に拡張される。VBA 7.0以降(Office 2010以降、およびそれに紐づくAutoCADのVBA環境)では、この環境差異を吸収するために `PtrSafe` 属性と `LongPtr` 型が導入された。
型の厳密な使い分け
- `Long`: 64bit環境であっても常に32bit(4バイト)の整数を保持する。Windows APIの構造体や関数定義において、`LONG`、`INT`、あるいは `BOOL`(Win32 APIの4バイト定義)を指定する場合は、64bit環境であっても `Long` のままで維持しなければならない。
- `LongPtr`: 32bit環境では4バイト、64bit環境では8バイトとしてコンパイルされるポインタ専用型。メモリハンドルの格納にのみ使用する。
—
2. 実装:64bit完全対応のWin32 API宣言とAutoCADウィンドウ制御
以下のコードは、AutoCADのメインウィンドウ(`AcadApplication.Hwnd`)をフックし、外部からウィンドウ制御やメッセージ送信を行うための堅牢なAPI宣言の模範解答である。
条件付きコンパイル定数 `#If VBA7` を用いることで、レガシーな32bit版AutoCAD(Office 2010以前のコンポーネントを流用している極稀な環境)との後方互換性を担保しつつ、モダンな64bit環境で完全に動作する。
Option Explicit
‘ =================================================================
‘ 64bit/32bit環境完全対応 Win32 API 宣言モジュール
‘ =================================================================
If VBA7 Then
‘ 64bit対応のVBA 7.0以降
Declare PtrSafe Function SetWindowText Lib “user32” Alias “SetWindowTextA” (ByVal hwnd As LongPtr, ByVal lpString As String) As Long
Declare PtrSafe Function GetWindowLongPtr Lib “user32” Alias “GetWindowLongPtrA” (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
Declare PtrSafe Function SetWindowLongPtr Lib “user32” Alias “SetWindowLongPtrA” (ByVal hwnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As LongPtr) As LongPtr
‘ 構造体や一部のAPIパラメータで使用するポインタ型
Public Const NULL_PTR As LongPtr = 0&
Else
‘ レガシーな32bit環境(AutoCAD 2013-2014の一部等)
Declare Function SetWindowText Lib “user32” Alias “SetWindowTextA” (ByVal hwnd As Long, ByVal lpString As String) As Long
Declare Function GetWindowLong Lib “user32” Alias “GetWindowLongA” (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Declare Function SetWindowLong Lib “user32” Alias “SetWindowLongA” (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Const NULL_PTR As Long = 0&
End If
‘ ウィンドウ情報のオフセット定数
Public Const GWL_USERDATA As Long = -21
”’
”’
Public Sub UpdateAutoCADTitle(ByVal customMessage As String)
Dim acadHwnd As LongPtr
Dim result As Long
On Error GoTo ErrorHandler
‘ AcadApplicationからウィンドウハンドルを取得
‘ ※ 64bit AutoCADでは、HwndプロパティはLongPtrを返すため安全に受ける必要がある
acadHwnd = ThisDrawing.Application.hwnd
If acadHwnd = NULL_PTR Then
MsgBox “AutoCADのウィンドウハンドルを取得できませんでした。”, vbCritical
Exit Sub
}
‘ API呼び出し
result = SetWindowText(acadHwnd, “AutoCAD [ ” & customMessage & ” ] – 処理実行中”)
If result = 0 Then
‘ エラーハンドリング(Err.LastDllErrorを参照)
MsgBox “API呼び出しに失敗しました。Error Code: ” & Err.LastDllError, vbExclamation
End If
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub
—
3. メモリリークとCOMオブジェクトのライフサイクル管理
AutoCAD VBAにおいて、最も見落とされがちなのが COMオブジェクトの解放と背後にあるアパルトメント(Apartment)のメモリ管理 である。
`ThisDrawing` や `ActiveDocument` は、VBAランタイムが暗黙的に参照を保持・解放してくれるため意識されにくいが、`GetObject` や `CreateObject` で別プロセスのAutoCADインスタンスを操作する場合や、複雑なSelectionSet、Dictionary、Collectionオブジェクトをループ内で生成・破棄する場合、明示的な解放を行わなければ確実にメモリリーク(メモリの肥大化およびCADプロセスのゾンビ化)を引き起こす。
鉄則:オブジェクト変数の徹底的な「Nothing代入」
VBAのガベージコレクションは参照カウンタ方式(Reference Counting)に依存している。循環参照や、プロセスを跨いだCOMオブジェクトの参照が残ると、VBA終了後も `acad.exe` プロセスがタスクマネージャー上に残存し続ける。
Public Sub ProcessMassiveEntities()
Dim acadApp As AcadApplication
Dim acadDoc As AcadDocument
Dim sSet As AcadSelectionSet
Dim i As Long
On Error GoTo Cleanup
‘ 外部または自身のアプケーションインスタンスの取得
Set acadApp = ThisDrawing.Application
Set acadDoc = acadApp.ActiveDocument
‘ 選択セットの安全な生成(既存名があれば削除)
On Error Resume Next
Set sSet = acadDoc.SelectionSets.Add(“TempProcessSet”)
If Err.Number <> 0 Then
Set sSet = acadDoc.SelectionSets.Item(“TempProcessSet”)
sSet.Clear
End If
On Error GoTo Cleanup
‘ — 大量処理のシミュレーション —
‘ ここでCOMオブジェクトにアクセスする処理を行う
‘ 処理終了後の明示的なクリーンアップ
sSet.Delete ‘ SelectionSetは明示的に削除が必須
Cleanup:
‘ 逆順でのオブジェクト参照の解放(メモリリーク防止の極意)
Set sSet = Nothing
Set acadDoc = Nothing
Set acadApp = Nothing
If Err.Number <> 0 Then
MsgBox “処理中にエラーが発生しました: ” & Err.Description, vbCritical
End If
End Sub
—
4. シニアエンジニアが押さえるべき設計上の注意点
1. `Err.LastDllError` の活用:
Win32 APIを呼び出した直後、VBAは独自の `Err` オブジェクトをクリアする場合がある。APIの失敗原因を正確に特定するためには、API実行直後に `Err.LastDllError` をローカル変数に退避させ、ログ出力やデバッグに活用するアーキテクチャが不可欠である。
2. 64bit Excel等との連携(Out-of-Process Automation):
AutoCAD VBAから外部のExcel(64bit)を操作する際、早期バインディング(`Dim xlApp As Excel.Application`)を使用する場合も、型ライブラリの整合性が崩れているとプロセス間でIPC(プロセス間通信)エラーが発生する。インターフェースを扱う変数は極力 `Object` 型で受け、実行時バインディング(Late Binding)を検討する柔軟性を持たせるべきである。
総括
64bit環境におけるAutoCAD VBAの開発は、単にコードを動かすことではなく、OSのメモリ構造とCOMのライフサイクルをコントロールするエンジニアリングそのものである。
`PtrSafe` と `LongPtr` を正しく理解し、ポインタのビット幅の変化に耐えうる堅牢な型定義を行うこと。そして、COMオブジェクトの参照を適切に断ち切るクリーンコードを徹底すること。この2点を満たしたシステムのみが、現代の極限のCAD自動化環境において真の安定性を発揮する。
