【SolidWorks VBA極限の知見】第1回:堅牢な例外処理の実装とトランザクションログによるクラッシュ防止
シニアエンジニア、そして社内のCADインフラを預かるシステム管理者諸君。
日々の業務で、数千点規模のアセンブリを一括処理するマクロが、メモリリークや不正なAPI戻り値によって突如としてSolidWorksごと沈没する悪夢にうなされていないだろうか。
VBAの標準機能である `On Error GoTo` だけでは、巨大なCOMオブジェクトモデルを持つSolidWorksの異常終了を防ぐことは不可能だ。APIが返すHRESULTの隠れた意味、参照カウンタの正確なデクリメント、そしてファイルI/Oを伴う独自トランザクションのロールバック。これらを網羅した「防衛的プログラミング」の極意を、ここに授ける。
—
1. SolidWorks APIにおける例外とクラッシュのメカニズム
SolidWorks VBAの開発において、最大の敵は「VBランタイムエラー」ではなく、「COM例外(COM Exception)」と「メモリの不整合」である。
`ModelDoc2` や `SelectionMgr` などのAPIオブジェクトは、内部でC++製の巨大なコアエンジンのラッパーとして動作している。もし存在しないフィーチャーIDを指定したり、既に破棄された(Destroyされた)ドキュメントのポインタにアクセスしたりした場合、VBA側で捕捉できない致命的なアクセラ違反(Access Violation)が発生し、SolidWorksプロセスごと強制終了する。
これを防ぐためには、以下の3原則を徹底しなければならない。
1. すべてのAPI呼び出しの戻り値(HRESULTやステータス列挙体)を検証する
2. エラー発生時は即座に処理を中断し、開いたドキュメントを安全にクローズする
3. エラーの詳細とコンテキストをテキストログに永続化し、デバッグの再現性を担保する
—
2. 実装:堅牢なトランザクション管理とエラーハンドラー
以下に、実務の現場で即座に使える「堅牢な例外処理テンプレート」を提示する。
このコードは、ファイル処理の途中でエラーが発生した場合でも、確実にモデルを閉じ、メモリを解放し、独自のトランザクションログにエラーの足跡を残す構造を持っている。
Option Explicit
‘ Windows API Declarations for High-Precision Logging Timestamps
Private Declare PtrSafe Sub GetLocalTime Lib “kernel32” (lpSystemTime As SYSTEMTIME)
Private Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type
‘ 終了ステータス列挙体
Private Enum ExecutionStatus
Status_Success = 0
Status_Warning = 1
Status_Error = 2
Status_Fatal = 3
End Enum
/
- @brief メインエントリーポイント:安全なドキュメントバッチ処理
/
Public Sub ExecuteRobustBatchProcess()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim targetPath As String
Dim logPath As String
‘ 初期化
targetPath = “C:\CADData\ComplexAssembly.sldasm”
logPath = ThisWorkbook.Path & “\SolidWorks_Macro_Execution.log”
‘ 独自トランザクションの開始ログ
Call WriteTransactionLog(logPath, “TRANSACTION_START”, “バッチ処理を開始しました。”, Status_Success)
On Error GoTo ErrorHandler
‘ SolidWorks アプリケーションの取得 (Early Bindingを推奨)
Set swApp = CreateObject(“SldWorks.Application”)
If swApp Is Nothing Then
Err.Raise vbObjectError + 1000, “ExecuteRobustBatchProcess”, “SolidWorksのインスタンス取得に失敗しました。”
End If
‘ 可視化の設定(処理速度向上と予期せぬUI干渉の防止)
swApp.Visible = True
‘ ドキュメントのオープン(サイレントモード&エラーハンドリング)
Dim openError As Long
Dim openWarning As Long
Set swModel = swApp.OpenDoc6(targetPath, swDocASSEMBLY, swOpenDocOptions_Silent, “”, openError, openWarning)
If swModel Is Nothing Then
Err.Raise vbObjectError + 1001, “ExecuteRobustBatchProcess”, “ドキュメントのオープンに失敗しました。Error Code: ” & openError
End If
‘ — ここに実際の重いAPI処理を記述 —
Call ProcessHeavyModifications(swModel, logPath)
‘ ————————————
‘ 正常終了処理
Call WriteTransactionLog(logPath, “TRANSACTION_COMMIT”, “すべての処理が正常に完了しました。”, Status_Success)
GoTo SafeExit
ErrorHandler:
‘ 異常系ハンドリング(ロールバックとクリーンアップ)
Dim errDesc As String
errDesc = “Error [” & Err.Number & “]: ” & Err.Description & ” (Source: ” & Err.Source & “)”
Call WriteTransactionLog(logPath, “TRANSACTION_ROLLBACK”, errDesc, Status_Fatal)
‘ 変更を破棄してドキュメントを閉じる(強制ロールバック)
If Not swModel Is Nothing Then
Dim docTitle As String
docTitle = swModel.GetTitle()
swApp.CloseDoc docTitle
End If
SafeExit:
‘ オブジェクトの明示的解放(メモリリーク防止の極意)
Set swModel = Nothing
Set swApp = Nothing
Exit Sub
End Sub
/
- @brief 重負荷処理のシミュレーションとAPI戻り値の検証
/
Private Sub ProcessHeavyModifications(ByRef swModel As SldWorks.ModelDoc2, ByVal logPath As String)
Dim swFeatMgr As SldWorks.FeatureManager
Set swFeatMgr = swModel.FeatureManager
If swFeatMgr Is Nothing Then
Err.Raise vbObjectError + 2001, “ProcessHeavyModifications”, “FeatureManagerの取得に失敗しました。”
End If
‘ 例として強制的にエラーを誘発するような処理、あるいはAPIの成否判定
‘ 実際のコードではここでフィーチャーの編集やコンフィギュレーション切替を行う
Call WriteTransactionLog(logPath, “PROCESS_STEP”, “フィーチャーツリーの走査を実行中…”, Status_Success)
‘ 例外のテスト(意図的なエラー発生が必要な場合はここで条件分岐)
Set swFeatMgr = Nothing
End Sub
/
- @brief 高精度タイムスタンプ付きトランザクションログ出力関数
/
Private Sub WriteTransactionLog(ByVal logPath As String, ByVal actionTag As String, ByVal message As String, ByVal status As ExecutionStatus)
Dim fileNum As Integer
Dim st As SYSTEMTIME
Dim statusStr As String
GetLocalTime st
Select Case status
Case Status_Success: statusStr = “[INFO]”
Case Status_Warning: statusStr = “[WARN]”
Case Status_Error: statusStr = “[ERROR]”
Case Status_Fatal: statusStr = “[FATAL]”
End Select
Dim logLine As String
logLine = Format$(st.wYear, “0000”) & “/” & Format$(st.wMonth, “00”) & “/” & Format$(st.wDay, “00”) & ” ” & _
Format$(st.wHour, “00”) & “:” & Format$(st.wMinute, “00”) & “:” & Format$(st.wSecond, “00”) & “.” & _
Format$(st.wMilliseconds, “000”) & ” | ” & _
statusStr & ” | ” & _
“[” & actionTag & “] | ” & _
message
fileNum = FreeFile
Open logPath For Append As #fileNum
Print #fileNum, logLine
Close #fileNum
End Sub
—
3. シニアエンジニアが押さえるべき「メモリ最適化とポインタ管理」の極意
上記のコードにおいて、単なる「エラー処理の記述」以上に重要なのが、オブジェクト変数のライフサイクル管理である。
① 参照カウンタのデクリメントと `Nothing` 代入
VBAのガベージコレクションは遅延実行されるため、COMオブジェクト(`SldWorks.SldWorks` や `SldWorks.ModelDoc2` など)を参照したままエラーが発生すると、内部でC++側のポインタが宙ぶらりんになり、メモリリークや次回の実行時クラッシュを引き起こす。
処理の脱出経路(`SafeExit` および `ErrorHandler`)の両方で、必ず `Set obj = Nothing` を明示的に実行し、COMコンポーネントの参照カウントを即座に解放させることが鉄則である。
② `On Error GoTo` のスコープ汚染を防ぐ
VBAのエラーハンドラーはプロシージャ単位でしか定義できない。そのため、複雑な処理は必ず小さなサブプロシージャ(今回の例における `ProcessHeavyModifications` など)に分割し、それぞれのスコープで適切な例外伝播(`Err.Raise`)を行うこと。エラーを握りつぶす(`Resume Next` の乱用)ことは、インフラ管理者にとって最も忌むべき悪手である。
—
4. 総括
SolidWorks VBAの安定稼働は、運に頼るものではなく、「APIの挙動に対する深い理解」と「徹底的な防衛的プログラミング」によってのみ勝ち取れるものだ。
今回紹介したトランザクションログと厳格なオブジェクト解放のパターンをあなたの開発環境に導入すれば、マクロの異常終了によるデータ破損の恐怖から完全に解放されるだろう。
コードを書くのではない。「システムをデザインする」のだ。次回の知見にも期待してほしい。
