【実務・中級編】【上級プロ向け】大規模マルチシート図面のメモリリークとCOMオブジェクト解放の完全最適化設計 – SolidWorks VBA解析バイブル

スポンサーリンク
スポンサーリンク

【上級プロ向け】大規模マルチシート図面のメモリリークとCOMオブジェクト解放の完全最適化設計

「昨日まで動いていた自動化ツールが、今日は100枚目の図面でフリーズした」
「処理が進むにつれて、タスクマネージャーのSolidWorksのメモリ使用量が指数関数的に増えていく」

もし君が、数百枚、数千枚という規模の図面をバッチ処理するツールを開発しているなら、この現象は「バグ」ではない。「設計の敗北」だ。

多くの開発者は、`Set swApp = Nothing` と書けばメモリが解放されると信じている。しかし、それはあまりにも無知な幻想だ。SolidWorksのような巨大なCOM(Component Object Model)サーバーを相手にする場合、オブジェクトのライフサイクルを完璧に制御できなければ、君のツールは必ず「死ぬ」。

本稿では、単なるコードの書き方ではなく、大規模処理に耐えうる「堅牢なオブジェクト解放アーキテクチャ」の極意を伝授する。

1. なぜ `Set Nothing` だけでは不十分なのか?

VBAにおけるCOMオブジェクトの管理は、参照カウンタ(Reference Counting)に基づいている。オブジェクトに対する参照がゼロになった瞬間に、メモリから解放される仕組みだ。

しかし、SolidWorksのAPIは極めて複雑な階層構造を持っている。
`Application` → `ModelDoc2` → `DrawingDoc` → `Sheet` → `View` …

ここで問題となるのが、「隠れた参照(Hidden References)」だ。
例えば、ある `View` オブジェクトを取得したとき、その内部では `Sheet` や `DrawingDoc` へのポインタが複雑に絡み合っている。VBA側で `View` を `Nothing` にしたとしても、その背後にあるSolidWorks内部のメモリ管理構造が「まだ誰かが使っている」と判断すれば、メモリは解放されない。

これが積み重なることで、メモリリークが発生し、最終的にOSのメモリを食いつぶしてSolidWorksがクラッシュする。あるいは、メモリ不足によるスワップが発生し、処理速度が極端に低下するのだ。

2. 極限の設計思想:スコープ分離と逆順解放

プロフェッショナルな設計には、2つの鉄則がある。

① スコープ分離(The Isolation Principle)

一つの `Sub` プロシージャの中で、すべての処理(ファイルオープンからクローズまで)を行ってはならない。巨大なプロシージャは、ローカル変数の寿命を不必要に引き延ばす。
「一つの図面、一つの単位(Unit)」を独立したプロシージャに隔離し、そのプロシージャが終了した瞬間に、その中で使ったすべての参照がスコープ外に落ちるように設計せよ。

② 逆順解放(Reverse Hierarchy Release)

オブジェクトを取得した順序とは「逆の順番」で、明示的に `Nothing` を代入していく。
階層の末端(ViewやSheet)から、階層の根元(ModelDocやApp)へと遡るように解放を行う。これが、COMの参照カウンタを確実に減らす唯一の作法である。

3. プロダクション・グレードの実装例

以下に、大規模図面処理における「黄金律」を適用したコードを示す。このコードは、単に動くだけではなく、メモリ管理の堅牢性を最優先している。

Option Explicit

' ==============================================================================
' メイン制御ルーチン
' 役割: ファイルリストを走査し、各図面に対して独立した処理ユニットを呼び出す
' ==============================================================================
Sub BatchProcessDrawings()
    Dim swApp As SldWorks.SldWorks
    Dim fileList As Collection
    Dim filePath As Variant
    
    On Error GoTo ErrorHandler
    
    ' SolidWorksインスタンスの取得(一度だけ)
    Set swApp = Application.SldWorks
    
    ' 本来はDBやExcelから取得するが、ここではサンプルとして
    Set fileList = GetFileListFromDatabase() 

    Debug.Print "--- Batch Process Started ---"

    ' 各ファイルに対して独立したプロシージャを呼び出す
    ' これにより、各ファイル処理終了時にローカル変数が破棄される
    For Each filePath In fileList
        Debug.Print "Processing: " & filePath
        
        ' 処理の分離(Isolation)
        Call ProcessSingleDrawing(swApp, CStr(filePath))
        
        ' 処理の合間にOSへ制御を戻すための微小な猶予(必要に応じて)
        DoEvents 
    Next filePath

    Debug.Print "--- Batch Process Completed Successfully ---"
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

