【実務・中級編】【上級者向け】Visio図面をPDF出力する際、出力先のPDFが他プロセスで使用中の場合に待機・リトライする排他制御 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:図面PDF出力における「ファイルロック地獄」を完全制圧する排他制御アーキテクチャ

開発プロジェクトのリーダーである私たちが、現場の自動化ツール構築において最も直面したくない悪夢の一つ。それが、「他のプロセスによるファイルロック(Permission Denied / 共有違反)」だ。

特にVisio図面をPDFとして出力する際、出力先のPDFが別プロセス(Adobe Acrobat、プレビューアー、クラウドストレージの同期クライアント、あるいは前回の非同期処理の残骸)によって掴まれていると、容容赦なくVBAはRuntimeエラー(`Err.Number = 70` など)を吐いてクラッシュする。

「エラーが出たらVBScriptやタスクスケジューラで再実行すればいい」?
プロのエンジニアがその設計を口にした瞬間、プロジェクトの信頼性は地に落ちる。

今回は、Visio VBAの限界領域を知り尽くしたチーフアーキテクトの視点から、「ファイルロックエラーをミリ秒単位で検知し、安全に待機・リトライを行う極限の排他制御パターン」を伝授する。

—

なぜ素朴なエラーハンドラーでは実用に耐えないのか?

多くのプログラマが書く、最もありがちなアンチパターンを見てみよう。

‘ 【アンチパターン】これでは実務の現場で必ず破綻する
Sub BadExportPDF(vsoPage As Visio.Page, targetPath As String)
On Error GoTo ErrorHandler

‘ PDFのエクスポート
vsoPage.ExportAsFixedFormat visFixedFormatPDF, targetPath
Exit Sub

ErrorHandler:
‘ 単にメッセージを出して逃げるだけの無責任な設計
MsgBox “PDFの出力に失敗しました。”
Resume Next
End Sub

このコードが現場でゴミカス扱いされる理由は明確だ。
1. 一時的な競合(ファイル同期中など)を考慮していない:運良く1秒後にロックが解除される状況であっても、即座に処理が中断される。
2. エラーの特定をしていない:ファイルロック以外の致命的なエラー(パス不正、ディスク容量不足)までリトライの対象になり、無限ループや予期せぬ挙動を引き起こす。
3. OSやI/Oの負荷を無視している:CPUを空転させながら高速でリトライを繰り返すと、ファイルシステムへ無駄な負荷をかける。

真にロバスト(堅牢)なシステムとは、「外部環境の気まぐれに屈しない、粘り強い自己回復能力」を持つものである。

—

プロダクションコード:極限の排他制御・PDFエクスポートエンジン

それでは、実務の現場でそのまま稼働できる、極限まで洗練されたVBAコードを公開する。
このコードには、ファイルシステムの特性、Win32APIの活用、そして確実にリソースを解放する防御的プログラミングの知見が凝縮されている。

Option Explicit

‘ —————————————————————————
‘ 業務自動化アーキテクチャ水準 PDFエクスポートモジュール
‘ —————————————————————————

‘ Windows API: 処理を指定ミリ秒数だけ完全停止(CPUを消費しないスリープ)
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

‘ 定数定義
Private Const MAX_RETRY_COUNT As Long = 5 ‘ 最大リトライ回数
Private Const RETRY_INTERVAL_MS As Long = 2000 ‘ リトライ間隔(2000ms = 2秒)
Private Const ERR_PERMISSION_DENIED As Long = 70 ‘ 実行時エラー: 書き込み許可拒否(環境により変動するため文字列判定も併用)

/

  • 指定されたVisioページを堅牢な排他制御付きでPDFエクスポートする
  • @param vsoPage 対象のVisio.Pageオブジェクト
  • @param targetPath 出力先のフルパス(例: “C:\Data\Output.pdf”)
  • @return Boolean (True: 成功, False: 失敗)

/
Public Function ExportPDFWithRetry(ByVal vsoPage As Visio.Page, ByVal targetPath As String) As Boolean
Dim retryCount As Long
Dim isSuccess As Boolean
Dim fso As Object

Set fso = CreateObject(“Scripting.FileSystemObject”)
retryCount = 0
isSuccess = False

‘ 出力先ディレクトリの存在確認・自動生成(防御的設計)
Dim parentDir As String
parentDir = fso.GetParentFolderName(targetPath)
If Not fso.FolderExists(parentDir) Then
Err.Raise 9999, “ExportPDFWithRetry”, “出力先のディレクトリが存在しません: ” & parentDir
End If

