OneDrive/SharePoint時代のAutoCAD自動化:同期競合を制する「ファイルロック監視アルゴリズム」の実装
AutoCADの自動化プロセスを設計する際、多くのエンジニアが陥る「見えない罠」がある。それは、コードの論理的欠陥ではなく、「モダンなストレージ環境との物理的な衝突」だ。
かつて、ファイル操作はローカルディスクとの一対一の対話だった。しかし、現代の業務環境はOneDriveやSharePointといったクラウドストレージが標準だ。これらは、ファイルが更新された瞬間にバックグラウンドで「同期プロセス」を走らせ、ファイルハンドルを強奪する。
AutoCADが図面を保存しようとした瞬間に、OneDriveがそのファイルを同期のためにロックしている——。このコンマ数秒のタイミングのズレが、`Permission Denied (Err 70)` や `Path/File access error (Err 75)` を引き起こし、せっかくの自動化ツールを「不安定なゴミ」へと変えてしまうのだ。
本記事では、単なるリトライ処理ではない。Windows APIを駆使し、ファイルの状態を決定論的に監視する、プロフェッショナル仕様の「ファイルロック回避アルゴリズム」を伝授する。
1. なぜ `On Error Resume Next` では足りないのか
初心者は、エラーが出たら `Sleep` させてリトライする、というコードを書く。
' ―― 避けるべき、プロフェッショナルとは呼べない実装 ――
On Error Resume Next
Do
Err.Clear
ThisDrawing.SaveAs filePath
If Err.Number = 0 Then Exit Do
DoEvents
Sleep 1000
Loop
この設計がなぜ致命的なのか。理由は2つある。
1. 不確実な待機時間: 1秒で済むのか、1分かかるのかが予測できない。同期が長引けば、無限ループ、あるいはユーザーへの応答なしのフリーズを招く。
2. 不完全な状態判定: `SaveAs` が失敗したとき、ファイルが「書き込み途中」なのか「ロックされているだけ」なのかを判別できていない。最悪の場合、破損したファイルで上書きされるリスクがある。
我々が目指すべきは、「ファイルが物理的に解放されるまで、指数関数的バックオフ(Exponential Backoff)を用いて、スマートに待ち続ける」設計である。
2. 極限の設計思想:ファイルロック監視アルゴリズム
堅牢な設計には、以下の3つのコンポーネントが必要だ。
1. Low-level Lock Checker: Windowsのファイルシステムレベルで、対象ファイルが他のプロセス(OneDrive等)に掴まれていないかを判定する関数。
2. Smart Waiter (Exponential Backoff): 失敗するたびに待機時間を段階的に増やす制御ロジック。これにより、短時間のロックには即座に対応し、長時間の同期にはシステム負荷を抑えて待機できる。
3. Atomic Save Pattern: 直接ファイルを上書きせず、「一時ファイルへの保存 $\to$ 元ファイルの解放確認 $\to$ 置換」というアトミックな操作を行うことで、データ消失を防ぐ。
3. 実装:プロダクション・グレードのコード
以下に、そのまま業務ツールに組み込めるレベルのモジュールを示す。64bit版AutoCAD(VBA環境)を想定し、`PtrSafe` を適用している。
Option Explicit
' Windows APIの宣言
If VBA7 Then
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Else
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
End If
''' <summary>
''' クラウド同期等の競合を回避しながら、安全に図面を保存する。
''' </summary>
''' <param name="targetDoc">保存対象のAutoCADドキュメント</param>
''' <param name="targetPath">保存先フルパス</param>
Public Sub SafeSaveDWG(ByRef targetDoc As Object, ByVal targetPath As String)
Dim tempPath As String
tempPath = targetPath & ".tmp"
' 1. まずは一時ファイルとして保存(元のファイルを汚さない)
On Error GoTo ErrorHandler
targetDoc.SaveAs tempPath
' 2. 保存した一時ファイルが、同期プロセスによってロックされていないか確認
' ロックされている間は、指数関数的バックオフで待機する
If Not WaitForFileAvailability(tempPath) Then
Err.Raise vbObjectError + 513, "SafeSave", "ファイルが長時間ロックされており、解放されませんでした。"
End If
' 3. 元のファイルがロックされている可能性があるため、解放を待つ
If Not WaitForFileAvailability(targetPath) Then
' 元ファイルが存在しない場合は新規作成なので無視して良いが、
' 存在する場合はロック解除を待機する必要がある
If Dir(targetPath) <> "" Then
Err.Raise vbObjectError + 514, "SafeSave", "既存の図面ファイルが同期中のため、置換できません。"
End If
End If
' 4. アトミックな置換操作
' 既存ファイルを削除し、一時ファイルをリネームする
If Dir(targetPath) <> "" Then Kill targetPath
Name tempPath As targetPath
Debug.Print "Successfully saved: " & targetPath
Exit Sub
ErrorHandler:
Debug.Print "Critical Error in SafeSave: " & Err.Description
' 一時ファイルが残っていればクリーンアップ
If Dir(tempPath) <> "" Then Kill tempPath
Err.Raise Err.Number, "SafeSave", Err.Description
End Sub
''' <summary>
''' ファイルが利用可能(ロックされていない)になるまで待機する。
''' 指数関数的バックオフアルゴリズムを採用。
''' </summary>
Private Function WaitForFileAvailability(ByVal filePath As String) As Boolean
Dim retryCount As Integer
Dim maxRetries As Integer
Dim waitTime As LongPtr
Dim currentWait As LongPtr
maxRetries = 8 ' 最大リトライ回数
currentWait = 500 ' 初期待機時間 (0.5秒)
For retryCount = 1 To maxRetries
If IsFileAvailable(filePath) Then
WaitForFileAvailability = True
Exit Function
End If
' 指数関数的バックオフ: 待機時間を倍増させていく (500ms, 1000ms, 2000ms...)
Sleep currentWait
currentWait = currentWait * 2
DoEvents ' OSに制御を戻し、同期プロセスが進む余地を作る
Next retryCount
WaitForFileAvailability = False
End Function
''' <summary>
''' ファイルを排他的に開けるか試行することで、ロック状態を判定する。
''' </summary>
Private Function IsFileAvailable(ByVal filePath As String) As Boolean
Dim fileNum As Integer
On Error Resume Next
fileNum = FreeFile
' Binaryモードかつ排他的アクセスを試みる
Open filePath For Binary Access Read Write Lock Read Write As #fileNum
If Err.Number = 0 Then
Close #fileNum
IsFileAvailable = True
Else
' Err 70 (Permission Denied) 等が発生した場合はロック中と判断
IsFileAvailable = False
End If
On Error GoTo 0
End Function
4. アーキテクトの視点:設計の要諦
このコードを理解する上で、以下の3つのポイントを脳に刻んでほしい。
① 指数関数的バックオフの重要性
なぜ `currentWait = currentWait * 2` としているのか。
OneDriveの同期は、数ミリ秒で終わることもあれば、数分かかることもある。一定のインターバル(例:常に1秒)でリトライすると、同期中のCPU負荷を無駄に高め、かつ、長い同期に対しては非効率だ。待ち時間を倍々に増やすことで、「素早いレスポンス」と「低負荷な待機」を両立させている。
② `DoEvents` の真価
ループ内で `DoEvents` を呼び出すことは、単なる「おまじない」ではない。これは、VBAの実行スレッドに一時的に制御を戻し、Windowsのメッセージキューを処理させる命令だ。これにより、バックグラウンドで走っている同期プロセス(OneDrive.exe)がCPUリソースを確保しやすくなり、結果としてファイル解放が早まる。
③ アトミック性の確保
`targetDoc.SaveAs targetPath` を直接実行してはいけない。もし保存中に通信エラーや同期エラーが起きれば、元の図面は「中途半端な状態のファイル」に置き換わり、破損する。
必ず 「一時ファイルへ保存 $\to$ 成功を確認 $\to$ 置換」 というステップを踏むこと。これが、プロフェッショナルが守るべきデータの聖域だ。
結論
AutoCADの自動化は、CADの機能を知っているだけでは不十分だ。その背後にあるWindows OS、ファイルシステム、そしてクラウドストレージの挙動を理解して初めて、実戦に耐えうる「武器」となる。
今回紹介したアルゴリズムは、単なるエラー回避策ではない。「不確実な環境において、いかに決定論的な結果を導き出すか」という、ソフトウェアエンジニアリングの本質的な回答である。
このコードを、君のツールキットの核として組み込んでほしい。
