【テクニカル・上級編】【中級者向け】複数のVSDXファイルを順次開き、各図面のカスタムプロパティをCSVへ抽出してから閉じる – Visio VBA解析バイブル

スポンサーリンク

迷走するVBAプログラマへの警告:Visioバッチ処理における「メモリ枯渇」という死神

Visio VBAを用いたバッチ処理において、`Documents.Open`を繰り返すだけのコードを書いているなら、今すぐ手を止めてほしい。

数百ファイル規模の処理で、なぜか「メモリ不足」で落ちる、あるいは処理時間が後半になるにつれて指数関数的に増大する。これはVisioのメモリ管理モデルを理解していない者が陥る典型的な罠だ。Visioのオブジェクトモデルは非常に「粘着質」であり、一度掴んだリソースを明示的に解放しなければ、COMの参照カウンタは一生減ることはない。

今回は、数千ファイルの図面からカスタムプロパティ(シェイプデータ)を抽出するタスクを例に、システムを沈没させないための「極限のバッチ処理」を実装する。

—

1. 殺意を抱くほどの安定性を構築する:基本戦略

安定したバッチ処理の鍵は、「Visioインスタンスの汚染防止」と「オブジェクトの即時開放」に尽きる。

  • DoEventsの適切な配置: GUIをフリーズさせず、かつCPUを食い潰さないギリギリのバランスをとる。
  • Nothingの明示: VBAのガベージコレクションを信用するな。`Set obj = Nothing`は作法ではなく、生存のための儀式である。
  • エラーハンドリングの徹底: 1つの破損ファイルで全行程を停止させてはならない。

—

2. 実装:堅牢なシェイプデータ抽出エンジン

以下は、指定フォルダ内の全VSDXからカスタムプロパティを抽出し、CSVに書き出すアーキテクチャである。

Option Explicit

‘ メモリリークを防ぐためのオブジェクト解放管理
Public Sub ExtractShapeDataToCSV()
Dim fso As Object: Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim folder As Object: Set folder = fso.GetFolder(“C:\TargetFiles”)
Dim file As Object
Dim doc As Visio.Document
Dim csvFile As Object

‘ CSVファイルをストリームで開く(高速化のため)
Set csvFile = fso.CreateTextFile(“C:\Output\Report.csv”, True)
csvFile.WriteLine “FileName,ShapeName,PropLabel,PropValue”

‘ 画面更新を停止させ、COM通信のオーバーヘッドを削減
Application.ScreenUpdating = False

On Error Resume Next ‘ 破損ファイルでの停止を防ぐ
For Each file In folder.Files
If LCase(fso.GetExtensionName(file.Path)) = “vsdx” Then
Set doc = Application.Documents.Open(file.Path)

If Not doc Is Nothing Then
Call ProcessShapes(doc, csvFile, file.Name)
doc.Close ‘ 必須:閉じることでCOM参照を解放
Set doc = Nothing
End If
End If
DoEvents ‘ OSに制御を戻し、メモリ解放の隙を作る
Next
On Error GoTo 0

csvFile.Close
Application.ScreenUpdating = True
MsgBox “抽出完了”
End Sub

Private Sub ProcessShapes(doc As Visio.Document, csvFile As Object, fName As String)
Dim pag As Visio.Page
Dim shp As Visio.Shape
Dim i As Integer

For Each pag In doc.Pages
For Each shp In pag.Shapes
‘ カスタムプロパティ(Shape Data)の走査
‘ RowExistsで確認してからアクセスしないと例外のリスクがある
If shp.SectionExists(visSectionProp, False) Then
For i = 0 To shp.RowCount(visSectionProp) – 1
csvFile.WriteLine fName & “,” & shp.Name & “,” & _
shp.CellsSRC(visSectionProp, i, visCustPropsLabel).ResultStr(“”) & “,” & _
shp.CellsSRC(visSectionProp, i, visCustPropsValue).ResultStr(“”)
Next i
End If
Next shp
Next pag
End Sub

—

3. チーフアーキテクトの知見:なぜこのコードでなければならないのか

A. `CellsSRC` の優位性

`shp.Cells(“Prop.Label”)` のような名前参照は、実は内部で文字列検索を行っており遅い。`CellsSRC`(Section, Row, Column)を使うことで、メモリ上のインデックスに直接アクセスできる。数万シェイプを処理する場合、この差は数分から数十分の短縮に繋がる。

B. `Document.Close` の真実

VBAで `Set doc = Nothing` としても、Visioの`Documents`コレクションから除去されない限り、COMオブジェクトはメモリ上に幽霊として残り続ける。ループ内では必ず `doc.Close` を呼び出し、明示的にハンドルを破棄せよ。

C. 破損ファイルへの耐性

`On Error Resume Next` をループ全体に適用するのは危険だが、ファイルオープンプロセスに限定することで、特定の図面が破損していても「死なない」システムになる。ログ出力機能を追加し、失敗したファイル名を別ファイルに書き出す設計にするのがプロの流儀だ。

—

4. さらなる高みへ:Windows APIの活用

もし処理中にVisioが「応答なし」になり、OSから強制終了を食らう場合は、`Win32 API`の `SendMessage` を使い、Visioのメッセージキューを強制的にフラッシュさせる手法がある。しかし、まずは上記の実装を完璧に行うことだ。

リソース管理は、エンジニアの「誠実さ」そのものである。
メモリの境界を意識し、オブジェクトの死に際を見届けるコードを書け。それが、真に自動化を掌握するということだ。

次回の講義では、VSDXの内部構造であるOpen XML形式を直接叩き、Visioを立ち上げずにデータを抽出する「異次元の高速化」について語ろう。

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