Do While retryCount <= MAX_RETRY_COUNT On Error GoTo CatchError ' ------------------------------------------------------------------- ' Core Process: PDF出力実行 ' ------------------------------------------------------------------- ' 注意: VisioのExportAsFixedFormatはファイルがロックされている場合、 ' 実行時エラーをスローします。 vsoPage.ExportAsFixedFormat visFixedFormatPDF, targetPath, _ visIntentPrint, visPrintAll ' ここまで到達できればエクスポート成功 isSuccess = True Exit Do CatchError: ' エラー番号およびエラーメッセージからファイルロック起因かを判定 ' 70: 権限エラー, 53/75等も環境やOSの挙動により発生しうるため、汎用的な文字列トラップも有効 If IsFileLockedError(Err.Number, Err.Description) Then retryCount = retryCount + 1 If retryCount > MAX_RETRY_COUNT Then
‘ 最大リトライ回数オーバー:致命的エラーとしてログを刻みループを抜ける
Debug.Print “[FATAL] PDFエクスポートの排他制御が限界に達しました: ” & targetPath
Exit Do
End If
番兵ログ出力 retryCount, targetPath

‘ CPUを枯渇させないスマートな待機
Sleep RETRY_INTERVAL_MS

‘ エラーをクリアして次のループへ
Resume Next
Else
‘ ファイルロック以外の予期せぬエラー(パス不正、メモリ不足など)は即座に上位へスロー
Dim errDesc As String
errDesc = Err.Description
On Error GoTo 0
Err.Raise 9999, “ExportPDFWithRetry”, “予期せぬエラーが発生しました: ” & errDesc
End If
Loop

‘ クリーンアップ
Set fso = Nothing
ExportPDFWithRetry = isSuccess
End Function

/

  • エラーがファイルロックに起因するものか判定するヘルパー関数

/
Private Function IsFileLockedError(ByVal errNum As Long, ByVal errDesc As String) As Boolean
‘ 70: 権限なし/アクセス拒否
If errNum = ERR_PERMISSION_DENIED Then
IsFileLockedError = True
Exit Function
End If

‘ 日本語環境および英語環境におけるファイル競合エラーのキーワードを網羅
If InStr(1, errDesc, “permission”, vbTextCompare) > 0 _
Or InStr(1, errDesc, “access”, vbTextCompare) > 0 _
Or InStr(1, errDesc, “書き込み”, vbTextCompare) > 0 _
Or InStr(1, errDesc, “使用中”, vbTextCompare) > 0 Then
IsFileLockedError = True
Exit Function
End If

IsFileLockedError = False
End Function

/

  • 冗長にならない程度のデバッグ・監査ログ出力

/
Private Sub 番兵ログ出力(ByVal currentRetry As Long, ByVal path As String)
Debug.Print “[WARN] ” & Now & ” – PDFファイルが別プロセスに占有されています。” & _
“[” & currentRetry & “/” & MAX_RETRY_COUNT & “回目リトライ待機中] Path: ” & path
End Sub

—

アーキテクチャの解説:なぜこの設計が「最強」なのか

1. `Sleep` API によるスマートなスリープ

VBA標準の `DoEvents` やループによる時間稼ぎは、1コアのCPU使用率を100%に張り付かせ、PC全体のファンを唸らせる最悪の手法だ。
ここでは `kernel32` の `Sleep` 関数を呼ぶことで、OSレベルでスレッドを休止させ、CPU負荷を完全にゼロにした状態でのリトライ待機を実現している。

2. 二重の安全網(Err.Number + メッセージ文字列判定)

Windowsのファイルロックエラーは、ネットワークドライブ上(SMB接続)やクラウドストレージ(OneDrive / SharePoint / Box等)を介している場合、OfficeのバージョンやOSの言語設定によって `Err.Number` が一意に定まらないことがある。
`IsFileLockedError` 関数では、数値だけでなく「使用中」「アクセス」「permission」といったキーワードをマルチリンガルに捕捉し、環境依存のバグを完全に封じ込めている。

3. フェイルファスト(Fail-Fast)の原則遵守

ファイルロック以外のエラー(例:存在しないドライブを指定した、権限そのものが剥奪されている等)に対してまでリトライを繰り返すのは愚行である。
コード内のエラー分岐では、ロック起因以外の例外を即座にキャッチして上位にバブルアップ(再スロー)させているため、開発時のデバッグ効率が損なわれることもない。

—

実務導入へのアドバイス

このモジュールをあなたの業務自動化システム(図面の一括変換ツールなど)に組み込むだけで、朝に出社して「エラーで途中で止まっていました」という絶望的な報告を受ける確率は限りなくゼロに収束する。

コードはそのままコピペしてプロダクション環境に投入して構わない。
プロのエンジニアとしての誇りを持って、洗練された堅牢な自動化基盤を構築してほしい。

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