【上級プロ】巨大アセンブリ構築時におけるメモリリークを完全防止するための、COMオブジェクトの厳格な参照解放パターン
開発プロジェクトのリーダーである私から、現場で最も恐れられている現象について話をしよう。
数千点規模のコンポーネントをループ処理で組み込み、自動で巨大アセンブリを構築するVBAマクロ。帰宅前に実行ボタンを押し、翌朝出社してみたらSolidWorksごとフリーズし、未保存のデータとともにPCが沈黙している――。
君は、この悪夢のような状況に直面したことはないだろうか?
多くのエンジニアはこれを「SolidWorksのバグ」や「メモリ不足」で片付けようとする。だが、それは違う。真の原因は、VBAという言語のガベージコレクション(GC)の仕様を理解せず、COMオブジェクトの参照(Reference)を野放しにしている開発者自身の設計ミスにある。
今回は、数千点規模の巨大アセンブリ構築において、メモリリークを完全に封殺し、鉄壁の安定性を誇るプロダクションコードの設計思想を伝授する。
—
なぜ、VBAのループ処理はSolidWorksをクラッシュさせるのか?
SolidWorks APIをVBAから操作するとき、私たちは常に「COM(Component Object Model)の境界線」を跨いでいる。
VBAから `swApp.ActivateDoc3` や `AssemblyDoc.AddMate5` などのメソッドを呼び出すたび、裏側ではCOMサーバー(SolidWorks本体)側でオブジェクトが生成され、そのインターフェースポインタがVBA側に返される。
ここで問題になるのが、VBAのガベージコレクションの怠慢さだ。
‘ 【アンチパターン】これをしてはいけない
Dim i As Long
For i = 1 to 3000
‘ ループ内でCOMオブジェクトを次々と取得
Dim swMate As SldWorks.Mate2
Set swMate = swAssembly.AddMate(…)
‘ swMateの参照を解放しないまま、ループの次の周回へ進む
Next i
VBAのランタイムは、プロシージャ(SubやFunction)が終了するまで、あるいは変数がスコープを抜けるまで、COMオブジェクトの参照カウントをデクリメントしない。さらに言えば、ループ内での変数上書き(`Set var = …`)は、古いオブジェクトの参照を即座に解放するとは限らない。
結果として、数千回のループを回す間に数万個のゾンビのようなCOM参照がメモリ上に堆積し、VBAのヒープ領域とSolidWorksのプロセスを圧迫し、最終的にメモリー不足(Out of Memory)による強制終了を引き起こすのだ。
—
メモリリークを完全に断ち切る3つの鉄則
巨大アセンブリの自動生成において、以下の3つは絶対のルールである。
1. ループ内のCOMオブジェクトは、1回ごとに明示的かつ即座に `Nothing` 代入で解放する。
2. Variant型の暗黙的なオブジェクト保持を避け、型を明示する(Late BindingではなくEarly Bindingを徹底する)。
3. エラーハンドリング(`On Error GoTo`)を必ず実装し、異常終了時でもCOM参照のリークを残さない。
特に1番目。「`Set obj = Nothing` なんて意味があるのか?」と侮るなかれ。これを行わない限り、VBAはCOMオブジェクトの参照カウントを保持し続ける。ループの各イテレーションの最後に、確実にライフサイクルを断ち切る必要があるのだ。
—
【プロダクションコード】巨大アセンブリ構築・メモリ管理テンプレート
以下のコードは、数千点のパーツを安全にアセンブリへ配置・合致させ、メモリリークを完全に防止するための実践的なテンプレートだ。そのまま実務の現場で利用できる堅牢性を持たせている。
Option Explicit
‘ =========================================================================
‘ 巨大アセンブリ自動構築スクリプト(メモリリーク完全防止版)
‘ =========================================================================
Sub BuildMegaAssembly_ProductionModel()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swAssy As SldWorks.AssemblyDoc
‘ エラーハンドリングの有効化(異常終了時のクリーンアップ用)
On Error GoTo ErrorHandler
‘ 1. SolidWorksセッションの取得
Set swApp = Application.SldWorks
If swApp Is Nothing Then
MsgBox “SolidWorksが起動していません。”, vbCritical
Exit Sub
End If
Set swModel = swApp.ActiveDoc
If swModel Is Nothing Then
MsgBox “アクティブなアセンブリが存在しません。”, vbCritical
Exit Sub
End If
If swModel.GetType() <> swDocASSEMBLY Then
MsgBox “アクティブドキュメントがアセンブリではありません。”, vbCritical
Exit Sub
End If
Set swAssy = swModel
‘ 処理開始前のパフォーマンス向上設定
swApp.SetUserPreferenceToggle swInteractiveMode, False
swModel.EditRebuild3
‘ =====================================================================
‘ メインループ:数千点規模のコンポーネント配置と合致定義
‘ =====================================================================
Dim i As Long
Dim totalComponents As Long
totalComponents = 3000 ‘ 例:3000個のコンポーネント
Dim componentPath As String
componentPath = “C:\MyParts\StandardBolt.sldprt”
For i = 1 To totalComponents
‘ — 宣言のスコープをループ内に閉じ込める —
Dim swCompModel As SldWorks.ModelDoc2
Dim swComponent As SldWorks.Component2
Dim swMateFeat As SldWorks.Feature
Dim swMate As SldWorks.Mate2
‘ コンポーネントの挿入
Set swCompModel = swApp.OpenDoc6(componentPath, swDocPART, swOpenDocOptions_Silent, “”, 0, 0)
If Not swCompModel Is Nothing Then
Set swComponent = swAssy.AddComponent5(componentPath, 0, “”, False, “”, 0, 0, 0)
If Not swComponent Is Nothing Then
‘ ここで合致(Mate)の定義処理を行う(簡略化のためダミー関数コール想定)
‘ Set swMateFeat = DefineConcentricMate(swAssy, swComponent, …)
‘ 【重要】生成したフィーチャー・メイトオブジェクトの参照があれば個別に処理
If Not swMateFeat Is Nothing Then
‘ 必要に応じた処理
End If
End If
End If
‘ — 【最重要】ループごとの明示的な参照解放 —
‘ 生成したCOMオブジェクトを直ちに破棄し、メモリ蓄積を防ぐ
Set swMate = Nothing
Set swMateFeat = Nothing
Set swComponent = Nothing
Set swCompModel = Nothing
‘ 進捗状況をステータスバーに表示(数千回ループのフリーズ感を緩和)
If i Mod 50 = 0 Then
swApp.SendMsgToUser2 “Processing: ” & i & ” / ” & totalComponents & ” components integrated.”, 0, 0
DoEvents ‘ OSに制御を戻し、UIのハングアップを防ぐ
End If
Next i
CleanUp:
‘ 処理終了後の復元処理
If Not swApp Is Nothing Then
swApp.SetUserPreferenceToggle swInteractiveMode, True
End If
‘ ルートオブジェクトの解放
Set swAssy = Nothing
Set swModel = Nothing
Set swApp = Nothing
MsgBox “アセンブリの自動構築が正常に完了しました。”, vbInformation
Exit Sub
ErrorHandler:
‘ 予期せぬエラー発生時でも確実にCOMオブジェクトを解放してクラッシュを防ぐ
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
—
コードレビュー:なぜこの設計が「プロの現場」で選ばれるのか?
1. スコープの局所化(Variable Scoping)
`Dim` 宣言をループの外側で行うと、変数はプロシージャ終了までメモリ上に残り続ける。ループの「内側」で変数を宣言・破棄することで、VBAのメモリ管理サイクルを強制的にタイトに回している。
2. `DoEvents` とインタラクティブモードの制御
数千回のループ処理はWindowsメッセージキューを圧迫し、「応答なし」と判定されやすい。`swInteractiveMode` を `False` にして画面描画を抑制しつつ、適度な間隔で `DoEvents` を挟むことで、OSとの協調動作を維持しながら高速化を達成している。
3. 二段構えの解放(CleanUp & ErrorHandler)
万が一ループ内でファイルが見つからないなどの例外が発生しても、`On Error GoTo` によって必ず `CleanUp` ラベルへ誘導される。これにより、SolidWorksとVBAを繋ぐパイプラインがゾンビ化するのを防ぎ、安全にセッションを維持できる。
—
リーダーからの総括
業務自動化エンジニアとしての価値は、「動くコードを書くこと」ではない。「数千回実行しても、一度たりともリソースを漏らさず、長期間ノーメンテナンスで稼働し続ける堅牢なコードを構築すること」にある。
今回解説したCOMオブジェクトの厳格な参照解放パターンをマスターすれば、SolidWorks VBAの限界領域である巨大アセンブリの自動化をも完全に手中に収めることができるはずだ。
妥協のない設計で、君のプロジェクトを成功へと導いてほしい。
