【上級】64bit環境におけるAcadApplicationのAPI宣言:PtrSafeとLongPtrの使い替えとメモリ管理
こんにちは。開発プロジェクトを率いるチーフアーキテクトの私だ。
これまで数多くのレガシーなAutoCADカスタマイズ資産を見てきたが、いまだに「32bit時代に書かれたVBAコード」をそのまま64bit版AutoCADに持ち込み、不可解なクラッシュやメモリリークに頭を悩ませている現場が後を絶たない。
「なぜこのコードは動かないのか」
「なぜ時々AutoCADごと強制終了するのか」
その原因の多くは、API(Win32 API)宣言におけるポインタ型の扱いの誤り、そして64bitプロセスのメモリ空間に対する無知にある。
今回は、64bit環境における`AcadApplication`周辺のAPI宣言の真髄と、プロダクション環境で耐えうる堅牢なメモリ管理の設計思想をロジカルに伝授しよう。
—
1. なぜレガシーなAPI宣言は64bit AutoCADをクラッシュさせるのか?
32bitから64bitへの移行において、VBA開発者が直面する最大の壁は「メモリアドレス(ポインタ)のサイズ変化」だ。
- 32bit環境: ポインタサイズ = 4バイト(`Long`型で格納可能)
- 64bit環境: ポインタサイズ = 8バイト(`Long`型ではあふれる)
レガシーなコードによくある以下のような宣言を見てほしい。
‘ 【悪手】32bit専用のレガシーなAPI宣言
Declare Function FindWindow Lib “user32” Alias “FindWindowA” (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
このコードを64bit版のAutoCAD(プロセス空間が64bit)で実行するとどうなるか?
戻り値である8バイトのメモリアドレスが4バイトの`Long`型に無理やり押し込められ、の上位ビットが切り捨てられる(あるいはメモリの不正アクセスを引き起こす)。結果として、AutoCADがの前ぶれもなく突然沈黙する(クラッシュする)最悪のバグが産まれる。
—
2. PtrSafeとLongPtrの正しい使い分け
Office 2010以降および64bit版AutoCADに搭載されているVBA7環境では、この問題を解決するために`PtrSafe`と`LongPtr`という強力な武器が用意された。
PtrSafe
「このAPI宣言は64bit環境に対応しており、安全に実行できる」ことをVBAコンパイラに伝えるキーワードだ。64bit版VBAでは、`Declare`を使う場合は必ず`PtrSafe`を付与しなければならない(付与しないとコンパイルエラーになる)。
LongPtr
環境のビット数によって自動的にデータ型が変化する、いわば「カメレオン型の型」だ。
- 32bit環境では:4バイト(Long相当)
- 64bit環境では:8バイト(LongLong相当)
HWND(ウィンドウハンドル)やメモリポインタを扱う引数・戻り値は、すべてこの`LongPtr`で定義しなければならない。
—
3. 【実践】堅牢なAPIラッパーとAcadApplication連携のプロダクションコード
実務において、AutoCADのウィンドウハンドル(HWND)を特定し、外部アプリケーションとの連携やモーダルダイアログの親ウィンドウ制御を行うシーンは多い。
以下に、32bit/64bitの環境差異を完全に吸収し、メモリリークやヌルポインタ参照を防ぐように設計されたプロダクション品質のコードを示す。
Option Explicit
‘ ==============================================================================
‘ 64bit/32bit完全対応 Win32 API 宣言モジュール
‘ ==============================================================================
If Vba7 Then
‘ 64bit (および 32bit VBA7) 環境用
Private Declare PtrSafe Function GetActiveWindow Lib “user32” () As LongPtr
Private Declare PtrSafe Function SetWindowTextA Lib “user32” (ByVal hwnd As LongPtr, ByVal lpString As String) As Long
Private Declare PtrSafe Function FindWindowA Lib “user32” (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Else
‘ レガシーな 32bit 環境用 (必要に応じたフォールバック)
Private Declare Function GetActiveWindow Lib “user32” () As Long
Private Declare Function SetWindowTextA Lib “user32” (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function FindWindowA Lib “user32” (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
End If
/
- AutoCADのメインウィンドウタイトルを動的に書き換える堅牢なプロシージャ
- AcadApplicationとWin32 APIを安全に協調動作させる実例
/
Public Sub UpdateAcadTitleWithAPI()
Dim acadApp As AcadApplication
Dim hWndAcad As LongPtr
Dim result As Long
On Error GoTo ErrorHandler
‘ アプリケーションオブジェクトの取得(セーフティな参照)
Set acadApp = ThisDrawing.Application
‘ 1. AutoCADメインウィンドウのハンドルを取得
‘ ※AcadApplication自体のプロパティにはHWNDを直接取得するものがないため、
‘ ウィンドウタイトルをキーにしてハンドルを特定する高度なアプローチをとる
hWndAcad = FindWindowA(vbNullString, acadApp.Caption)
If hWndAcad = 0 Then
MsgBox “対象のAutoCADウィンドウが見つかりませんでした。”, vbCritical, “API Error”
Exit Sub
End If
‘ 2. APIを使用してウィンドウタイトルをセキュアに変更(例:処理中ステータスの埋め込み)
Dim newTitle As String
newTitle = acadApp.Name & ” – [Enterprise Edition – 64bit Active]”
result = SetWindowTextA(hWndAcad, newTitle)
If result = 0 Then
Err.Raise vbObjectError + 1000, “API_Call”, “SetWindowTextAの実行に失敗しました。”
End If
MsgBox “AutoCADのタイトルをAPI経由で正常に変更しました。”, vbInformation, “Success”
CleanUp:
‘ オブジェクトのライフサイクル管理:確実に参照を解放する
Set acadApp = ThisDrawing = Nothing ‘ 構文上の注意: 正しくは Set acadApp = Nothing
Set acadApp = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “Critical Error”
Resume CleanUp
End Sub
—
4. プロジェクトリーダーが教える「メモリ管理と設計の鉄則」
上記のコードを通して、プロのエンジニアが守るべき設計思想をいくつか解説しよう。
① `#If Vba7 Then` による条件付きコンパイルの徹底
コードベースを32bitと64bitで共通化したい場合、`#If Vba7 Then`(および `#If Win64 Then`)を駆使し、古い環境とモダンな環境の双方でコンパイルが通るように設計せよ。「うちはもう64bitしか使わないから」と決めつけるのはアマチュアの所業だ。保守性を担保するためには、古い環境への配慮もコードレベルで内包させるのがプロフェッショナルだ。
② AcadApplicationオブジェクトのライフサイクルと解放
VBAにおけるCOMオブジェクト(`AcadApplication`, `AcadDocument`など)は、適切な参照解放を行わないと、AutoCADのプロセス(`acad.exe`)の背後に「ゾンビプロセス」としてメモリ上に残存し続ける。
特に外部から`CreateObject`や`GetObject`で`AcadApplication`を操作した場合は、必ず最後に `Set acadApp = Nothing` を実行し、メモリリークの芽を摘み取ること。
③ ポインタの型キャストミスに酔うな
`LongPtr` を通常の `Long` や `Integer` に代入・演算する際、暗黙の型変換によるオーバーフローが発生しやすい。APIから返されたポインタは、基本的に「中身を勝手にいじらない、そのまま次のAPIに渡すか、アドレスとして保持するだけ」のブラックボックスとして扱うのが鉄則だ。
—
総括
64bit環境におけるAPI宣言とメモリ管理は、単なる「エラー逃れのエラー修正」ではない。AutoCADという巨大なCADエンジンのプロセスと、OSのメモリ空間を直接ブリッジする、きわめてアーキテクチャレイな重要タスクだ。
今回紹介した `PtrSafe`、`LongPtr`、そして条件付きコンパイルのパターンをプロジェクトの標準テンプレートとして組み込み、バグの踏み跡すらない堅牢な業務自動化ツールを構築してほしい。
あなたのコードが、現場の生産性を極限まで引き上げることを期待している。
