【テクニカル・上級編】【上級プロ】大量のコンポーネントを持つ巨大アセンブリのメモリリークを防ぐオブジェクト解放の極意 – SolidWorks VBA解析バイブル

スポンサーリンク

巨大アセンブリを殺すな:SolidWorks VBAにおける「真のメモリ管理」とオブジェクトの生存戦略

SolidWorks APIを使いこなしていると自負する中堅層が、必ず直面する壁がある。それが「巨大アセンブリの連続処理における突如の強制終了」だ。

「`Set obj = Nothing` を書いているのに、なぜメモリが解放されないのか?」

この問いに対する答えを持っていない者は、エンジニアとは呼べない。VBAのガベージコレクション(GC)は、お世辞にも賢いとは言えない。SolidWorksのCOMオブジェクトは、参照カウントが1残っているだけで、バックグラウンドのプロセスを死なせない。数万点のコンポーネントを扱うアセンブリにおいて、その「わずかな取り残し」は、システム全体を崩壊させるトリガーとなる。

今日は、伝説的なアーキテクチャの知見に基づき、SolidWorks APIを極限まで制御し、メモリリークを根絶するための極意を伝授する。

—

1. VBAの「Nothing」は魔法の杖ではない

多くのエンジニアが犯す最大の誤解は、`Set obj = Nothing` を書けばメモリが即座に回収されると信じていることだ。

VBAにおいて `Nothing` への代入は、あくまで「その変数とオブジェクトの接続を切る」行為に過ぎない。SolidWorksのバックエンド(COMインターフェース)では、そのオブジェクトが他の参照によって保持されている可能性を考慮し、即時のメモリ解放を保証しない。

巨大アセンブリをループ処理する際、以下のルールを鉄則とする。

  • スコープの最小化: 巨大な関数の中に全ての変数を宣言するな。処理単位で関数を分割し、変数の寿命を物理的に強制終了させろ。
  • プロパティのキャッシュ禁止: `AssemblyDoc.GetComponents` で取得した配列をグローバル変数で保持し続けるな。必要になった瞬間に取得し、使い終われば即座に破棄せよ。

2. 破壊的なメモリ管理:強制解放のテクニック

SolidWorks APIの `Component2` オブジェクトや `ModelDoc2` オブジェクトをループで回す際、内部的に参照カウントがリークするケースが多い。これを防ぐための、実戦的な解放パターンを提示する。

‘ 巨大アセンブリ内の全コンポーネントを走査する際のメモリ保護パターン
Public Sub ProcessHugeAssembly()
Dim swApp As SldWorks.SldWorks
Dim swAssy As SldWorks.AssemblyDoc
Dim swComp As SldWorks.Component2
Dim vComps As Variant
Dim i As Long

Set swApp = Application.SldWorks
Set swAssy = swApp.ActiveDoc

vComps = swAssy.GetComponents(False)

‘ 巨大な配列をループさせる際、変数の再利用は禁物
For i = LBound(vComps) To UBound(vComps)
Set swComp = vComps(i)

‘ 個別のコンポーネントに対する処理
Call PerformTaskOnComponent(swComp)

‘ ループ内で明示的に参照を破壊する
Set swComp = Nothing

‘ 【重要】巨大ループ時は定期的(例:100回毎)にDoEventsを挟み、
‘ UIのスタックをクリアする。これがないとSolidWorksはフリーズを検知する
If i Mod 100 = 0 Then DoEvents
Next i

‘ 後始末
Erase vComps
Set swAssy = Nothing
End Sub

3. なぜ「DoEvents」がメモリ管理の一部なのか

巨大アセンブリの処理中、Windowsのメッセージキューが飽和すると、SolidWorksは応答不能(Not Responding)と判定され、OS側から強制終了のシグナルが送られることがある。

`DoEvents` は単なる「GUIの更新」ではない。Windows APIのメッセージポンプを叩き起こし、溜まった不要なCOMポインタの解放処理をOS側に促すための「心臓マッサージ」だ。これを軽視する者は、巨大アセンブリを扱ってはいけない。

4. レガシー環境でのメモリ最適化:Windows APIの介入

VBAの限界を感じる場合、`Kernel32.dll` を呼び出し、明示的にプロセスのメモリフットプリントを確認・最適化する手法がある。

‘ プロセスのメモリをトリミングして空き容量を確保する(上級者向け)
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

Public Sub TrimMemory()
‘ メモリを解放し、スワップアウトを促す
Call SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1)
End Sub

※この `SetProcessWorkingSetSize` は劇薬だ。ループの直後に呼び出しすぎると、SolidWorksが逆にキャッシュを読み直すためにパフォーマンスが低下する。数千件のコンポーネント処理が終わった「節目」にのみ使用せよ。

—

5. シニアエンジニアへの提言:設計思想の転換

巨大アセンブリを自動化する際、最も重要なのは「SolidWorksを過信しないこと」だ。

1. ステートフルな処理を避ける: メモリ上にコンポーネントの参照を保持し続ける設計をやめ、ID(ComponentID)による再取得ベースの処理に切り替える。
2. ログによる追跡: どのコンポーネントでメモリ消費が跳ね上がっているか、`GetProcessMemoryInfo` を使ってログを出力せよ。リークの犯人は常に「複雑な合致(Mate)を持つコンポーネント」にある。
3. VB.NET/C#への移行検討: もし業務がレガシーVBAに依存しているなら、せめて処理のコアロジックをCOMインターフェースを介したDLL化(C#)へ移行せよ。VBAのGCよりも、.NETのガベージコレクタの方が遥かに高度なメモリ管理が可能だ。

技術は手段であり、目的ではない。
SolidWorksという巨大で複雑な怪物と対峙するとき、我々に求められているのは「コードを書くこと」ではなく、「いかにしてシステムを安定させ、止まらない自動化を実現するか」というアーキテクトとしての矜持だ。

メモリ管理の細部に宿る神を恐れよ。それが、君のシステムが次世代まで生き残るための唯一の道である。

タイトルとURLをコピーしました