ネットワークの不確実性を制御せよ:AutoCAD VBAにおける堅牢な「再試行型セーブ」の実装論
業務自動化の最前線において、コードの正しさは「ローカル環境で動作するか」では測れない。真のプロフェッショナルが向き合うべきは、NAS(Network Attached Storage)のSMBプロトコルが引き起こす一瞬の遅延、共有サーバーによるファイルロック、そして分散環境におけるI/Oの不確実性だ。
AutoCADの`SaveAs`メソッドを単に呼び出すだけのスクリプトは、設計としては未完成である。ネットワークの瞬断や、他のプロセスによる一時的なアクセス権の競合が発生した瞬間、そのシステムは「脆弱な玩具」へと成り下がる。
本稿では、シニアエンジニアが実装すべき、Windows APIを駆使した「リトライアル(再試行)メカニズム」の極致を解説する。
1. 敗北のシナリオ:なぜ標準のSaveAsは失敗するのか
多くの開発者が陥る罠は、`On Error Resume Next`による場当たり的なエラー回避だ。しかし、ネットワーク越しのファイル操作において発生するエラーは、致命的な論理エラーではなく、「時間的な不整合(Transient Error)」であることが多い。
- SMBプロトコルの遅延: ファイルヘッダーの書き込み中にネットワークのパケットロスが発生。
- アンチウイルスソフトの干渉: 保存直後のファイルスキャンによる一時的なファイルロック。
- 共有サーバーの排他制御: 他のユーザーまたはプロセスによる、短時間の読み書きロック。
これらは、数秒待機して再試行するだけで解決する問題である。これを「致命的なエラー」として処理し、ユーザーに中断を強いる設計は、大規模な設計フローにおいては致命的な生産性の低下を招く。
2. 技術的解法:Windows APIによる精密な待機制御
VBA標準の`Wait`関数や、単なる`For…Next`による空ループは、CPUリソースを不必要に消費し、OSのスケジューリングを阻害する。我々が求めるのは、呼び出しスレッドを適切にスリープさせ、OSに制御を戻しつつ、ミリ秒単位でリトライタイミングを制御する手法だ。
ここで、`kernel32.dll`の`Sleep`関数を導入する。
Windows APIの宣言
' --- 宣言セクション ---
If VBA7 Then
' 64bit環境(AutoCAD 2014以降の多く)に対応した宣言
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Else
' レガシーな32bit環境用
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
End If
3. 実装の真髄:堅牢なリトライ・ロジックの実装
以下に示すのは、単なる保存処理ではない。エラーの種類を識別し、オブジェクトのライフサイクルを意識した、実戦投入レベルの関数である。
''' <summary>
''' ネットワーク遅延を考慮した堅牢な名前を付けて保存
''' </summary>
''' <param name="acDoc">対象のAutoCAD Documentオブジェクト</param>
''' <param name="targetPath">保存先のフルパス</param>
''' <param name="fileFormat">AutoCADのファイル形式(acR2018等)</param>
''' <returns>成功時はTrue、致命的な失敗時はFalse</returns>
Public Function RobustSaveAs(ByRef acDoc As Object, _
ByVal targetPath As String, _
ByVal fileFormat As Long) As Boolean
Const MAX_RETRIES As Integer = 5 ' 最大リトライ回数
Const RETRY_INTERVAL_MS As Long = 3000 ' リトライ間隔(3秒)
Dim retryCount As Integer
Dim isSuccess As Boolean
Dim errNum As Long
isSuccess = False
retryCount = 0
' エラーハンドリングの開始
On Error GoTo ErrorHandler
Do While retryCount < MAX_RETRIES
' 保存試行
acDoc.SaveAs targetPath, fileFormat
' ここに到達したということは成功を意味する
isSuccess = True
Exit Do
ErrorHandler:
errNum = Err.Number
' エラーコードの判別(実装環境により適宜調整)
' 70: Permission Denied (ファイルロック等)
' 75: Path/File access error
' 53: Path not found (ネットワークの一時的な切断)
Select Case errNum
Case 70, 75, 53
retryCount = retryCount + 1
' ログ出力(実務では専用のロギングクラスを使用すること)
Debug.Print "Warning: Save failed (Error " & errNum & "). " & _
"Retry attempt " & retryCount & "/" & MAX_RETRIES
' Windows APIによる精密なスリープ
Sleep RETRY_INTERVAL_MS
' エラーをクリアしてループを継続
Err.Clear
Case Else
' リトライ不可能な致命的エラー(メモリ不足、不正なパス形式等)
Debug.Print "Critical Error: " & Err.Description
GoTo Finalize
End Select
Loop
Finalize:
' オブジェクトのライフサイクル管理
' 引数で渡されたacDoc自体をNothingにしてはいけないが、
' 内部で生成した一時オブジェクトがある場合はここで明示的に解放する。
RobustSaveAs = isSuccess
Exit Function
End Function
4. アーキテクトの視点:メモリ最適化とオブジェクトの整合性
この実装において、シニアエンジニアが特に注視すべきは「エラー発生時のオブジェクトの状態」である。
オブジェクトのライフサイクル
`SaveAs`がエラーを返した際、AutoCADのドキュメントオブジェクトが「中途半端な状態(Dirty State)」にある可能性がある。リトライを行う前に、そのドキュメントがメモリ上で破損していないか、あるいは他のアドインがハンドリングしていないかを考慮しなければならない。
メモリリークの防止
VBAはCOM(Component Object Model)上で動作している。エラーハンドリングの `ErrorHandler` 内で、`Set obj = Nothing` を適切に行わないまま `Err.Clear` してループを回すと、エラー発生のたびに参照カウントが乱れ、メモリリークを引き起こす。特に、大量の図面をバッチ処理するシステムでは、この「微細な漏れ」が数時間後にAutoCADのクラッシュを招く。
バージョン互換性の制御
`fileFormat` 引数には、`acR2018` (2018形式) や `acR2013` といった定数を渡す。ネットワーク越しに古いバージョンの図面を書き出す際、ファイルヘッダーの整合性チェックが厳格に行われるため、リトライロジックは「書き込みの成否」だけでなく「ファイルヘッダーの確定」までを見守る必要がある。
結論
堅牢な自動化システムとは、単に「正しい処理を行うもの」ではない。「起こりうる不完全な環境において、いかにして完全な結果を導き出すか」を設計したものである。
Windows APIによる待機、厳格なエラーコードの選別、そしてオブジェクトのライフサイクルへの深い洞察。これらを備えて初めて、あなたの書いたコードは、現場の混沌としたインフラ環境において、揺るぎない信頼を勝ち取ることができるのだ。
