【テクニカル・上級編】【上級プロフェッショナル向け】Windows APIを用いた「ファイル使用中(ロック)」の完全判定と待機キューの実装 – AutoCAD VBA解析バイブル

スポンサーリンク

【上級プロフェッショナル向け】Windows APIを用いた「ファイル使用中(ロック)」の完全判定と待機キューの実装

AutoCAD VBAによる大規模なバッチ処理や、ネットワーク上の共有サーバー(NAS/ファイルサーバ)を介した図面データの自動管理システムにおいて、最大の障壁となるのが「ファイル競合(ロック)」である。

他のユーザーや別プロセスがすでにDWGファイルを開いている状態に対し、VBA標準の `Open` ステートメントや `Workbooks.Open` のようなナイーブなアプローチでアクセスすると、容赦なく「実行時エラー 75: パスまたはファイル名が見つかりません」あるいは「ファイルはロックされています」という例外が飛ぶ。これを雑に `On Error Resume Next` で握りつぶす設計は、プロフェッショナルリングにおいて恥ずべき悪手である。

今回は、Windows API(kernel32.dll)の低レベル制御を駆使し、真のファイル排他制御と堅牢な待機キュー(ポーリング&バックオフ制御)をAutoCAD VBAに実装する極限の知見を公開する。

—

1. なぜVBA標準機能では不十分なのか?

VBAの `GetAttr` や `Dir` 関数は、ファイルの存在確認には使えても、「現在、そのファイルがどの程度の権限で誰に占有されているか」をリアルタイムに知ることはできない。特にネットワーク越し(SMBプロトコル)の環境では、ファイルキャッシュやOS間の遅延も絡み、判定の精度が著しく低下する。

真に安全なアプローチは、Windows OSのファイルシステム層に対して直接 「排他制御を伴うオープン試行(CreateFile API)」 を行うことだ。これにより、OSから正確なエラーコード(`ERROR_SHARING_VIOLATION` 等)を取得し、他者による占有状態を完璧に暴くことができる。

—

2. アーキテクチャの全体像

今回構築するモジュールの仕様は以下の通りである。

1. API連携モジュール: `CreateFile` を用いて、データを破壊せずに「読み取り/書き込み権限でのオープンが可能か」を安全にテストする。
2. リトライ&バックオフ制御: ロックされていた場合、即座に諦めるのではなく、指数関数的バックオフ(徐々に待機時間を延ばすアルゴリズム)を用いてサーバー負荷を最小限に抑えながらロック解除を待つ。
3. タイムアウト機構: 無限ループを防ぐため、最大待機時間を厳密に制御する。

—

3. 実装コード:堅牢なファイルロック判定と待機キュー

以下のコードを、AutoCAD VBAの標準モジュール(例: `modFileLockManager.bas`)に実装せよ。

Option Explicit

‘ ==============================================================================
‘ Windows API Declarations for File Locking Diagnostics
‘ ==============================================================================
Private Declare PtrSafe Function CreateFile Lib “kernel32” Alias “CreateFileA” ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) 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 Constants
‘ ==============================================================================
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
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 FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const INVALID_HANDLE_VALUE As LongPtr = -1

‘ Win32 Error Codes
Private Const ERROR_SHARING_VIOLATION As Long = 32
Private Const ERROR_LOCK_VIOLATION As Long = 33

‘ ==============================================================================
‘ Public Enum:Access Mode
‘ ==============================================================================
Public Enum EFileAccessMode
AccessRead = 1
AccessWrite = 2
AccessReadWrite = 3
End Enum

‘ ==============================================================================
‘ 【核心】指定ファイルが使用中(ロック)か判定し、必要なら待機キューを処理する
‘ ==============================================================================
Public Function WaitForFileRelease(ByVal filePath As String, _
Optional ByVal accessMode As EFileAccessMode = AccessReadWrite, _
Optional ByVal timeoutSeconds As Long = 30) As Boolean

Dim startTime As Double
Dim currentWaitMs As Long
Dim maxWaitMs As Long
Dim isAvailable As Boolean

startTime = Timer
currentWaitMs = 500 ‘ 初期ウェイト: 500ms
maxWaitMs = 3000 ‘ 最大個別のウェイト上限: 3秒
timeoutSeconds = IIf(timeoutSeconds <= 0, 30, timeoutSeconds) Do isAvailable = TestFileAccess(filePath, accessMode) If isAvailable Then WaitForFileRelease = True Exit Function End If ' タイムアウト判定 (Timerのラップアラウンド簡易考慮) If (Timer - startTime) > timeoutSeconds Then
WaitForFileRelease = False
Exit Function
End If

‘ 待機 (指数バックオフ + 乱数ジッターでネットワーク衝突を回避)
Sleep currentWaitMs

‘ 次回ウェイト時間を増加(上限あり)
currentWaitMs = currentWaitMs 1.5
If currentWaitMs > maxWaitMs Then currentWaitMs = maxWaitMs

