「アクセス拒否」の壁を越えて。OneDrive時代のAutoCAD VBA:同期競合を回避する究極のファイル制御術
こんにちは。現場で数々の自動化ツールを構築してきた、あなたの先輩エンジニアです。
「マクロの記録」から一歩踏み出し、自分でコードを書き始めた皆さん。素晴らしい挑戦ですね。しかし、AutoCAD VBAの世界には、初心者からベテランまでを等しく悩ませる「見えない敵」が存在します。
それが、OneDriveやSharePointといったクラウドストレージとの「同期競合」です。
「昨日まで動いていたマクロが、今日に限って『アクセスが拒否されました(Error 70)』と怒られる……」
「保存処理の途中で、突然AutoCADが止まってしまう……」
もしあなたがそんな経験をしているなら、それはあなたのコードが悪いのではありません。「ファイルが今、OS(Windows)やクラウドによって掴まれている(ロックされている)」という、現代特有の状況を想定できていないだけなのです。
今日は、その壁を打ち破るための「プロフェッショナルな待ち(リトライ)アルゴリズム」を伝授します。これをマスターすれば、あなたの作るツールは「現場で本当に使える、信頼性の高い道具」へと進化します。
1. なぜクラウド環境で「保存失敗」が起きるのか?
まず、敵の正体を理解しましょう。図解的に説明すると、以下のような「すれ違い」が起きています。
1. あなた(AutoCAD): 「よし、図面を保存するぞ!」 $\rightarrow$ *ファイルを書き込みモードで開こうとする*
2. OneDrive(同期エンジン): 「おっ、ファイルが更新されたな! すぐにクラウドへアップロードするぞ!」 $\rightarrow$ *ファイルを読み取り/同期モードでロックする*
3. あなた(AutoCAD): 「あれ? ファイルが開けない! アクセス拒否だ!」 $\rightarrow$ エラー発生!
このように、AutoCADがファイルを操作しようとした瞬間に、OneDriveが「同期のためにファイルを掴んでしまっている」のが原因です。これをファイルロック(File Lock)と呼びます。
プロのエンジニアは、この「すれ違い」を前提に設計します。「一度失敗したら終わり」ではなく、「相手が手を離すまで、賢く待つ」。これが極限の知見です。
2. Windows APIという「強力な武器」を手に入れる
VBA標準の機能(FileSystemObjectなど)でもファイルの状態は分かりますが、クラウドの同期ロックを正確に、かつ高速に検知するには、Windows APIを直接叩くのが最も確実で、最もパフォーマンスが高い方法です。
今回は、Windowsの深層部にアクセスして「このファイルは今、他のプロセスに使われているか?」を判定する関数を実装します。
実装するアルゴリズムの設計図
1. 判定フェーズ: Windows API(`CreateFile`)を使い、ファイルに「独占的なアクセス」を試みる。
2. 判定結果:
- 成功 $\rightarrow$ ロックされていない! $\rightarrow$ 保存実行へ。
- 失敗 $\rightarrow$ ロックされている! $\rightarrow$ 「少し待機してリトライ」へ。
3. リトライフェーズ: `Sleep`関数で数ミリ秒〜数秒休み、再度判定。
4. タイムアウト: 無限に待つわけにはいかないので、一定回数失敗したらエラーとして終了する。
3. 【実践】最強のファイルロック監視コード
さあ、ここからが本番です。以下のコードは、そのまま実務のモジュールに組み込めるレベルの設計にしています。
Option Explicit
' --- Windows API の宣言 ---
' ファイルのハンドルを操作するために必要です
Private Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As LongPtr, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr) As LongPtr
' ファイルを閉じるために必要です
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long
' 指定した時間(ミリ秒)だけ処理を一時停止させるために必要です
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
' 定数の定義(APIで使用)
Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_SHARE_NONE As Long = 0
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As LongPtr = -1
' --- メインロジック ---
''' <summary>
''' ファイルがロックされているかどうかを判定する関数
''' </summary>
''' <param name="filePath">対象ファイルのフルパス</param>
''' <returns>ロックされていればTrue、そうでなければFalse</returns>
Function IsFileLocked(ByVal filePath As String) As Boolean
Dim hFile As LongPtr
' CreateFileを使用して、ファイルに「独占的なアクセス権」を要求してみる
' 他のプロセス(OneDrive等)が掴んでいると、ここで失敗して INVALID_HANDLE_VALUE が返る
hFile = CreateFile(filePath, GENERIC_READ, FILE_SHARE_NONE, 0, OPEN_EXISTING, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
' 失敗した = 誰かが使っている(ロックされている)
IsFileLocked = True
Else
' 成功した = 独占的に開けた = ロックされていない
CloseHandle hFile
IsFileLocked = False
End If
End Function
''' <summary>
''' クラウド同期によるロックを回避しながら、安全に保存を行うプロフェッショナル版保存関数
''' </summary>
''' <param name="doc">対象のAutoCADドキュメント</param>
''' <param name="savePath">保存先のフルパス</param>
Sub SafeSaveAs(ByVal doc As AcadDocument, ByVal savePath As String)
Dim retryCount As Integer
Const MAX_RETRIES As Integer = 10 ' 最大10回リトライ
Const SLEEP_INTERVAL As Long = 1000 ' 待機時間(1000ms = 1秒)
On Error GoTo ErrorHandler
Debug.Print "保存プロセス開始: " & savePath
' --- リトライループの開始 ---
Do
If Not IsFileLocked(savePath) Then
' ロックされていないことが確認できたら、保存を実行
doc.SaveAs savePath
Debug.Print "保存に成功しました。"
Exit Sub
Else
' ロックされている場合
retryCount = retryCount + 1
Debug.Print "ファイルがロックされています。リトライ中... (" & retryCount & "/" & MAX_RETRIES & ")"
If retryCount >= MAX_RETRIES Then
Err.Raise vbObjectError + 513, "SafeSave", "同期エラー:ファイルが長時間ロックされています。手動で確認してください。"
End If
' 次のリトライまで少し待つ(ここでOneDriveの同期完了を待つ)
Sleep SLEEP_INTERVAL
End If
Loop
' --- リトライループ終了 ---
ErrorHandler:
If Err.Number <> 0 Then
MsgBox "【致命的なエラー】" & vbCrLf & Err.Description, vbCritical, "保存失敗"
End If
End Sub
''' <summary>
''' テスト用メインルーチン
''' </summary>
Sub TestSafeSave()
Dim targetDoc As AcadDocument
Set targetDoc = ThisDrawing ' 現在開いている図面
Dim myPath As String
myPath = "C:\Users\YourName\OneDrive\Documents\Test_Project.dwg" ' ※実際のパスに変更してください
' 安全な保存を実行
SafeSaveAs targetDoc, myPath
End Sub
4. コードの解説:ここをクリアすれば、VBAの基本はバッチリ!
初心者の方が「コピペして終わり」にならないよう、このコードの心臓部を噛み砕いて説明しますね。
① Windows API (`CreateFile`) の役割
通常のVBAでは `Open “file” For Input As #1` のような書き方をしますが、これは「中身を読む」ための命令です。
一方、今回の `CreateFile` は、「そのファイルを独占的に支配できるか?」をOSに直接聞きに行く、非常に強力な命令です。もしOneDriveがファイルを使っていれば、OSは「ダメだよ(Invalid Handle)」と答えます。この「ダメだよ」という返事を受け取ることが、ロック検知の鍵です。
② リトライループと `Sleep`
ここが「プロの設計」です。
エラーが出た瞬間にプログラムを終了させるのではなく、`Do…Loop` を使って「もう一度試す」というループを作っています。
そして、`Sleep` を入れるのが重要です。猛烈な勢いでリトライを繰り返すと、PCのCPUに負荷がかかり、かえって状況を悪化させます。「1秒休んで、また聞く」。この余裕が、クラウド同期との「すれ違い」を解消する魔法になります。
③ タイムアウト(MAX_RETRIES)
「いつまでも待ち続ける」のは、ユーザーにとって「フリーズした」ように見えてしまいます。
「10回試してもダメなら、諦めてエラーを出す」という期限を決めておく。これが、ユーザーに不快感を与えないための「おもてなし」の設計です。
5. 陥りやすいエラーと回避策
この技術を導入する際に、以下の点に注意してください。
- パスの指定ミス: `IsFileLocked` は、存在しないパスを渡すとエラーになります。事前に `Dir(filePath) <> “”` でファイルの存在確認をすることをお勧めします。
- 権限不足: ネットワークドライブや、管理者権限が必要なフォルダに保存しようとすると、ロックとは別の理由で `CreateFile` が失敗します。
- 無限ループの恐怖: `MAX_RETRIES` を設定し忘れると、OneDriveが同期に失敗し続けている場合、AutoCADが永遠に動きません。必ず「出口」を作りましょう。
まとめ:一歩先のエンジニアへ
いかがでしたか?
単に「保存する」という命令の裏側には、OSやクラウドといった、目に見えないプロセスの動きが複雑に絡み合っています。
「マクロの記録」は、いわば「決まった動作をなぞるだけ」のものです。
しかし、今回のように「もしこうなったら、こう動く」という例外処理(エラーハンドリング)を組み込むことが、真のプログラミングです。
このコードを自分のツールに組み込んでみてください。きっと、あなたの作るツールは、同僚やクライアントから「このマクロ、すごく安定しているね!」と信頼されるようになるはずですよ。
応援しています。次はもっと深い、オブジェクトのライフサイクル管理についてお話ししましょう!
