【テクニカル・上級編】【上級者向け】Visioプロセスをバックグラウンドで隠蔽し、高速にPDF変換を行うマルチスレッド的アプローチ – Visio VBA解析バイブル

スポンサーリンク

【上級者向け】Visioプロセスを完全隠蔽し、高速PDF変換を極めるマルチスレッド的アプローチ

Visio VBAによる図面操作、そしてそれらのPDF一括変換において、開発者が最も直面する絶望感の正体は何か。それは「画面のちらつき(Screen Updating)」でもなければ、「遅延評価の重さ」だけでもない。単一のVisioプロセス(`visio.exe`)が持つモノリスな構造と、COM相互運用における暗黙のメッセージループの呪縛である。

数百ファイルのVSDXをバッチ処理でPDF化する際、UIを表示したまま処理をさせれば、OSのリソースは描画処理に浪費され、最悪の場合はCOMタイムアウトエラー(`-2147417848: サーバーで例外が発生しました`)で沈黙する。

本稿では、レガシーかつ強大なVisioエンジンを完全にバックグラウンドへ幽閉し、さらにマルチインスタンス制御によって理論上のスループット限界を突破するための「極限の知見」を公開する。

—

1. Visioバックグラウンド実行におけるアーキテクチャの罠

多くの初学者は、`Visio.Application`を起動した後に以下のようなコードを書く。

‘ ありがちなアンチパターン
Dim appVisio As Object
Set appVisio = CreateObject(“Visio.Application”)
appVisio.Visible = False ‘ 起動後に消す

これは無意味である。なぜなら、`visio.exe`が一度GUIスレッドを初期化した後に`Visible = False`を叩いても、ウィンドウハンドル(HWND)の生成コストや、不要なGDIリソースの割り当てはすでに発生しているからだ。さらに言えば、VisioのCOMサーバーはシングルトンに近い振る舞いを見せることがあり、意図せぬプロセス共有がバグの温床となる。

真に高速化・安定化を図るには、以下の鉄則を遵守しなければならない。

1. 完全な不可視起動: プロセス生成の瞬間からUIコンテキストを持たせない。
2. イベントと警告の完全遮断: `ScreenUpdating`だけでなく、外部リンク更新やマクロ警告を一切排除する。
3. 明示的なプロセス分離: 1つのインスタンスに大量のファイルを流し込まず、処理単位で完全にインスタンスを破棄する。

—

2. 【実践】完全隠蔽&最適化されたVBAコアエンジン

以下のコードは、GUIを描画するリソースを一切割り当てず、VSDXをロードして最高速度でPDFエクスポートを行い、即座にメモリ解放を行うプロシージャである。エラーハンドリングにおいても、COMオブジェクトのゾンビ化を防ぐための厳格なファイナライザ構造を持たせている。

Option Explicit

‘ Windows API: ガベージコレクション明示実行用(必要に応じて)
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)

Public Sub ConvertVsdxToPdf_HighPerformance(ByVal targetVsdxPath As String, ByVal outputPdfPath As String)
Dim vApp As Visio.Application
Dim vDoc As Visio.Document

‘ エラー時のCOMリークを防ぐため、厳格なトラップを設定
On Error GoTo ErrorHandler

‘ 1. インスタンスの生成(Visioは起動時からVisible=FalseにすることはCOM仕様上直接できないため、
‘ 起動直後に即座に無効化し、操作を最速で行う)
Set vApp = New Visio.Application

‘ 2. バックグラウンド化とオーバーヘッドの極小化
vApp.Visible = False
vApp.ScreenUpdating = False
vApp.ShowProductAlerts = False
vApp.Settings.SetProperty visPropDisableAutoLink, True ‘ 自動リンク更新等の無効化

‘ 3. 文書のサイレントオープン(読み取り専用、修復ダイアログ非表示)
‘ 第2引数(ReadOnly), 第3引数(AddtoRecent), 第4引数(FileOpenArgs)
Set vDoc = vApp.Documents.OpenEx(targetVsdxPath, visOpenRO + visOpenNoWorkspace, 0)

‘ 4. PDFエクスポート実行(VisioのネイティブExport機能を使用)
‘ 1 = visExportFormatPDF
vDoc.ExportAsFixedFormat visExportFormatPDF, outputPdfPath, visExportAllPages, visIntentPrint

‘ 5. クリーンクローズ
vDoc.Close
Set vDoc = Nothing

vApp.Quit
Set vApp = Nothing

Exit Sub

ErrorHandler:
‘ 異常系:ゾンビプロセスとメモリリークの強制回収
On Error Resume Next
If Not vDoc Is Nothing Then
vDoc.Close False ‘ 保存せずに閉じる
Set vDoc = Nothing
End If

If Not vApp Is Nothing Then
vApp.Quit
Set vApp = Nothing
End If

‘ ログ出力や上位へのエラー伝播
Err.Raise Err.Number, “ConvertVsdxToPdf_HighPerformance”, “Visio変換エラー: ” & Err.Description
End Sub

