【テクニカル・上級編】【上級プロフェッショナル向け】Windows APIを用いた「ファイル使用ユーザー名」の特定と、オープン待機キューの構築 – AutoCAD VBA解析バイブル

スポンサーリンク
スポンサーリンク

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の挙動を理解し、ファイルシステムとアプリケーションの境界線において、確実に、かつ予測可能な動作を保証する「制御機構」である。

この「スマートオープン」の実装は、大規模な図面管理システムにおける自動化の信頼性を、次元の異なるレベルへと引き上げるだろう。

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