The Architecture of Silence: 大規模SolidWorks自動化におけるCOMオブジェクト解放の極限設計
自動化エンジニアとして、数百、あるいは数千の図面をバッチ処理するプロジェクトに挑む際、多くの者が直面する「見えない壁」がある。
それは、プログラムが完走することではない。「途中でSolidWorksが沈黙する(フリーズする)」、あるいは「メモリ消費が指数関数的に増大し、OSによってプロセスが強制終了される」という現象だ。
多くの開発者はこれを「SolidWorksのバグ」や「PCのスペック不足」として片付けようとする。しかし、それは致命的な誤解である。真犯人は、COM(Component Object Model)のライフサイクル管理に対する、設計思想の欠如にある。
本稿では、単なるAPIリファレンスの解説ではない。大規模マルチシート図面を制御する際に、いかにしてメモリリークを封じ込め、堅牢なシステムを構築するか。その極限の知見を共有する。
1. COM参照カウントの深淵:なぜ `Set obj = Nothing` では足りないのか
VBAにおける `Set obj = Nothing` は、VBA側の変数からオブジェクトへの参照を外すだけであり、SolidWorks(COMサーバー)側で保持されている参照カウント(Reference Count)を確実にゼロに落とすことを保証するものではない。
特に、SolidWorksのような階層構造が極めて深いアプリケーションでは、以下の「連鎖的な参照」がメモリリークの温床となる。
- `SldWorks` (Application)
- `ModelDoc2` (Document)
- `DrawingDoc` (Drawing)
- `Sheet` (Sheet)
- `View` (Drawing View)
エンジニアが `DrawingDoc` を `Nothing` にしたとしても、もしコード内のどこかで `View` オブジェクトを変数として保持し続けていれば、その `View` は親である `Sheet` を、`Sheet` は `DrawingDoc` を、そして `DrawingDoc` は `SldWorks` を、それぞれ参照し続ける。
結果として、「ドキュメントは閉じたはずなのに、メモリ上には見えない図面データが居座り続ける」というゾンビプロセス状態が作り出されるのだ。
2. 鉄則:ボトムアップ・デストラクション(Bottom-Up Destruction)
大規模図面を処理する際の設計指針はただ一つ。「末端(Leaf)から根(Root)に向かって、明示的に参照を断ち切る」ことだ。
ツリー構造を逆順に辿るイメージを持つ。`View` を解放し、次に `Sheet` を、最後に `DrawingDoc` を、そして `SldWorks` を。この順序を誤ると、COMの参照カウントは複雑に絡み合い、解放の連鎖が止まる。
実装における設計パターン
以下のコードは、単なる「動くコード」ではない。メモリの挙動を制御することを目的とした「防衛的設計」に基づいたテンプレートである。
' ==============================================================================
' Module: DrawingAutomationEngine
' Description: 大規模図面処理におけるメモリ最適化実装
' ==============================================================================
Option Explicit
''' <summary>
''' 図面ファイルを安全に処理し、メモリを完全にクリーンアップするメインルーチン
''' </summary>
Public Sub ProcessLargeScaleDrawings()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
Dim fileList As Variant
Dim i As Long
On Error GoTo ErrorHandler
' SolidWorksへの接続
Set swApp = Application.SldWorks
If swApp Is Nothing Then Exit Sub
' 本来はファイルリストを動的に取得するが、ここでは例として
fileList = Array("C:\Temp\Drawing1.SLDDRW", "C:\Temp\Drawing2.SLDDRW")
For i = LBound(fileList) To UBound(fileList)
' 1ファイルごとにスコープを隔離し、メモリリークを最小限に抑える
Call ProcessSingleDrawing(swApp, CStr(fileList(i)))
' 連続処理によるメモリ蓄積を防ぐため、OSに制御を戻す間隔を設ける場合もある
' DoEvents は魔法の杖ではないが、メッセージキューの処理を促すために有効
DoEvents
Next i
MsgBox "全処理が正常に完了しました。", vbInformation
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
''' <summary>
''' 単一の図面に対する高度に隔離された処理ロジック
''' </summary>
Private Sub ProcessSingleDrawing(ByRef swApp As SldWorks.SldWorks, ByVal filePath As String)
' 内部変数には「極めて限定的なスコープ」を持たせる
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
Dim swSheet As SldWorks.Sheet
Dim swView As SldWorks.View
Dim vSheets As Variant
Dim sSheetName As Variant
' ドキュメントのオープン
Set swModel = swApp.OpenDoc6(filePath, swDocDRAWING, swOpenDocOptions_Silent, "", 0, 0)
If swModel Is Nothing Then Exit Sub
Set swDraw = swModel
' --- 図面操作ロジック開始 ---
' 例: 全シートのビューを走査
vSheets = swDraw.GetSheetNames
For Each sSheetName In vSheets
Set swSheet = swDraw.ActivateSheet(CStr(sSheetName))
' ビューの走査(ここでViewオブジェクトを生成・保持する)
Set swView = swDraw.GetFirstView ' Sheet自体が最初のViewとして扱われる点に注意
Do While Not swView Is Nothing
' --- ここにビューへの操作を記述 ---
' 例: swView.SetScale3 0.5, 0.5, swScaleType_e.swScaleType_Standard
' 次のビューへ
Set swView = swView.GetNextView
Loop
' 【重要】末端オブジェクトの明示的破棄
' ループ内で生成されたViewへの参照を確実に切る
Set swView = Nothing
Set swSheet = Nothing
Next sSheetName
' --- 図面操作ロジック終了 ---
' ドキュメントを閉じる
swApp.CloseDoc swModel.GetTitle
' 【極限の知見】
' VBAには .NETの Marshal.ReleaseComObject は存在しないが、
' 変数へのNothing代入を「階層の逆順」で行うことで、
' COM参照カウントを確実に減らす。
Set swDraw = Nothing
Set swModel = Nothing
End Sub
3. アーキテクチャの極意:隔離(Isolation)と分離(Decoupling)
上記のコードで最も重要なのは、`ProcessSingleDrawing` という「隔離されたサブプロシージャ」に処理を閉じ込めている点だ。
なぜプロシージャを分けるのか?
VBAのメモリ管理において、ローカル変数はプロシージャの終了とともにスコープから外れる。しかし、大規模なループ内で全ての処理を一つの `Sub` に記述してしまうと、全てのオブジェクト変数がループの最後まで生き残り続け、参照カウントが累積していく。
プロシージャを分けることで、以下のメリットが得られる。
1. スタックのクリーンアップ: プロシージャ終了時に、そのスコープ内で宣言された変数が(論理的に)破棄対象となる。
2. エラーハンドリングの局所化: 一つの図面の破損が、システム全体のクラッシュを招くのを防ぐ。
3. デバッグの容易性: どのオブジェクトが解放に失敗しているのか、スタックトレースから特定しやすくなる。
さらに上を行く:プロセス分離戦略
もし、数百枚の図面処理が数時間に及ぶような極めてクリティカルなタスクであれば、VBA(Excel/SolidWorks内蔵VBA)の限界を認めるべきだ。
真のシニアエンジニアは、「VBAをオーケストレーター(指揮者)として使い、実務は外部プロセスに投げる」設計を選択する。
- VBA: ファイルリストの管理、進捗管理、全体のフロー制御。
- VB.NET / C# (外部EXE): SolidWorks APIを用いた実務処理。
VB.NETであれば、`System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)` を用いて、COMオブジェクトの参照カウントを「物理的に」強制的にデクリメントできる。これはVBAには不可能な、COM制御における究極の武器である。
結論
大規模なSolidWorks自動化における成功は、コードの書きやすさではなく、「いかに美しく、痕跡を残さずにオブジェクトを消し去るか」にかかっている。
1. 階層を意識せよ: `View` $\rightarrow$ `Sheet` $\rightarrow$ `Drawing` $\rightarrow$ `App` の順で破棄せよ。
2. スコープを最小化せよ: 処理は小さなプロシージャに分割し、変数の寿命をコントロールせよ。
3. 限界を知れ: VBAの挙動が制御不能になったら、迷わず.NETによるプロセス分離へ移行せよ。
これらを遵守することで、君の構築するシステムは、数千枚の図面を前にしても、静寂を保ちながら正確に仕事を完遂するだろう。