' ==============================================================================
' 処理ユニット(Isolation Unit)
' 役割: 単一の図面に対する全ての操作。
' 重要な設計: 終了時にすべてのCOMオブジェクトを「逆順」でNothingにする
' ==============================================================================
Private Sub ProcessSingleDrawing(ByRef swApp As SldWorks.SldWorks, ByVal targetPath As String)
    ' オブジェクト変数の宣言(プロシージャのスコープ内に閉じ込める)
    Dim swModel As SldWorks.ModelDoc2
    Dim swDraw As SldWorks.DrawingDoc
    Dim swSheet As SldWorks.Sheet
    Dim swView As SldWorks.View
    Dim boolstatus As Boolean
    
    On Error GoTo SubErrorHandler

    ' 1. ファイルのオープン
    Set swModel = swApp.OpenDoc6(targetPath, swDocDRAWING, swOpenDocOptions_Silent, "", 0, 0)
    If swModel Is Nothing Then
        Debug.Print "Failed to open: " & targetPath
        Exit Sub
    End If

    ' 2. DrawingDocへのキャスト
    Set swDraw = swModel
    
    ' --- ここから図面操作のロジック ---
    ' 例: 全シートのビュー情報を取得する
    Set swSheet = swDraw.GetCurrentSheet
    If Not swSheet Is Nothing Then
        Set swView = swDraw.GetFirstView ' Sheet自体が最初のViewとして扱われる
        
        Do While Not swView Is Nothing
            ' ここでビューの属性変更などの重い処理を行う
            ' ...
            
            ' 次のビューへ
            Set swView = swView.GetNextView
        Loop
    End If
    ' --- ここまでロジック ---

    ' 3. ファイルの保存とクローズ
    swModel.Save3 swSaveAsOptions_Silent, 0, 0
    swApp.CloseDoc swModel.GetTitle

CleanUp:
    ' 【極めて重要】逆順解放アーキテクチャ
    ' 階層の深い順に Nothing を代入し、参照カウンタを確実に削る
    Set swView = Nothing
    Set swSheet = Nothing
    Set swDraw = Nothing
    Set swModel = Nothing
    Exit Sub

SubErrorHandler:
    Debug.Print "Error in single file [" & targetPath & "]: " & Err.Description
    Resume CleanUp
End Sub

' ==============================================================================
' サポート関数(モック)
' ==============================================================================
Private Function GetFileListFromDatabase() As Collection
    Dim col As New Collection
    col.Add "C:\Temp\Drawing1.SLDDRW"
    col.Add "C:\Temp\Drawing2.SLDDRW"
    ' 実際にはここでSQLやExcelからリストを構築する
    Set GetFileListFromDatabase = col
End Function

4. アーキテクトが教える、さらなる高みへの注意点

データベース・ファイル連携の罠

大規模処理では、図面情報だけでなく、マスタデータ(ExcelやSQL)との連携が不可欠だ。
ここで注意すべきは、「ファイル・ハンドル(File Handle)の開放」である。
VBAで `Open` ステートメントを使ってテキストファイルを読み書きしたり、Excelオブジェクトを操作したりする場合、エラーが発生して `Set Nothing` まで到達しなかった瞬間に、そのファイルは「他のプロセスが使用中のため開けません」という状態に陥る。
必ず `On Error GoTo CleanUp` を実装し、エラー時であっても必ずクローズ処理を通る設計にせよ。

パフォーマンスの重み:API呼び出しの回数

メモリ解放と同じくらい重要なのが、API呼び出しのコストだ。
`swModel.GetTitle` や `swModel.Parameter` のようなプロパティへのアクセスは、内部的にCOM通信を発生させる。ループの中でこれらのプロパティを何度も呼び出すのではなく、一度変数に格納(キャッシュ)してから利用せよ。

プロフェッショナルのためのチェックリスト

1. [ ] Isolation: 全ての図面処理は、独立した `Sub` に封じ込められているか?
2. [ ] Reverse Release: オブジェクトは取得した順序の逆で `Nothing` にされているか?
3. [ ] Error Trapping: エラー発生時、必ず `CleanUp` セクションを通ってオブジェクトが解放されるようになっているか?
4. [ ] Scope Control: グローバル変数に `swApp` 以外のオブジェクトを保持していないか?(グローバル変数はメモリリークの温床だ)

結論

大規模な自動化ツールにおいて、コードの「正しさ」とは、期待通りの結果を出すことだけではない。「いかなる状況下でも、リソースを美しく、完全に、制御下に置き続けること」である。

この設計思想をマスターすれば、君のツールは単なる「便利なマクロ」から、エンタープライズレベルの「堅牢なシステム」へと昇華するだろう。

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