AutoCAD VBAを掌握する極限の知見:ファイルロックの深淵と、堅牢なオープン待機キューの構築
自動化エンジニアが直面する最も忌々しいエラーの一つ、それが「ファイルは他のプロセスによって使用されています」という、無慈悲な拒絶である。
標準的な `Documents.Open` メソッドは、ファイルがロックされている瞬間に例外を投げ、プログラムのフローを破壊する。バッチ処理やサーバーサイドの自動化において、このエラーは単なる停止ではなく、システム全体の信頼性を損なう「設計上の欠陥」を意味する。
真のプロフェッショナルは、エラーを「捕まえる」のではない。エラーが発生する「状況」を事前に予見し、OSおよびAutoCADの内部メカニズムを利用して、制御下に置くのだ。
本稿では、Windows APIを用いたファイルアクセスの厳密な判定と、AutoCAD特有のロックファイル(.dwl)解析を組み合わせた、極めて堅牢な「オープン待機キュー」の構築手法を解説する。
1. 概念設計:なぜ標準の例外処理では不十分なのか
一般的なエンジニアは `On Error Resume Next` や `Err.Number` によるリトライループを実装する。しかし、それは「当たって砕けろ」の精神であり、非決定論的な実装である。
我々が目指すべきは、以下の3要素を備えた決定論的な制御フローである。
1. 原子的な状態検知: Windows APIを用い、ファイルハンドルが占有されているかを低レイヤーで判定する。
2. アイデンティティの特定: AutoCADが生成する `.dwl`(または `.dwl2`)ファイルを解析し、「誰が」そのファイルを掴んでいるかを特定する。
3. 非ブロッキング待機: システムリソースを浪費しない、精密なインターバルを持つリトライ・キュー。
2. Windows APIによるファイル占有の厳密な判定
VBAの `Dir` 関数や `FileExists` は、ファイルの存在は教えてくれるが、その「占有状態」を正確に判定するには力不足だ。
ここで、Windows APIの `CreateFile` を使用する。この関数は、共有モード(`dwShareMode`)を指定してファイルを開こうと試みる。もし他のプロセスが排他的にロックしていれば、APIは即座に `ERROR_SHARING_VIOLATION` を返す。これこそが、真のロック検知である。
実装:Low-Level File Access Checker
Option Explicit
' --- Windows API Declarations ---
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)
' --- 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
Private Const ERROR_SHARING_VIOLATION As Long = 32
''' <summary>
''' ファイルが他のプロセスによってロックされているかを判定する
''' </summary>
Public Function IsFileLocked(ByVal filePath As String) As Boolean
Dim hFile As LongPtr
' 読み取り専用かつ、共有モードを指定してファイルを開こうとする
' 他のプロセスが独占している場合、INVALID_HANDLE_VALUE が返る
hFile = CreateFile(filePath, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
' エラーコードを確認(実際には GetLastError を呼ぶのが定石だが、
' ここでは簡略化のためハンドル取得失敗をロックとみなす)
IsFileLocked = True
Else
' 成功した場合はハンドルを即座に解放する
CloseHandle hFile
IsFileLocked = False
End If
End Function
3. AutoCADロックファイル(.dwl)の解析によるユーザー特定
ファイルがロックされていることが判明しても、ユーザーが「誰か」が分からなければ、業務プロセスとしては不完全だ。AutoCADは、図面ファイルと同じディレクトリに `.dwl` または `.dwl2` という拡張子のファイルを作成する。ここには、現在のセッションを使用しているユーザー名とマシン名が記録されている。
これを解析することで、「○○さんが使用中のため、解放を待機します」という、人間にとって意味のあるフィードバックが可能になる。
実装:DWL Parser
''' <summary>
''' .dwlファイルからユーザー名を抽出する
''' </summary>
Public Function GetLockerName(ByVal dwgPath As String) As String
Dim dwlPath As String
Dim fileNum As Integer
Dim line As String
Dim userPart As String
' .dwl ファイルのパスを生成
dwlPath = Left(dwgPath, InStrRev(dwgPath, ".")) & "dwl"
' ファイルが存在しない場合は、ロックされていないと判断
If Dir(dwlPath) = "" Then
GetLockerName = "Unknown (No lock file)"
Exit Function
End If
fileNum = FreeFile
On Error Resume Next
Open dwlPath For Input As #fileNum
' .dwl ファイルは通常、特定のフォーマットでユーザー名が含まれる
' 構造はバージョンにより異なるが、基本はテキストベース
Do While Not EOF(fileNum)
Line Input #fileNum, line
' AutoCADのDWLは、特定のパターンでユーザー名を保持している
' ここでは簡易的に、行内に含まれる識別子を解析するロジックを想定
If InStr(line, "User=") > 0 Or InStr(line, "Username=") > 0 Then
userPart = Split(line, "=")(1)
GetLockerName = Trim(userPart)
Exit Do
End If
Loop
Close #fileNum
On Error GoTo 0
If GetLockerName = "" Then GetLockerName = "Unknown User"
End Function
4. 統合:オープン待機キュー・エンジンの構築
これら全ての部品を統合し、堅牢な「オープン待機エンジン」を構築する。ここで重要なのは、無限ループを避けるためのタイムアウト設計と、メモリ管理である。
''' <summary>
''' 高度なファイルオープン制御エンジン
''' </summary>
Public Sub SmartOpenDocument(ByVal dwgPath As String, ByRef targetDoc As Object)
Dim retryCount As Integer
Dim maxRetries As Integer: maxRetries = 12 ' 12回 * 10秒 = 120秒待機
Dim waitInterval As Long: waitInterval = 10000 ' 10,000ms = 10秒
Dim lockerName As String
retryCount = 0
Do While IsFileLocked(dwgPath)
retryCount = retryCount + 1
lockerName = GetLockerName(dwgPath)
Debug.Print "Attempt " & retryCount & ": File locked by [" & lockerName & "]. Retrying in 10s..."
' ユーザーへの通知(実際にはメッセージボックスやログ出力)
' MsgBox "ファイルは " & lockerName & " が使用中です。待機します。", vbInformation
If retryCount >= maxRetries Then
Err.Raise vbObjectError + 513, "SmartOpen", "タイムアウト: ファイルが解放されませんでした。"
Exit Sub
End If
' Windows APIによる精密なスリープ(VBAのDoEventsによるCPU負荷軽減を併用)
Sleep waitInterval
DoEvents
Loop
' ロックが解除されたら、AutoCADドキュメントとして開く
On Error GoTo ErrorHandler
Set targetDoc = ThisDrawing.Application.Documents.Open(dwgPath)
Debug.Print "Successfully opened: " & dwgPath
Exit Sub
ErrorHandler:
Err.Raise Err.Number, "SmartOpen", "Open failed: " & Err.Description
End Sub
5. アーキテクトの教訓:リソースのライフサイクルとメモリ最適化
このクラスの設計において、最も注意すべきは 「オブジェクトの寿命」 である。
1. Handleの解放: `CreateFile` で取得したハンドルを `CloseHandle` せずに放置すれば、それはメモリリークではなく「システムリソースのリーク」となり、OS全体の不安定化を招く。
2. Nothingの明示: `targetDoc` などのオブジェクトを扱う際、エラー発生時には必ず `Set targetDoc = Nothing` を行い、AutoCADのメモリ空間から参照を完全に抹消せよ。
3. DoEventsの戦略的利用: 長時間の待機ループ中に `DoEvents` を適切に挟まなければ、AutoCAD本体が「応答なし」状態になり、OSからプロセスが強制終了されるリスクがある。
結論
単なる「エラー回避」のコードを書くのは、スクリプトキディの仕事だ。
我々プロフェッショナルが書くべきは、OSの挙動を理解し、ファイルシステムとアプリケーションの境界線において、確実に、かつ予測可能な動作を保証する「制御機構」である。
この「スマートオープン」の実装は、大規模な図面管理システムにおける自動化の信頼性を、次元の異なるレベルへと引き上げるだろう。