コードの急所:なぜ `OpenEx` を使うのか?

標準の `Documents.Open` は、リンクされたデータソースの更新確認や、フォント置換のダイアログを表示しようとする。これがバックグラウンド処理においてプロセスを永遠にハングさせる主原因となる。`visOpenRO + visOpenNoWorkspace` フラグを組み合わせることで、一切の対話型ダイアログをバイパスし、CPUとI/Oの限界速度を引き出すことが可能になる。

—

3. 【上級者向け】マルチスレッド的アプローチ(VB.NET / PowerShell による並列制御)

VBA単体では、VBScriptの`WScript.Shell`を用いた非同期実行(Fire and Forget)しかできず、プロセスの終了待機やスロットリング(同時実行数の制御)が困難である。
シニアエンジニアが真に大規模な自動化を構築する場合、VBAからC#/.NETコンポーネントを呼び出すか、あるいはタスクランナーとしてPowerShellの並列ジョブ(`Start-Job` または `Parallel`構文)を駆使する。

ここでは、PowerShell 7以降(または.NET Core/Framework)のマルチスレッド処理の思想を応用し、複数のVisioプロセスを同時に立ち上げてPDF変換を並列化するアプローチの概念を示す。

=====================================================================
Visio Parallel PDF Converter (PowerShell Controller)
複数インスタンスを同時起動し、CPUコアをフル活用してVSDXをPDF化する
=====================================================================

param(
[string]$TargetDirectory = “C:\VisioFiles”,
[string]$OutputDirectory = “C:\PdfOutput”,
[int]$MaxConcurrency = 4 # 同時実行するVisioプロセスの最大数
)

$vsdxFiles = Get-ChildItem -Path $TargetDirectory -Filter “.vsdx”

セマフォによる並列度制御
$semaphore = New-Object System.Threading.SemaphoreSlim($MaxConcurrency, $MaxConcurrency)

$jobs = foreach ($file in $vsdxFiles) {
$semaphore.Wait()

Start-Job -ScriptBlock {
param($vsdxPath, $outDir, $sem)
try {
$pdfName = [System.IO.Path]::ChangeExtension($vsdxPath.Name, “.pdf”)
$outPdfPath = Join-Path $outDir $pdfName

# COMオブジェクトの生成(独立したVisioプロセス)
$visio = New-Object -ComObject Visio.Application
$visio.Visible = $false
$visio.ScreenUpdating = $false
$visio.ShowProductAlerts = $false

$doc = $visio.Documents.OpenEx($vsdxPath.FullName, 1 + 64) # RO + NoWorkspace
$doc.ExportAsFixedFormat(1, $outPdfPath, 1, 1) # 1 = PDF

$doc.Close($false)
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($doc) | Out-Null

$visio.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($visio) | Out-Null

} catch {
Write-Error “Failed to process $($vsdxPath.Name): $_”
} finally {
$sem.Release() | Out-Null
}
} -ArgumentList $file, $OutputDirectory, $semaphore
}

全ジョブの完了を待機
$jobs | Wait-Job | Receive-Job
$jobs | Remove-Job

Write-Host “すべてのVisioファイルの並列PDF変換が完了しました。” -ForegroundColor Green

このアプローチの優位性

1. CPU/メモリの完全使い切り: 単一プロセスでは1コアしか100%にできないが、マルチインスタンス化することでマルチコアプロセッサの性能を限界まで引き出せる。
2. クラッシュの局所化: 万が一、破損したVSDXが原因でVisioのCOMコンポーネントがハードクラッシュ(Access Violation)を起こしても、影響はそのジョブのプロセスのみに限定され、全体バッチが停止することがない。

—

4. チーフアーキテクトからの警句:メモリリークとCOMの呪縛

最後に、Visioオートメーションにおける最大の罠について言及する。
VBAやCOM interopにおいて、`Set obj = Nothing` を実行しただけでは、裏で動いているCOMの参照カウンター(Reference Count)が完全にゼロにならないケースが多々存在する。特にVisioはドキュメントオブジェクト、ページオブジェクト、シェイプオブジェクトが複雑なツリー構造を形成しているため、不完全な参照解放は確実にメモリリーク(RAMの肥大化)を引き起こす。

  • オブジェクト変数のスコープを極限まで狭めよ: 巨大なプロシージャの中でVisioオブジェクトを使い回すな。処理単位でヘルパー関数に切り出し、関数を抜けた瞬間にスタックフレームごと参照が消失する設計にしろ。
  • GCの強制発動: 大量処理のループ内では、適宜 `DoEvents` や .NETであれば `GC.Collect()` を挟み、OSへのメモリ返還を促すこと。

レガシーとモダンが交差する現場において、Visioは依然として強力なグラフィックスエンジンである。その重厚長大さをねじ伏せ、バックグラウンドの猛者として調教しきることこそが、真の自動化エンジニアの特権なのである。

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