ファイルロックの深淵:Windows APIを用いた決定論的なファイル占有検知
AutoCADの自動化、あるいは大規模なCADデータ管理システムを構築するエンジニアが、必ずと言っていいほど直面する壁がある。それは、「ファイルが別のプロセスによって使用されている」という、非決定論的なランタイムエラーだ。
標準的なVBAの `On Error Resume Next` や、`FileSystemObject` による存在確認、あるいは単純な `Open` ステートメントでのバイナリ読み込み試行……。それらは、プロトタイプ制作には十分かもしれない。しかし、数千のDWGファイルを連続して処理するバッチシステムや、社内の基幹システムと連携するミッションクリティカルなツールにおいて、これらは「脆弱な設計」の代名詞でしかない。
なぜなら、それらの手法は「エラーが起きてから対処する」というリアクティブ(受動的)なアプローチであり、ファイルシステムにおける真の占有状態を正確に、かつ低レイヤーで把握できていないからだ。
本稿では、Windows APIの `CreateFile` を用い、OSのカーネルレベルでファイルの共有モード(Sharing Mode)を検証することで、他プロセスによるロック状態をプロアクティブ(能動的)に検知する、極限の制御手法を解説する。
1. なぜ `FileSystemObject` では不十分なのか
多くの開発者が `Scripting.FileSystemObject` を利用するが、これは高レベルなラッパーに過ぎない。FSOは「ファイルが存在するか」「属性は何か」を判定するには最適だが、「他のプロセスが書き込み権限で排他的にロックしているか」という、OSのファイルシステム・ドライバが管理する共有ロック(Share Lock)の状態を詳細に問い合わせる設計にはなっていない。
AutoCADの図面操作において致命的なのは、Excelがその図面をデータソースとして参照している、あるいは別のAutoCADインスタンスがその図面を編集モードで開いているといった状況だ。これらを検知せずに `Documents.Open` を実行すれば、AutoCADは予期せぬダイアログを表示するか、最悪の場合、COM例外をスローして自動化プロセス全体をクラッシュさせる。
我々が求めるのは、「そのファイルに対して、今この瞬間に、排他的なアクセス権を取得できるか」という問いへの、決定論的な回答である。
2. Windows API `CreateFile` による真実の判定
Windows OSにおいて、ファイルのオープンは `CreateFile` 関数によって行われる。この関数の引数の一つに `dwShareMode` がある。ここが鍵だ。
この引数に `0` を指定すると、「他のプロセスによる読み取りも書き込みも一切許可しない(排他的アクセス)」という意味になる。もし、既に他のプロセスがそのファイルを何らかの形で開いていれば、`CreateFile` は `INVALID_HANDLE_VALUE` を返し、`GetLastError` は `ERROR_SHARING_VIOLATION` (32) を報告する。
これこそが、ファイルが「ロックされている」ことの物理的な証明である。
実装コード:極限のファイル検知モジュール
以下に、64bit環境(AutoCAD 2014以降の主流)に対応した、堅牢な検知ロジックを示す。
Option Explicit
' --- Windows API Declarations ---
' 64bit環境に対応するため PtrSafe を使用
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 Function GetLastError Lib "kernel32" () As Long
' --- Constants ---
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
Private Const ERROR_SHARING_VIOLATION As Long = 32
''' <summary>
''' 指定されたファイルが他のプロセスによってロックされているかを判定する。
''' </summary>
''' <param name="filePath">対象のフルパス</param>
''' <returns>ロックされている場合は True、使用可能なら False</returns>
Public Function IsFileLocked(ByVal filePath As String) As Boolean
Dim hFile As LongPtr
' 1. ファイルを「読み取り専用」かつ「共有モードなし(排他的)」で開こうと試みる
' これにより、他プロセスが少しでも共有ロックをかけていれば失敗する。
hFile = CreateFile( _
filePath, _
GENERIC_READ, _
FILE_SHARE_NONE, _
0, _
OPEN_EXISTING, _
0, _
0)
' 2. ハンドルの取得に失敗したか判定
If hFile = INVALID_HANDLE_VALUE Then
' 失敗理由を確認
If GetLastError() = ERROR_SHARING_VIOLATION Then
' 他のプロセスによる共有違反(ロック状態)
IsFileLocked = True
Else
' ロック以外のエラー(ファイルが存在しない、権限がない等)
' 実務上はここで詳細なエラーハンドリングを行うべきだが、
' 本機能の目的は「ロック検知」であるため、ここでは False とする
IsFileLocked = False
End If
Else
' 3. 成功した場合(=誰も使っていない)は、即座にハンドルを解放する
' これを怠ると、自プロセスがファイルを引き止める「ゾンビ・ロック」を引き起こす
CloseHandle hFile
IsFileLocked = False
End If
End Function
3. アーキテクトの視点:リソース管理とライフサイクル
このコードを実装する際、シニアエンジニアが最も注意すべきは 「ハンドルの解放(CloseHandle)」 である。
`CreateFile` は、OSのリソースを直接消費する。もし `IsFileLocked` が `False`(使用可能)を返した際に `CloseHandle` を呼び忘れた場合、そのVBAプロセス自体がファイルに対して「排他的なハンドル」を保持し続けてしまう。その結果、次にAutoCADがそのファイルを開こうとした際、「自分自身で自分をロックしている」という、デバッグ困難なデッドロック状態に陥るのだ。
オブジェクトのライフサイクルを制御することは、メモリ管理以上に、システム全体の安定性に直結する。
実践的なアプリケーション構成例
検知ロジックを単体で使うのではなく、AutoCADのドキュメント操作フローに組み込む。
Public Sub SafeOpenDWG(ByVal dwgPath As String)
On Error GoTo ErrorHandler
' 1. 事前検知(プロアクティブ・フェーズ)
If IsFileLocked(dwgPath) Then
MsgBox "エラー: ファイルは他のアプリケーションによって使用されています。" & vbCrLf & _
"対象ファイル: " & dwgPath, vbCritical, "ファイル占有検知"
Exit Sub
End If
' 2. 実行(オペレーショナル・フェーズ)
' ここで初めて AutoCAD の Open メソッドを叩く
Dim doc As AcadDocument
Set doc = ThisDrawing.Application.Documents.Open(dwgPath)
' 3. 処理...
Debug.Print "Successfully opened: " & doc.Name
Exit Sub
ErrorHandler:
' ここでは API レベルでは検知できなかった、
' AutoCAD 固有の致命的なエラー(破損ファイル等)を捕捉する
MsgBox "AutoCAD 実行時エラー: " & Err.Description, vbExclamation
End Sub
4. 結論:堅牢な自動化への道
プロフェッショナルな自動化エンジニアにとって、エラーハンドリングとは「エラーが起きた後の後始末」ではない。「エラーが起きる条件を事前に排除する設計」のことである。
Windows APIを直接叩くという手法は、一見するとオーバーエンジニアリングに見えるかもしれない。しかし、大量のデータを扱うエンタープライズ環境において、この「決定論的な判定」がもたらすシステムの信頼性は、何物にも代えがたい。
`CreateFile` による低レイヤーの制御をマスターし、オブジェクトのライフサイクルを完全に掌握すること。それが、レガシーなVBA環境から次世代の自動化アーキテクチャへと昇華させるための、唯一の道である。
