堅牢性の極致:SolidWorks バッチ処理における「例外安全」な自動化アーキテクチャ
深夜、サーバー上で回されている数千枚の図面生成ジョブ。その進捗バーが、ある一つの破損した部品ファイル、あるいは予期せぬ「ファイルが見つかりません」というダイアログの出現によって、数時間後に停止している――。
これは、多くのエンジニアが経験する「自動化の敗北」である。
一般的なリファレンスは、`On Error GoTo` を使った基本的なエラーハンドリングを教える。しかし、それは「正常系に近いエラー」でしか通用しない。数千件のバッチ処理を完遂させるために必要なのは、「システムが予期せぬ状態(非定常状態)に陥った際、いかにして状態をリセットし、次のタスクへ遷移するか」という、レジリエンス(回復力)に基づいた設計思想である。
本稿では、SolidWorks APIを極限まで制御し、破損ファイルやモーダルダイアログのハングアップを物理的に打破する、プロフェッショナル向けのバッチエンジン設計を解説する。
1. COMオブジェクトのライフサイクルとメモリの「死の連鎖」
SolidWorks VBAにおいて、最も見落とされがちな、かつ致命的なのがCOMオブジェクトの解放漏れである。
バッチ処理において、`swApp.NewDocument` や `swDoc.Extension.SelectByID2` を繰り返すと、メモリ上に微細な参照が蓄積される。これがいわゆる「メモリリーク」だ。単にメモリが消費されるだけではない。参照が残っていることで、`swApp.CloseDoc` を呼び出してもプロセスが完全に解放されず、次のファイルを開く際のメモリ空間が断片化し、最終的に SolidWorks 自体が「応答なし」に陥る。
極限のメモリ管理原則
1. 明示的な破棄: すべてのオブジェクト変数に対し、使用直後に `Set obj = Nothing` を実行せよ。
2. スコープの最小化: オブジェクトの生存期間を、可能な限り関数(Sub/Function)の内部に閉じ込めろ。
3. 階層的な解放: `DrawingDoc` → `View` → `Sheet` の順に、下位オブジェクトから順に解放していくことが、COMの参照カウントを正しく減らす鉄則である。
2. Windows API による「ダイアログの物理的制圧」
API経由で操作している最中に、SolidWorksが「破損した参照」を検知してモーダルダイアログ(警告ダイアログ)を表示したとする。この瞬間、VBAのコードはダイアログの背後で「待機状態」となり、制御権を失う。これがバッチジョブがハングアップする最大の原因だ。
これを解決するには、VBAの範疇を超え、Windows APIを用いてプロセス外部からダイアログを検知・操作する必要がある。
' --- API Declaration ---
If VBA7 Then
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As Long
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Else
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
End If
Public Const WM_CLOSE = &H10
''' <summary>
''' SolidWorksの警告ダイアログが存在する場合、それを強制的に閉じる
''' </summary>
Public Sub KillSolidWorksDialogs()
Dim hwnd As LongPtr
Dim windowTitle As String
Dim buffer As String * 255
' SolidWorksのダイアログクラス名、またはタイトルの一部を指定
' ここでは例として、一般的なエラーダイアログを想定
hwnd = FindWindow(vbNullString, "SolidWorks") ' 親ウィンドウを探す
' 実際には、特定の警告メッセージのタイトルをキャッチするロジックを組む
' 例: FindWindow(vbNullString, "Error")
If hwnd <> 0 Then
' ダイアログが見つかった場合、WM_CLOSEメッセージを送信して強制終了させる
' SendMessageではなくPostMessageを使うことで、API自体のハングを防ぐ
PostMessage hwnd, WM_CLOSE, 0, 0
End If
End Sub
3. 実践:例外安全なリトライ・エンジン・パターン
単一の `On Error Resume Next` では、エラーの「原因」を特定できない。プロフェッショナルなエンジンは、「試行(Try)」「失敗(Catch)」「回復(Recover)」の3フェーズを分離して設計する。
以下に、破損ファイルに遭遇しても、ログを記録し、SolidWorksをクリーンな状態に戻して次のファイルへ進む、極限まで堅牢性を高めた実装の骨組みを示す。
Option Explicit
' 処理ステータス管理用定数
Private Const STATUS_SUCCESS = 0
Private Const STATUS_RETRY = 1
Private Const STATUS_FATAL = 2
''' <summary>
''' メインバッチエンジン
''' </summary>
Public Sub RunBatchProcess(fileList As Collection)
Dim filePath As Variant
Dim retryCount As Integer
Const MAX_RETRIES As Integer = 2
For Each filePath In fileList
retryCount = 0
Dim isProcessed As Boolean: isProcessed = False
Do While retryCount <= MAX_RETRIES And Not isProcessed
Dim result As Integer
result = ProcessSingleDrawing(CStr(filePath))
Select Case result
Case STATUS_SUCCESS
isProcessed = True
WriteLog filePath, "SUCCESS", ""
Case STATUS_RETRY
retryCount = retryCount + 1
WriteLog filePath, "RETRY", "Attempt " & retryCount
' 異常事態の回復処理:ダイアログを閉じ、ドキュメントを強制クローズ
RecoverSystemState
Case STATUS_FATAL
isProcessed = True ' Fatalはリトライしても無駄なので終了
WriteLog filePath, "FATAL_ERROR", "File corrupted or unrecoverable"
End Select
Loop
If Not isProcessed Then
WriteLog filePath, "FAILED", "Max retries exceeded"
End If
Next filePath
End Sub
''' <summary>
''' 個別ファイル処理(隔離された実行環境)
''' </summary>
Private Function ProcessSingleDrawing(targetPath As String) As Integer
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
On Error GoTo ErrorHandler
Set swApp = Application.SldWorks
' 1. ファイルオープン
Set swModel = swApp.OpenDoc6(targetPath, swDocDRAWING, swOpenDocOptions_Silent, "", 0, 0)
If swModel Is Nothing Then
ProcessSingleDrawing = STATUS_RETRY
GoTo CleanUp
End If
Set swDraw = swModel
' --- ここに図面生成・ビュー配置の核心ロジックを記述 ---
' 例: Viewの配置、シートフォーマットの制御など
' --------------------------------------------------
ProcessSingleDrawing = STATUS_SUCCESS
CleanUp:
' 明示的なオブジェクト解放(極めて重要)
Set swDraw = Nothing
If Not swModel Is Nothing Then
swApp.CloseDoc swModel.GetTitle
End If
Set swModel = Nothing
Set swApp = Nothing
Exit Function
ErrorHandler:
' エラー内容に応じた判定
' Err.Number などの情報を基に、リトライ可能か判断する
Debug.Print "Error in " & targetPath & ": " & Err.Description
ProcessSingleDrawing = STATUS_RETRY
Resume CleanUp
End Function
''' <summary>
''' システム状態の回復(ダイアログの排除とメモリのクリーンアップ)
''' </summary>
Private Sub RecoverSystemState()
' 1. Windows APIによるダイアログの強制終了
KillSolidWorksDialogs
' 2. SolidWorksの内部状態をリセットするための待機
DoEvents
Sleep 1000 ' Windows APIのSleepを使用
' 3. 必要に応じて、あまりに不安定な場合はSolidWorksプロセス自体を再起動するロジックをここに挟む
End Sub
''' <summary>
''' ログ出力(システム管理者向けの監査証跡)
''' </summary>
Private Sub WriteLog(path As String, status As String, msg As String)
Dim fnum As Integer
fnum = FreeFile
Open ThisWorkbook.Path & "\batch_log.txt" For Append As #fnum
Print #fnum, Now & " | " & status & " | " & path & " | " & msg
Close #fnum
End Sub
4. 結論:エンジニアリングの真髄
優れた自動化エンジニアは、「コードが正しく動くこと」をゴールとはしない。「コードが失敗したとき、システムがいかに美しく、かつ確実に崩壊を免れるか」をゴールとする。
今回紹介したアーキテクチャの要諦は以下の3点に集約される。
1. Isolation (隔離): 個別ファイルの処理を独立させ、一つのエラーが全体のフローを停止させない。
2. Detection (検知): Windows APIを用いて、VBAの制御下から外れた「不可視のエラー(ダイアログ)」を捕捉する。
3. Recovery (回復): 失敗を前提とし、状態をリセットして「次の試行」へ繋げるためのメカニズムを持つ。
深夜のサーバー室で、静かに、しかし確実にタスクを完遂し続けるエンジン。それこそが、我々プロフェッショナルが構築すべき真の自動化システムである。
