【上級プロフェッショナル向け】Windows API「CreateFile」による「他プロセスでのファイル使用状況」の完全検知
AutoCADの自動化ツールを開発する際、多くのエンジニアが「図面のオープン」という単純なタスクに甘い設計を持ち込む。
「`Documents.Open` を実行し、エラーが出たらキャッチする」
もし君がそう考えているなら、今すぐその設計を捨ててほしい。それはプロの仕事ではない。なぜなら、AutoCADが図面を開こうとした瞬間に、他プロセス(Excel、テキストエディタ、あるいは別のCADプロセス)がそのファイルに対して排他的なロックをかけていた場合、AutoCAD自体が不安定になったり、不適切なエラーダイアログがユーザーの前に突きつけられたり、最悪の場合はファイルハンドルが中途半端に残るリスクがあるからだ。
本稿では、AutoCAD VBAの枠を超え、Windows Kernel層の挙動を直接制御する Windows API `CreateFile` を用いて、ファイルが「本当に、安全に、独占的に」扱える状態かを判定する、極限の堅牢性を備えた実装手法を伝授する。
1. なぜ標準のVBA関数では不十分なのか
VBAには `Dir()` 関数や `FileLen()` 関数がある。これらは「ファイルが存在するか」「サイズはいくらか」を知るには十分だ。しかし、「そのファイルが現在、他のプロセスによってロックされているか」を判定する能力は皆無に等しい。
よくある誤った手法として、`Open filePath For Append As #1` のように、書き込みモードでファイルを開こうとしてエラーを判定する手法がある。しかし、これは極めて低レベルで、副作用が大きすぎる。
- 意図せずファイルにメタデータを書き込んでしまうリスク。
- ファイルシステムへの不必要なI/O負荷。
- 共有モード(Share Mode)の制御が不完全。
プロフェッショナルが選ぶべき道は、「OSに対して、そのファイルを独占的に開ける権利があるか、事前に問い合わせる」 ことだ。これこそが Windows API `CreateFile` の真価である。
2. Windows API `CreateFile` による判定のメカニズム
`CreateFile` 関数は、Windows OSにおいてファイルやデバイスへのハンドルを作成するための最も低レベルな関数の一つだ。
我々が利用するのは、以下のロジックである。
1. `CreateFile` を呼び出し、「共有モード(dwShareMode)を 0(なし)」 に設定してアクセスを試みる。
2. もし他のプロセスがファイルを開いていれば、OSは `ERROR_SHARING_VIOLATION`(共有違反)を返す。
3. このエラーを検知することで、ファイルがロックされていることを確信できる。
この手法は、ファイルの中身を一切読み書きせず、OSのファイルシステムマネージャーの判定結果を直接取得するため、極めて高速かつ安全である。
3. プロダクション・レディな実装例
以下に、64bit/32bit両対応(PtrSafe)であり、かつメモリリークを防ぐためにハンドル管理を徹底した、そのまま実務に投入可能なコードを示す。
Option Explicit
' --- Windows API Declarations ---
' 64bit/32bit両対応の宣言
If VBA7 Then
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
Else
Private Declare 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 Long
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
End If
' --- 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
''' <summary>
''' 指定されたファイルが他プロセスによってロックされているかを判定する。
''' </summary>
''' <param name="filePath">フルパス</param>
''' <returns>True: ロックされている(開けない), False: 安全(開ける)</returns>
Public Function IsFileLocked(ByVal filePath As String) As Boolean
#If VBA7 Then
Dim hFile As LongPtr
#Else
Dim hFile As Long
#End If
' ファイルの存在チェック(APIを呼ぶ前の基本)
If Dir(filePath) = "" Then
' ファイルが存在しない場合は「ロックされていない」とみなすが、
' 実務上は呼び出し元で別途エラーハンドリングすべき。
IsFileLocked = False
Exit Function
End If
' CreateFileを「独占モード(FILE_SHARE_NONE)」で試行
' これにより、他者が少しでもアクセスしていれば失敗する。
hFile = CreateFile( _
filePath, _
GENERIC_READ, _
FILE_SHARE_NONE, _
0, _
OPEN_EXISTING, _
0, _
0)
' ハンドルが不正な値(INVALID_HANDLE_VALUE)を返した場合、
' 何らかの理由でアクセスを拒否された(=ロックされている)と判断
If hFile = INVALID_HANDLE_VALUE Then
IsFileLocked = True
Else
' 成功した場合は、即座にハンドルを閉じてリソースを解放する
' これを忘れると、自プロセスがファイルロックの原因になる
CloseHandle hFile
IsFileLocked = False
End If
End Function
4. 設計における「極限の注意点」
このコードを組み込むにあたり、設計リーダーとして君に伝えておかなければならない「落とし穴」が3つある。
① レースコンディション(競合状態)の理解
`IsFileLocked` が `False` を返したとしても、その直後のマイクロ秒後に別のプロセスがファイルを開く可能性がある。これを「Time-of-check to time-of-use (TOCTOU)」問題と呼ぶ。
したがって、「判定が通ったからといって、絶対に開けるという保証はない」 という前提で、`Documents.Open` の実行時にも必ずエラーハンドリングを組み込むこと。
② データベース・ログとの連携
業務効率化ツールにおいて、ロックによってスキップされたファイルは「失敗」ではなく「保留」として扱うべきだ。
大量の図面をバッチ処理する場合、ロックされていたファイル名をDB(SQL ServerやAccess)に記録し、「リトライ待ちステータス」 を付与する設計にせよ。ユーザーに「エラーで止まりました」と伝えるのは素人の仕事だ。「一部のファイルは他者が使用中のため、後ほど再試行します」と伝えるのがプロの設計である。
③ リソースリークの徹底排除
上記のコードで `CloseHandle` を呼び出しているのは、単なる作法ではない。もし `IsFileLocked` が `False` を返した際にハンドルを閉じ忘れたら、君の作ったツール自体が「ファイルをロックしている犯人」になり、次にそのファイルを開こうとするAutoCADユーザーを混乱に陥れることになる。
5. まとめ:堅牢な自動化への道
AutoCAD VBAによる自動化は、単なる「操作の記録」ではない。それは、Windowsという巨大なエコシステムの上で、ファイルシステムとプロセス管理をいかにエレガントに制御するかという、高度なシステムプログラミングである。
`CreateFile` を使いこなすことは、単なるテクニックではない。ユーザーの作業環境を尊重し、予期せぬエラーからシステムを守るという、エンジニアとしての「責任」の現れなのだ。
この知見を、君の作るプロダクトに刻み込んでほしい。
