図面ライフサイクルの深淵:`ThisDrawing.Close` と `Documents.Close` が分かつ、堅牢な自動化への境界線
「図面を閉じる」という行為。一見すると、単にウィンドウを消し、メモリを解放するだけの些末な操作に見えるだろう。しかし、大規模なバッチ処理システムや、長期間稼働し続ける社内共通のCADアドインを設計するエンジニアにとって、この一見単純な操作こそが、システム全体の安定性を左右する「地雷原」であることを知っているはずだ。
多くの初学者は、`ThisDrawing.Close` と `Documents.Close` を「なんとなく似たようなもの」として混同し、その結果として、予期せぬランタイムエラーや、制御不能なメモリリーク、あるいは実行中のマクロが自爆するという、設計上の致命的な欠陥を招き入れる。
本稿では、単なるメソッドの使い分けに留まらず、AutoCADのCOMオブジェクトモデルにおけるライフサイクル、参照カウント、そしてメモリ管理の観点から、この二つのメソッドの決定的な差異を解剖する。
1. 実行コンテキストの死角:`ThisDrawing.Close` の危うさ
`ThisDrawing` オブジェクトは、現在VBAコードが実行されている「そのもの」の図面を指す。つまり、`ThisDrawing.Close` を呼び出すということは、「今まさに自分が立っている足場を、自分の手で切り落とす」行為に等しい。
実行スタックの破壊
VBAの実行エンジンは、メソッドの実行中にその図面に関連付けられたメモリ空間(スタックおよびヒープ)を参照し続けている。`ThisDrawing.Close` が実行され、図面が閉じられると、その図面に関連付けられた全てのオブジェクト参照、および実行中のプロシージャのコンテキストが強制的に破棄される。
これが何を意味するか。
もし、`ThisDrawing.Close` を呼び出した後に、その図面から取得したオブジェクト(例えば `ThisDrawing.ModelSpace`)に対して操作を続けようとすれば、即座に `Run-time error ‘-2147352567’: Invalid object reference` が発生する。
アーキテクトの視点:
単一図面内で完結するスクリプトにおいて、自分自身を閉じる操作は、極めて慎重に行わなければならない。基本的には「処理の最後、全てのオブジェクト参照を `Nothing` にした直後」という、実行コンテキストの終焉と同期させる必要がある。
2. 管理者権限による制御:`Documents.Close` の支配力
一方で、`Documents.Close` は `Application` オブジェクトが保持する `Documents` コレクションに対するメソッドである。これは「自分自身」ではなく、「管理対象のリストの中から特定の図面を指定して閉じる」という、上位レイヤーからの制御を意味する。
複数図面管理における優位性
バッチ処理(複数のDWGを順次開き、処理して閉じる)において、真に利用すべきは後者である。`Documents.Close` を使用することで、アクティブな図面とは無関係に、特定のインデックスや名前を指定して図面をクローズできる。これにより、実行コンテキストを汚染することなく、計算リソースを計画的に回収することが可能となる。
3. メモリ解放の真実:参照カウントとCOMオブジェクトの罠
ここからが、シニアエンジニアが向き合うべき真の戦場だ。
AutoCAD VBAはCOM(Component Object Model)アーキテクチャ上で動作している。オブジェクトがメモリから解放されるかどうかは、そのオブジェクトに対する「参照カウント」がゼロになるかどうかに依存する。
初心者が陥る最大の罠は、`Documents.Close` を実行しただけで、メモリが解放されたと錯覚することだ。
参照の残骸(Orphaned References)
図面を閉じても、VBA側でその図面内のオブジェクト(`AcadEntity` や `AcadLayer` など)を変数に保持し続けている場合、参照カウントはゼロにならない。図面は「見かけ上」閉じられていても、メモリ上にはその図面のデータ構造が「幽霊」のように残り続ける。これが、長時間稼働するバッチ処理で発生する「メモリリーク」の正体である。
鉄則:明示的な破棄
大規模なシステムを設計する場合、以下のプロセスを徹底しなければならない。
1. 対象図面のオブジェクトを全て取得。
2. 全てのオブジェクト変数に対し、`Set obj = Nothing` を実行。
3. その上で、`Documents.Close` を実行。
4. 実装例:堅牢なバッチ・クローズ・エンジンの設計
以下に、複数の図面を安全に処理し、メモリリークを最小限に抑えながらクローズしていく、プロフェッショナルな実装パターンを示す。
''
' @description 複数の図面を安全にクローズするための堅牢なルーチン
' @param docToClose 閉じたい図面オブジェクト
' @return Boolean 成功したかどうか
''
Function SafeCloseDocument(ByRef docToClose As Object) As Boolean
On Error GoTo ErrorHandler
Dim i As Integer
' 1. 図面内の全オブジェクトの参照を明示的に切り離す(重要)
' ※ 実際には、ループ内で生成した全ての変数に対して Nothing を行う必要がある
' ここでは概念的な流れを示す
' 2. 図面を閉じる。変更がある場合は保存する(要件に応じて変更)
' Documents.Close は指定した図面をコレクションから切り離す
docToClose.Application.Documents.Close docToClose.Saved = True ' 保存せずに閉じる例
' 3. VBA側の参照を即座に解放
Set docToClose = Nothing
SafeCloseDocument = True
Exit Function
ErrorHandler:
Debug.Print "Error closing document: " & Err.Description
SafeCloseDocument = False
End Function
''
' @description 図面コレクションを逆順で走査し、特定の条件に合致する図面を閉じる
''
Sub BatchProcessAndClose()
Dim acadApp As Object
Dim doc As Object
Dim i As Integer
Set acadApp = ThisDrawing.Application
' 【極限の知見】コレクションを操作(削除)する場合、
' 必ず「後ろから(逆順)」ループを回すこと。
' 前から回すと、インデックスがずれて処理漏れが発生する。
For i = acadApp.Documents.Count To 1 Step -1
Set doc = acadApp.Documents.Item(i)
' 特定の条件(例:ファイル名に "TEMP" を含む)に基づいて処理
If InStr(doc.Name, "TEMP") > 0 Then
Debug.Print "Processing and closing: " & doc.Name
' ここで実際の図面処理を行う...
' PerformComplexTask doc
' 安全なクローズを呼び出し
If Not SafeCloseDocument(doc) Then
' エラーハンドリング
End If
End If
' ループの最後で必ず参照をクリア
Set doc = Nothing
Next i
MsgBox "Batch processing completed.", vbInformation
End Sub
5. 結論:アーキテクトとしての矜持
`ThisDrawing.Close` は「自分を消す」ための手段であり、`Documents.Close` は「環境を制御する」ための手段である。
システム管理や大規模自動化の設計において、前者を多用することは、設計上の敗北を意味する。常に「どのオブジェクトが、どのスコープで、どの程度の期間、メモリを占有しているか」を意識せよ。
Windows API(`FindWindow` や `SendMessage`)を用いた強制的なプロセス制御に手を染める前に、まずはCOMオブジェクトのライフサイクルを完璧に掌握すること。それが、レガシーな環境においても、最新のプラットフォームにおいても、揺るぎない安定性を提供する唯一の道である。
