共有リソースプールの「データ不整合」を断つ:VBAにおける極限の排他制御術
VBAによる共有リソース管理において、最も忌むべきは「更新の衝突」によるデータ破壊だ。共有ファイルがネットワークドライブ上に置かれている場合、Excelの標準的な共有機能は脆弱極まりなく、実務レベルでは使い物にならない。
シニアエンジニアとして、私は断言する。「競合は発生するもの」と前提し、OSレベルの排他制御をVBAに強制的に実装せよ。
本稿では、Windows APIを駆使した堅牢な排他ロックの実装と、メモリを肥大化させないオブジェクト管理の真髄を解説する。
—
1. 共有リソース管理の「悪夢」を回避する戦略
一般的な `Open … For Binary Lock Read Write` は、ファイルが既に開かれている場合にランタイムエラーを吐く。だが、これだけでは不十分だ。プロセスが異常終了した場合にロックファイルが放置される「デッドロック」に対処しなければならない。
我々が取るべき戦略は以下の3点である。
1. APIによる強制排他: `CreateFile` APIで共有モードを厳密に制御する。
2. トランザクション管理: 編集前にローカルへ一時コピーを作成し、コミット時にのみ上書きする。
3. メモリ最適化: `Set Object = Nothing` だけでなく、参照カウンタを意識したスコープ設計。
—
2. Windows APIによるファイル排他制御の実装
以下のコードは、対象ファイルを「誰も開けない状態」で確保する、いわゆる「セマフォ的制御」を行うための核となるロジックだ。
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 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
Else
‘ レガシー環境対応
Private Declare Function CreateFile Lib “kernel32” …
End If
‘ 定数定義
Private Const GENERIC_WRITE = &H40000000
Private Const FILE_SHARE_NONE = &H0& ‘ 他のプロセスを一切許可しない
Private Const OPEN_EXISTING = 3
Public Function AcquireFileLock(ByVal filePath As String) As LongPtr
‘ ファイルを排他モードで開く(ハンドルを維持することでロックを実現)
AcquireFileLock = CreateFile(filePath, GENERIC_WRITE, FILE_SHARE_NONE, 0, OPEN_EXISTING, 0, 0)
End Function
Public Sub ReleaseFileLock(ByVal hFile As LongPtr)
If hFile <> 0 Then CloseHandle hFile
End Sub
—
3. リソースプールの安全な更新フロー
単にロックするだけでは不完全だ。編集中のクラッシュに備え、以下のプロセスを徹底する。
更新ロジックのフロー
1. ロック取得: 上記APIでターゲットを固定。
2. バックアップ: `FileSystemObject` を使い、現在時刻のスタンプ付きでバックアップを生成。
3. 更新処理: `Workbooks.Open` で読み込み、書き込み、保存、閉じる。
4. ロック解放: ハンドルを `CloseHandle` で破棄。
Public Sub SafeUpdateResourcePool(ByVal targetPath As String)
Dim hLock As LongPtr
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 1. 排他ロック取得
hLock = AcquireFileLock(targetPath)
If hLock = -1 Then ‘ INVALID_HANDLE_VALUE
MsgBox “リソースプールは現在使用中です。後ほど再試行してください。”, vbCritical
Exit Sub
End If
On Error GoTo Cleanup
‘ 2. ログを残すバックアップ処理
fso.CopyFile targetPath, targetPath & “.bak”, True
‘ 3. 実処理 (ここを最適化する)
Dim wb As Workbook
Set wb = Workbooks.Open(targetPath)
‘ — 更新ロジック —
wb.Close SaveChanges:=True
Set wb = Nothing
Cleanup:
‘ 4. 必ずハンドルを解放する(エラー時も通るように)
ReleaseFileLock hLock
Set fso = Nothing
If Err.Number <> 0 Then MsgBox “致命的なエラー: ” & Err.Description
End Sub
—
4. シニアエンジニアが意識すべき「メモリの重み」
VBAはガベージコレクションが極めて遅い。特に大規模なリソースプールを扱う際、`Workbooks.Open` や `Set wb = Nothing` を繰り返すと、メモリリークの温床になる。
- 明示的なオブジェクト解放: `Set obj = Nothing` は必須だが、それ以上に「スコープの局所化」を徹底せよ。巨大なオブジェクトは関数内のごく短い期間のみ生成し、即座に破棄する。
- イベントの無効化: `Application.ScreenUpdating = False` や `Application.EnableEvents = False` は、パフォーマンスだけでなく、意図しないイベント発火によるロックの不整合を防ぐために不可欠だ。
—
結論
VBAは、正しく制御すればエンタープライズ環境でも十二分に機能する。しかし、それは「VBA任せ」にするのではなく、OSのAPIという「基盤の作法」を理解した者だけが到達できる領域だ。
共有リソースプールの競合は、単なる技術の問題ではなく「誰がいつアクセス権を持つか」という、システム設計の根本的な問いである。この排他制御を実装し、貴殿のシステムを「落ちないアーキテクチャ」へと昇華させてほしい。
以上。コードを書くことに魂を込めることを忘れるな。
