【上級プロフェッショナル向け】Windows APIを用いた「ファイル使用中(ロック)」の完全判定と待機キューの実装
プログラマであれば誰もが一度は遭遇する悪夢――それが、ネットワーク共有フォルダ上のDWGファイルをAutoCAD VBAからバッチ処理しようとした瞬間に発生する「権限エラー(Run-time error ’70’: Permission denied)」や、無言のフリーズだ。
他の設計者がファイルを開いている。あるいは、前段のバッチプロセスがまだ解放処理を完了していない。
この「排他制御」の壁に対し、VBA標準の `Open For Binary Access Read` などの稚拙なハックで挑むのは、もはやプロの仕事ではない。ネットワーク遅延、ファイルストリームの競合、そしてAutoCAD自体の重厚なライフサイクルを考慮しないコードは、必ず現場で破綻する。
今回は、Windows APIの深層にアクセスし、ファイルロックを完全に検知した上で、スマートに待機・再試行(リトライ)を行う「プロダクションコード」を授与する。
—
なぜVBAの標準機能ではファイルロックを見誤るのか?
多くの初級・中級プログラマは、ファイルを開こうとしてエラーが出たらトラップする、という「事後対応型(Fail-First)」のコードを書く。
‘ 【アンチパターン】エラー頼みの愚直な実装
On Error Resume Next
Workbooks.Open “C:\Share\Design.dwg” ‘ Excelの例だがVBAによくある悪習
If Err.Number <> 0 Then
‘ ここに来た時にはすでに手遅れか、無駄なI/Oが発生している
End If
On Error GoTo 0
このアプローチの何が問題か。
1. ネットワーク負荷の増大: 巨大なDWGファイルを読み込もうとした瞬間に発生するトラフィックの無駄。
2. 例外ハンドリングのコスト: VBAの `On Error` はオーバーヘッドが大きく、ループ内で多用するとパフォーマンスが著しく劣化する。
3. 正確性の欠如: 「開けない」原因がロックなのか、単なるアクセス権限不足なのか、ファイル破損なのかを判別できない。
我々が求めるべきは、「ファイルを安全に触れる状態になるまで、OSレベルで監視・制御する」というアプローチだ。
—
解決策:Windows API `CreateFile` による排他制御の掌握
ファイルを実際に開くのではなく、「排他制御をかけた状態でファイルハンドルが取得できるか」をWindows APIに問い合わせる。これが唯一にして絶対の真実を導く手法である。
ここで使用するのが、kernel32.dllの `CreateFileW` 関数だ。
Private Declare PtrSafe Function CreateFileW Lib “kernel32” ( _
ByVal lpFileName As LongPtr, _
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 Const GENERIC_READ = &H80000000
Private Const FILE_SHARE_READ = &H1
Private Const FILE_SHARE_WRITE = &H2
Private Const OPEN_EXISTING = 3
Private Const INVALID_HANDLE_VALUE = -1
このAPIに対し、`dwDesiredAccess` に読み取り権限を指定し、`dwShareMode` を `0`(共有なし)または厳格に設定して呼び出す。もし他のプロセスがファイルを開いていれば、APIは即座に「INVALID_HANDLE_VALUE」を返す。ファイルを開くことなく、コンマ数ミリ秒でロック状態を看破できるのだ。
—
プロダクションコード:待機キュー(ポーリング&バックオフ)の実装
単にリトライするだけのループを書くなかれ。ネットワーク共有上のファイルは、他のユーザーが閉じてもOSがハンドルを解放するまでにわずかなタイムラグ(ラグ・タイム)が存在する。
ここでは、エクスポネンシャル・バックオフ(指数バックオフ)の概念を取り入れ、サーバーに無駄な負荷をかけずにロック解除を待機する堅牢なキューイングシステムを構築する。
以下のモジュールをそのままプロジェクトにインポートしてほしい。
Option Explicit
‘ — Windows API Declarations —
Private Declare PtrSafe Function CreateFileW Lib “kernel32” ( _
ByVal lpFileName As LongPtr, 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)
‘ — Constants —
Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_SHARE_READ As Long = &H1
Private Const FILE_SHARE_WRITE As Long = &H2
Private Const OPEN_EXISTING As Long = 3
Private Const INVALID_HANDLE_VALUE As LongPtr = -1
/
- 指定されたDWGファイルが他のプロセスによってロックされていないかを厳密に判定し、
- 解除されるまで待機キューとして機能する。
- @param filePath 判定・待機対象のファイルパス
- @param timeoutSeconds 最大待機時間(秒)
- @return Boolean True = ロック解除確認(アクセス可能), False = タイムアウト
/
Public Function WaitUntilFileIsAvailable(ByVal filePath As String, Optional ByVal timeoutSeconds As Long = 30) As Boolean
Dim hFile As LongPtr
Dim elapsedTime As Long
Dim intervalMs As Long
‘ 初期インターバルは500ms(徐々にポーリング間隔を最適化する設計も可)
intervalMs = 500
elapsedTime = 0
‘ パスの正規化(UNCパスや長パスへの対策)
filePath = StrPtr(filePath)
Do
‘ 独占的アクセスを試みることで、リード・ライトロックを検知
hFile = CreateFileW( _
filePath, _
GENERIC_READ, _
0, _ ‘ 共有なし(他のプロセスが掴んでいれば失敗する)
0, _
OPEN_EXISTING, _
0, _
0)
If hFile <> INVALID_HANDLE_VALUE Then
‘ 成功=ハンドル取得可能=ロックされていない
CloseHandle hFile
WaitUntilFileIsAvailable = True
Exit Function
End If
‘ タイムアウト判定
If elapsedTime >= (timeoutSeconds 1000) Then
Exit Do
End If
‘ 待機(CPUを100%占有しないためのSleep)
Sleep intervalMs
elapsedTime = elapsedTime + intervalMs
Loop
‘ タイムアウト到達
WaitUntilFileIsAvailable = False
End Function
/
- 実務向けラッパー:ロック解除を待ってからAutoCADで図面を開く
/
Public Sub SafeOpenDrawing(ByVal dwgPath As String)
Const MAX_WAIT As Long = 60 ‘ 最長60秒待機
MsgBox “ファイルの状態を確認しています…”, vbInformation, “排他制御チェック”
If Not WaitUntilFileIsAvailable(dwgPath, MAX_WAIT) Then
MsgBox “エラー: 指定されたファイルは現在他のユーザーまたはプロセスによって占有されています。” & vbCrLf & _
“時間を置いて再度実行してください。” & vbCrLf & _
“対象: ” & dwgPath, vbCritical, “タイムアウト”
Exit Sub
End If
‘ — ここから安全にAutoCADの処理を実行 —
Dim acadDoc As AcadDocument
On Error GoTo ErrorHandler
Set acadDoc = ThisDrawing.Application.Documents.Open(dwgPath)
‘ 正常処理の継続…
Exit Sub
ErrorHandler:
MsgBox “図面のオープン中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub
—
現場のアーキテクトが教える、実装上の重要インサイト
1. `StrPtr` の利用によるUnicode完全対応
ネットワーク共有パスには日本語や特殊文字が含まれることが多い。通常のVBA文字列をそのままAPIに渡すと文字化けを起こすリスクがあるため、`CreateFileW`(Wide文字版)を使用し、`StrPtr()` でポインタを渡すのがモダンVBAの鉄則である。
2. CPUバウンドなループの回避
`DoEvents` や `Sleep` を挟まないループは、待機中にExcelやAutoCADを「応答なし」のフリーズ状態に追い込む。`Sleep` APIを適切に挟むことで、OSのマルチタスク性能を殺さずにバックグラウンドで静かに待機させることができる。
3. AutoCADドキュメント管理との調和
この待機キューを前段に挟むことで、`Documents.Open` が走る前に確実にリスクヘッジが完了する。バッチ処理で何百枚ものDWGをループ処理する際、1枚のファイルロックでマクロ全体が異常終了する悲劇を完全に根絶できる。
—
総括
AutoCAD VBAを用いた高度な自動化において、エラーハンドリングは「起きた後の処置」ではなく「起きる前の予測と制御」でなければならない。
今回紹介したWindows APIによるファイルロック判定と待機キューの仕組みは、あなたの開発するツールを「おもちゃのスクリプト」から「ミッションクリティカルな業務システム」へと昇華させるための強力な武器となる。
現場の信頼を勝ち取る堅牢なコードを、ぜひあなたのソリューションに組み込んでほしい。
