AutoCAD VBA:図面を開く際の「死のループ」を断つ―メモリ管理と遅延読み込みの極意
AutoCADのバッチ処理や大規模な図面管理システムを構築する際、多くのエンジニアが「図面を連続して開く」という行為で、不可解なメモリリークや処理速度の漸減に直面する。
CADのAPIは、一見すると直感的なオブジェクトモデルに見えるが、その裏側にはCOM(Component Object Model)の深い闇が広がっている。今回は、中級者が陥る「ただ開いて閉じるだけ」という罠から脱し、シニアエンジニアが現場で実践している「メモリを汚さない図面操作」の核心を解説する。
—
1. 忘却すべき「Application.Documents.Open」の甘い罠
多くのコードでは、何の疑いもなく `Documents.Open` を連呼する。だが、これこそがシステムを重くする最大の要因だ。
AutoCADの図面を開く際、デフォルトでは全ての外部参照(XRef)や未定義のプロキシオブジェクトがロードされる。これが大規模な図面群において、メモリを飽和させる。
極限の知見:Read-Onlyと「遅延読み込み」の哲学
図面を編集する必要がない場合、あるいは特定の画層やブロック情報のみを抽出したい場合、読み取り専用モードでのオープンは必須だ。さらに、`DemandLoad` システム変数を調整し、不要なオブジェクトのロードを物理的に遮断せよ。
‘ 【核心】図面を開く際のメモリ負荷を最小化する設計
Public Sub FastOpenDrawing(ByVal filePath As String)
Dim doc As AcadDocument
‘ システム変数の退避と変更(読み込み負荷を低減)
‘ DEMANDLOAD: 外部参照をロードするタイミングを制御
Dim originalDemandLoad As Integer
originalDemandLoad = ThisDrawing.GetVariable(“DEMANDLOAD”)
ThisDrawing.SetVariable “DEMANDLOAD”, 0 ‘ 0=ロードしない, 2=参照時のみ
‘ 図面を読み取り専用で開く(ロックを回避しメモリ消費を抑える)
Set doc = Application.Documents.Open(filePath, True)
‘ — ここに最小限の抽出処理 —
‘ 後処理
doc.Close False
‘ システム変数の復元
ThisDrawing.SetVariable “DEMANDLOAD”, originalDemandLoad
End Sub
—
2. VBAにおける「COMオブジェクト」の死のサイクル
VBAのガーベジコレクションを過信してはならない。特に `ActiveDocument` や `ModelSpace` オブジェクトをループ内で何度も参照すると、参照カウントが適正にデクリメントされず、メモリ空間に「ゾンビオブジェクト」が蓄積される。
明示的解放の鉄則
`Set obj = Nothing` を書くのは基本だが、それだけでは足りない。ループ内でのオブジェクト取得を避けることが、パフォーマンスを維持する唯一の道だ。
- ループ内でオブジェクトを生成しない: `.Item(i)` や `.ModelSpace` へのアクセスは、ループの外で一度だけ行い、変数にキャッシュせよ。
- イベントハンドラの解除: 図面を閉じる前に、クラスモジュールで接続しているイベントを明示的に `Nothing` に設定せよ。これを行わないと、閉じたはずの図面のメモリが解放されず、数千ファイル処理した瞬間にAutoCADがクラッシュする。
—
3. Windows APIによる「強制メモリクリーンアップ」
VBAはプロセス内のメモリ管理が甘い。大量の図面を処理する際、数時間経つとOSから割り当てられたメモリが解放されず、Page Fault(ページフォールト)が多発する。
これを回避するため、`SetProcessWorkingSetSize` を使用し、一定のタイミングでプロセスのワーキングセット(使用メモリ)をOSに返還させるテクニックがある。
‘ Windows APIの宣言
If VBA7 Then
Private Declare PtrSafe Function SetProcessWorkingSetSize Lib “kernel32” _
(ByVal hProcess As LongPtr, ByVal dwMinimumWorkingSetSize As LongPtr, _
ByVal dwMaximumWorkingSetSize As LongPtr) As Long
Private Declare PtrSafe Function GetCurrentProcess Lib “kernel32” () As LongPtr
End If
‘ メモリの強制解放処理
Public Sub ForceMemoryCleanup()
Dim hProcess As LongPtr
hProcess = GetCurrentProcess()
‘ メモリを解放し、OSへワーキングセットを戻す
SetProcessWorkingSetSize hProcess, -1, -1
End Sub
注:この操作は頻繁に行うと逆にOSのキャッシュ効率を下げる。10〜20図面処理するごとに呼び出すのが定石だ。
—
4. アーキテクトからの提言
もし、あなたがこれから数万枚単位の図面を扱うシステムを設計するのであれば、VBA単体での完結を諦めるべきだ。
1. ObjectARX/C# (RealDWG) への移行: COM経由のVBAは、どうしてもプロセスを介したオーバーヘッドが避けられない。ObjectARXであれば、メモリを直接叩けるため、速度は桁違いになる。
2. スクリプト駆動の外部分離: `Acad.exe /nologo /b ScriptFile.scr` でバッチ処理を行い、AutoCADプロセスを一定数処理ごとに再起動するアーキテクチャが、結局のところ最も安定する。
「完璧なコード」など存在しない。存在するのなら、それは「メモリを制御下に置いたコード」だけだ。
現場の安定稼働は、こうした泥臭いメモリ管理と、APIの裏側を覗き見る勇気から生まれる。健闘を祈る。