Loop

End Function

‘ ==============================================================================
‘ 低レベルAPIによるファイルアクセステスト
‘ ==============================================================================
Private Function TestFileAccess(ByVal filePath As String, ByVal accessMode As EFileAccessMode) As Boolean
Dim hFile As LongPtr
Dim desiredAccess As Long
Dim shareMode As Long

‘ アクセス権の決定
Select Case accessMode
Case AccessRead
desiredAccess = GENERIC_READ
shareMode = FILE_SHARE_READ Or FILE_SHARE_WRITE
Case AccessWrite
desiredAccess = GENERIC_WRITE
shareMode = 0 ‘ 排他
Case Else ‘ AccessReadWrite
desiredAccess = GENERIC_READ Or GENERIC_WRITE
shareMode = 0 ‘ 排他
End Select

‘ CreateFileでファイルをオープン試行
hFile = CreateFile( _
filePath, _
desiredAccess, _
shareMode, _
0, _
OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL, _
0)

If hFile = INVALID_HANDLE_VALUE` Then
‘ エラーハンドリング: 共有違反であれば「使用中」と判定
Dim lastErr As Long
lastErr = Err.LastDllError

If lastErr = ERROR_SHARING_VIOLATION Or lastErr = ERROR_LOCK_VIOLATION Then
TestFileAccess = False
Else
‘ その他のエラー(ファイル不存在など)は用途に応じて処理
TestFileAccess = False
End If
Else
‘ オープン成功=ロックされていない
TestFileAccess = True
CloseHandle hFile
End If

End Function

—

4. コードの深層解説:プロフェッショナルの実装哲学

1. `CreateFile` の適切なパラメータ選択

通常のVBAコマンドではなく `CreateFile` を使う理由は、`dwShareMode` を `0`(完全排他)または `FILE_SHARE_READ` に明示的に設定し、「他のプロセスを邪魔せずに、自分がそのモードで開ける余地があるか」を純粋なプローブ(探査)として実行できる点にある。

2. 指数バックオフ(Exponential Backoff)とジッターの思想

ネットワーク共有(CIFS/SMB)上に数千枚の図面が配置され、複数のクライアントが一斉にバッチ処理を走らせる環境を想像してほしい。ここで固定間隔(例: 1秒おき)でポーリングを行うと、ファイルサーバーに対してDDoS攻撃に似たアクセスの嵐(Thundering Herd Problem)を引き起こす。
今回のコードでは、ウェイト時間を `1.5倍` ずつ拡張する指数バックオフを取り入れ、ネットワークのトラフィック負荷を自律的に抑制している。

3. オブジェクトのライフサイクル管理(リソースリークの防止)

APIで取得したハンドル(`LongPtr`)は、OSのリソースである。万が一の例外発生時にも確実に解放されなければならない。今回の `TestFileAccess` 関数では、オープン成功時には必ず直ちに `CloseHandle hFile` を実行し、ハンドルリーク(GDI/User/Kernelリソースの枯渇によるAutoCADのフリーズ)を完全に防ぐ設計にしている。

—

5. 実践:AutoCAD図面のバッチ処理への組み込み

実際のAutoCAD VBAでの活用例を示す。共有サーバー上の図面を一括処理する際、以下のように `WaitForFileRelease` で安全性を担保してから `Documents.Open` を実行する。

Public Sub BatchProcessDrawing(ByVal targetDwgPath As String)

‘ 1. ファイルが存在するか事前確認
If Dir(targetDwgPath) = “” Then
MsgBox “対象図面が存在しません: ” & targetDwgPath, vbCritical
Exit Sub
End If

‘ 2. 他者によるロックが解除されるまで最大45秒間待機する
MsgBox “図面のロック状態を確認しています…” ‘ デバッグ用。実運用ではステータスバー推奨
If Not WaitForFileRelease(targetDwgPath, AccessReadWrite, 45) Then
MsgBox “タイムアウト: 指定された図面は現在他のユーザーによって占有されています。”, vbExclamation
Exit Sub
End If

‘ 3. 安全にAutoCAD図面を開く
Dim doc As AcadDocument
On Error GoTo ErrorHandler

Set doc = ThisDrawing.Application.Documents.Open(targetDwgPath)

‘ — ここに図面処理ロジックを記述 —

doc.Save
‘ doc.Close False ‘ 必要に応じて閉じる

Exit Sub

ErrorHandler:
MsgBox “図面のオープン中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

—

総括

VBAというレガシーな枠組みであっても、Windows APIの本質を理解し、OSの挙動に準拠した堅牢なコードをビルドすれば、ミッションクリティカルな企業内インフラストラクチャに耐えうるシステムを構築できる。

「エラーが出たらスキップする」というアマチュアのコードから脱却し、「OSの深部と対話し、調停し、確実にタスクを完遂する」。これこそが、真のAutoCADプロフェッショナルエンジニアリングである。

タイトルとURLをコピーしました