SolidWorks VBAを掌握する極限の知見:数千点のアセンブリループ処理におけるCOMメモリリーク完全撲滅の鉄則
開発プロジェクトのリーダーである私のもとに、よくこんな悲鳴が届く。
「数千点規模の大型アセンブリをVBAで自動構築・処理するスクリプトを書いたが、ループが後半に差し掛かるにつれて極端に動作が重くなり、最終的にSolidWorksごとフリーズする、あるいはExcelがクラッシュする」
原因は明白だ。COMオブジェクトの参照カウントの管理放棄、そして不適切な変数解放である。
VBAは一見するとガベージコレクションを備えた優しい言語に見えるが、それはExcelのセルやシートといった単一アプリケーション内の話だ。背後でC++の重厚なCOM(Component Object Model)アーキテクチャが稼働しているSolidWorks APIを相手にする時、VBAの自動メモリ管理など全くあてにならない。
今回は、数千点のアセンブリをループ処理しても1バイトのメモリリークをも許さない、プロフェッショナルな設計思想と実装の鉄則を授けよう。
—
1. なぜVBAの「なんとなく書いたコード」はメモリリークを起こすのか?
SolidWorks VBAでアセンブリのコンポーネントを巡回し、合致(Mate)を動的に生成する処理を想像してほしい。
‘ 【悪手】ありがちな地雷コード
Dim swAssy As SldWorks.AssemblyDoc
Set swAssy = swApp.ActiveDoc
Dim swCompList As Variant
swCompList = swAssy.GetComponents(False)
Dim i As Long
For i = LBound(swCompList) To UBound(swCompList)
‘ ループ内で次々とオブジェクトを取得
Dim swComp As SldWorks.Component2
Set swComp = swCompList(i)
‘ 何らかの処理や合致定義…
Debug.Print swComp.Name2
Next i
このコードの何が問題か?
VBAの `For` ループ内や暗黙のオブジェクト参照において、SolidWorks側から返却されたCOMインターフェースの参照カウンタ(Reference Count)がインクリメントされたまま、VBAのスコープ外に放置されている。
COMの世界では、ポインタを取得するたびに「使います」という意思表示(AddRef)がなされ、使い終わったら「解放します」という宣言(Release)が必要だ。VBAはスコープを抜ける時に一括で解放してくれる「こともある」が、数千回もの巨大なループ内では解放のタイミングが追いつかず、SolidWorksのプロセス(SLDWORKS.exe)のヒープ領域が徐々に肥大化し、最終的にOut of Memory(メモリ不足)を引き起こす。
—
2. メモリリークを完全撲滅するための3大鉄則
大規模アセンブリを安全に踏破するため、以下の鉄則をコードに刻み込め。
鉄則①:ループ変数は必ずループ「外」で宣言し、毎回 `Nothing` を代入して明示的に解放する
VBAの `Dim` をループの内部で行うと、意図しないスコープの残存やポインタの解放漏れを誘発する。変数は必ずプロシージャの先頭、あるいはループの外で宣言し、ループの末尾で必ず `Set xxx = Nothing` を叩いて参照カウントを強制的にデクリメントさせよ。
鉄則②:Variant型の配列(GetComponentsなど)も、使い終えたら即座にクリアする
`swAssy.GetComponents(False)` が返す配列自体もCOMの参照を内包している。これをメモリ上に残したままループを回すと、確実にメモリがリークする。
鉄則③:オブジェクトの階層構造(ドキュメント > コンポーネント > フィーチャー > 合致)を意識した逆順解放
生成・取得した順序ではなく、依存関係の深い末端のオブジェクトから順に `Nothing` を代入していくのがデストラクタ的アプローチの基本である。
—
3. 【プロダクションコード】数千点アセンブリの安全走査と合致定義エンジン
それでは、上記の鉄則をすべて網羅した、実務でそのまま使える堅牢なVBAコードを提示する。このコードは、巨大アセンブリ内の特定コンポーネント群を走査し、メモリリークを完全に抑え込みながら処理を行うテンプレートだ。
Option Explicit
Sub Production_AssemblyMemorySafeRunner()
‘ — 1. アプリケーション・ドキュメントルートの取得 —
Dim swApp As SldWorks.SldWorks
Set swApp = Application.SldWorks
Dim swModel As SldWorks.ModelDoc2
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
Dim swAssy As SldWorks.AssemblyDoc
Set swAssy = swModel
‘ — 2. コンポーネント配列の取得 —
‘ Falseを指定してトップレベルだけでなく、サブアセンブリも含めて取得
Dim vComps As Variant
vComps = swAssy.GetComponents(False)
If IsEmpty(vComps) Then
MsgBox “コンポーネントが存在しません。”, vbExclamation
Exit Sub
End If
‘ — 3. ループ変数の事前宣言(メモリリーク防止の要) —
Dim i As Long
Dim swComp As SldWorks.Component2
Dim swChildModel As SldWorks.ModelDoc2
Dim lUBound As Long
lUBound = UBound(vComps)
‘ 処理開始のログ(パフォーマンス計測用)
Dim startTime As Double
startTime = Timer
On Error GoTo ErrorHandler
‘ — 4. 厳格なメモリ管理下でのループ処理 —
For i = 0 To lUBound
‘ コンポーネントの取得
Set swComp = vComps(i)
‘ 抑制状態(Suppressed)のコンポーネントはスキップして無駄な処理を回避
If Not swComp.IsSuppressed() Then
‘ 必要に応じてコンポーネントのモデルドキュメントにアクセス
‘ Set swChildModel = swComp.GetModelDoc2()
‘ —————————————————-
‘ ここに実際の合致定義やプロパティ操作ロジックを記述
‘ 例: Debug.Print swComp.Name2
‘ —————————————————-
‘ 子モデルを取得していた場合はここで解放
If Not swChildModel Is Nothing Then
Set swChildModel = Nothing
End If
End If
‘ 【最重要】ループの1イテレーション毎にCOMオブジェクトの参照を完全に断つ
Set swComp = Nothing
Next i
Debug.Print “処理完了: 経過時間 ” & (Timer – startTime) & ” 秒”
CleanUp:
‘ — 5. 最終的なルートオブジェクトの確実な解放 —
Set swComp = Nothing
Set swChildModel = Nothing
vComps = Empty ‘ Variant配列のメモリ破棄
Set swAssy = Nothing
Set swModel = Nothing
Set swApp = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
—
4. チーフアーキテクトからの実践的アドバイス
上記のコードを見て、「ここまで厳格に `Nothing` を代入する必要があるのか?」と思った読者もいるかもしれない。
答えは「Yes。数千点規模を扱うなら、これですら最低ライン」だ。
もしあなたの自動化ツールが、さらに複雑な「ループ内で新規コンポーネントをアセンブルし、その都度 `AddMate5` などの合致フィーチャーを生成する」という処理を含んでいる場合、生成される合致フィーチャー(Mate)のオブジェクト自体もループ内で必ず解放しなければならない。
外部データベースやCSV連携時の注意点
数千点のアセンブリを操作する場合、Excelや外部のSQLite/SQL Serverなどのデータベースから「どのパーツとどのパーツをどう合致させるか」のマスターデータを読み込みながら処理することが多い。
この時、データベースのコネクションやレコードセット(ADODB.Recordsetなど)の解放も、SolidWorksのCOM解放と全く同じ思想で管理すること。VBAのADO接続をループ内で開きっぱなしにすると、こちらも見事にメモリリークを引き起こす。データベース接続はループの外で1度だけ行い、メモリ上にディクショナリ(`Scripting.Dictionary`)としてキャッシュした上で、SolidWorksのコンポーネント名と突合せるアーキテクチャを採用するのが、プロとしての正しい選択だ。
総括
SolidWorks VBAにおけるマクロ開発は、もはや単なる「おまけの自動化スクリプト」ではない。数千点のモデルを正確に、高速に、そして安定して制御する「エンジニアリングシステム開発」そのものである。
COMオブジェクトのライフサイクルを支配する者だけが、巨大アセンブリの自動化という聖域を制覇できる。ぜひ、今日の知見をあなたの開発現場のコードベースに適用し、一切のフリーズとサヨナラしてほしい。
