Visio VBAのPDF出力における「排他制御の極致」― 泥臭い現場を生き抜くためのリトライ・アーキテクチャ
Visioでの自動化において、PDF出力は単なる「Exportメソッドの実行」ではない。特に、共有フォルダやドキュメント管理システムとの連携環境下では、PDF出力は「他プロセスによるファイルロック」という不可避な壁に直面する。
多くの開発者が、ループで回すだけの単純なエラーハンドリングで満足している。だが、それはシステム運用における「時限爆弾」を放置しているに等しい。本稿では、レガシーなVBA環境において、いかにして堅牢な排他制御とリトライ戦略を実装すべきか、その極致を解説する。
—
1. なぜ「単純なエラー無視」が崩壊を招くのか
Visioで`ExportAsFixedFormat`を実行する際、出力先ファイルが他プロセス(Adobe Acrobatやプレビューウィンドウ等)で開かれていると、VBAは無慈悲にエラーを吐く。
多くの初学者はここで`On Error Resume Next`を多用するが、これはメモリリークやVisioアプリケーションの不安定化の温床となる。我々アーキテクトが目指すべきは、「ファイルが解放されるまで、システム負荷を最小限に抑えて待機し、確実に閉じる」という受動的かつ能動的な戦略だ。
2. 実装の核心:排他制御ロジック
以下に、Windows APIを用いたファイルアクセス確認と、指数関数的バックオフに近い待機ロジックを実装した関数を示す。
Option Explicit
‘ ファイルロック確認のための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” 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
Private Const GENERIC_WRITE = &H40000000
Private Const OPEN_EXISTING = 3
Private Const INVALID_HANDLE_VALUE = -1
”’
”’
Private Function IsFileLocked(ByVal filePath As String) As Boolean
Dim hFile As LongPtr
‘ 排他書き込みモードで開けるかを試行
hFile = CreateFile(filePath, GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
IsFileLocked = True
Else
CloseHandle hFile
IsFileLocked = False
End If
End Function
”’
”’
Public Sub ExportPdfWithRetry(doc As Visio.Document, outputPath As String)
Dim retryCount As Integer
Dim maxRetries As Integer: maxRetries = 5
Dim waitSeconds As Integer: waitSeconds = 2
Do
If Not IsFileLocked(outputPath) Then
On Error Resume Next
doc.ExportAsFixedFormat visFixedFormatPDF, outputPath
If Err.Number = 0 Then Exit Sub
On Error GoTo 0
End If
‘ 待機時間が長くなるにつれインターバルを伸ばす(指数バックオフ)
Application.Wait (Now + TimeSerial(0, 0, waitSeconds))
waitSeconds = waitSeconds + 2
retryCount = retryCount + 1
Loop While retryCount < maxRetries
Err.Raise 9999, "ExportPdf", "PDF出力に失敗しました。ファイルが占有されています。"
End Sub
---
3. アーキテクトの視点:なぜこの実装なのか
① メモリ管理と「死」の回避
`CreateFile` APIを用いる理由は、ファイルシステムのメタデータレベルでロック状態を即座に判定するためだ。VBAの`Open For Append Lock Write`などは動作が不安定であり、環境依存性が高い。API経由の判定は、プロセス間通信のオーバーヘッドを最小化する。
② 指数バックオフの採用
単に1秒待機を繰り返すのは、CPUのリソースを無駄に浪費するだけだ。`waitSeconds`をループごとに加算することで、後続のプロセスが終了するのを待つ間、Visioの描画スレッドに呼吸の余地を与える。これが大規模ドキュメント出力時の安定性を左右する。
③ オブジェクトの明示的解放
VBAにおいて`Nothing`への代入を忘れることは、大規模システムにおける罪である。もしこのPDF出力関数をループ内で多用するなら、必ず`Set doc = Nothing`を徹底し、Visioのガベージコレクションを促すこと。さもなくば、メモリ断片化(Fragmenting)により、数千枚の図面処理の途中でVBAがクラッシュする未来が待っている。
4. 結びに:自動化の真髄
「動けば良い」というコードは、PCの電源を切るまでしか通用しない。我々エンジニアに求められているのは、「例外が発生したとき、システムが自律的に回復できるか」という設計思想だ。
PDF出力という単純な操作一つとっても、そこにはOSのファイルシステム、アプリケーションのメモリモデル、そしてネットワークの遅延が複雑に絡み合っている。それらすべてを制御下に置くこと。それこそが、伝説と呼ばれるための第一歩である。
君たちのコードが、誰の手も借りずに静かに、しかし確実に仕事を遂行することを願っている。
