Visio VBAを掌握する極限の知見:Documentのソリューションセクション(SolutionXRef)による不可視メタデータ管理
Visio VBAを用いた大規模な業務自動化ソリューションにおいて、最も頭を悩ませる問題の一つが「実行履歴、バージョン管理メタデータ、あるいはライセンス情報の永続化」だ。
一般的な開発者は、カスタムプロパティ(Shape Data)やドキュメントのカスタムプロパティ(Document.DocumentProperties)にこれらを書き込もうとする。しかし、それはアマチュアのやり方だ。ユーザーが「図形データ」ウィンドウを開けば容易に改ざん・削除され、不用意な手動編集によってシステムがクラッシュするリスクを常に抱えることになる。
真のシニアアーキテクトが知るべきは、Visioのネイティブ・エンジンが持つ隠された機構、`SolutionXRef` セクション(ソリューションセクション)の活用だ。
本稿では、Documentオブジェクトの奥底に潜むこの領域を完全掌握し、外部から不可視な状態でシステムの内部状態やライセンス情報を安全に永続化する極限のテクニックを解説する。
—
1. SolutionXRefセクションとは何か?
VisioのShapeSheetアーキテクチャには、通常のユーザーインターフェースからは一切見えない、アドオンやソリューション専用のデータ格納領域が存在する。それが `visSectionSolution`(インデックス: `visSectionScratch` のような一般的なセクションとは異なる特殊領域)である。
この領域の最大の特長は以下の通りだ:
- GUIからの完全な不可視性: Visioの画面上、あるいは通常のShape Dataウィンドウからこのデータを見ることはできない。ユーザーによる誤削除や改ざんを完全に防ぐ。
- Documentオブジェクトへの直結: シェイプ単位だけでなく、`Document.DocumentSheet` にソリューションセクションを持たせることで、図面ファイル全体に紐づくメタデータを安全に保持できる。
- ファイルフォーマットへのネイティブな統合: `.vsdx` の内部XML(`document.xml` やページ・シェイプのXMLパーツ)の `Solution` 要素としてシームレスに保存されるため、外部DBを伴わないスタンドアロンな配布が可能。
—
2. 実装:不可視メタデータの書き込みと読み込み
それでは、実際に `DocumentSheet` をターゲットにして、メタデータを不可視領域に書き込み、それを安全に回収する堅牢なVBAコードを実装する。
ここでは、オブジェクトのライフサイクル管理とエラーハンドリングを徹底した、プロダクション品質のコードを提示する。
Option Explicit
‘ ==============================================================================
‘ módulo: ModSolutionManager
‘ 概要: DocumentのSolutionセクションを使用した不可視メタデータ管理
‘ ==============================================================================
Private Const SOL_SECTION As Integer = VisSectionIndices.visSectionSolution
Private Const SOL_ROW As Integer = 0
Private Const SOL_CELL_NAME As Integer = VisCellIndices.visCustPropsLabel ‘ 代替としてセルを使用、またはカスタムセル定義
Private Const SOL_CELL_VAL As Integer = VisCellIndices.visCustPropsValue
”’
”’
”’ 対象のVisio Documentオブジェクト
”’ メタデータのキー名
”’ メタデータの値
Public Sub WriteSolutionMetadata(ByVal targetDoc As Visio.Document, ByVal keyName As String, ByVal keyValue As String)
Dim vsoDocSheet As Visio.Shape
Dim secExists As Boolean
‘ 引数のバリデーション
If targetDoc Is Nothing Then Exit Sub
On Error GoTo ErrorHandler
‘ DocumentSheet(ドキュメント全体のShapeSheet)を取得
Set vsoDocSheet = targetDoc.DocumentSheet
‘ ソリューションセクションの存在確認と追加
If (vsoDocSheet.Stat And visodsLocked) <> 0 Then
Err.Raise 9999, “SolutionManager”, “ドキュメントシートがロックされています。”
End If
‘ セクションが存在しない場合は追加
If Not vsoDocSheet.SectionExists(SOL_SECTION, visExists) Then
vsoDocSheet.AddSection SOL_SECTION
End If
‘ 行の存在確認と追加 (Solutionセクションは特殊なインデックスを持つため動的制御)
If Not vsoDocSheet.RowExists(SOL_SECTION, SOL_ROW, visExists) Then
vsoDocSheet.AddRow SOL_SECTION, SOL_ROW, visRowSkip
End If
‘ セルへの値の書き込み(FormulaUを使用すること。文字列はダブルクォーテーションで囲む)
‘ ※極限のパフォーマンスのため、画面描画とイベントを一時停止
Visio.Application.ScreenUpdating = False
Visio.Application.EventsEnabled = False
‘ セルにキーと値を格納(例としてCell[0]にキー、Cell[1]に値を格納するカスタムロジック)
‘ 注: 実際にはCellsSRCを使って自由度の高いセルインデックスを指定可能
vsoDocSheet.CellsSRC(SOL_SECTION, SOL_ROW, 0).FormulaU = Chr(34) & EscapeString(keyName) & Chr(34)
vsoDocSheet.CellsSRC(SOL_SECTION, SOL_ROW, 1).FormulaU = Chr(34) & EscapeString(keyValue) & Chr(34)
CleanUp:
Visio.Application.ScreenUpdating = True
Visio.Application.EventsEnabled = True
Set vsoDocSheet = Nothing
Exit Sub
ErrorHandler:
MsgBox “メタデータの書き込みに失敗しました: ” & Err.Description, vbCritical, “Architecture Error”
Resume CleanUp
End Sub
”’
”’
Public Function ReadSolutionMetadata(ByVal targetDoc As Visio.Document, ByVal keyName As String) As String
Dim vsoDocSheet As Visio.Shape
Dim retrievedKey As String
On Error GoTo ErrorHandler
If targetDoc Is Nothing Then Exit Function
Set vsoDocSheet = targetDoc.DocumentSheet
‘ セクションおよび行が存在するかチェック
If vsoDocSheet.SectionExists(SOL_SECTION, False) Then
If vsoDocSheet.RowExists(SOL_SECTION, SOL_ROW, False) Then
‘ キーの突合
retrievedKey = vsoDocSheet.CellsSRC(SOL_SECTION, SOL_ROW, 0).ResultStr(VisCustomPropertyFormats.visNoObjQuery)
If retrievedKey = keyName Then
ReadSolutionMetadata = vsoDocSheet.CellsSRC(SOL_SECTION, SOL_ROW, 1).ResultStr(VisCustomPropertyFormats.visNoObjQuery)
GoTo CleanUp
End If
End If
End If
ReadSolutionMetadata = “”
CleanUp:
Set vsoDocSheet = Nothing
Exit Function
ErrorHandler:
ReadSolutionMetadata = “”
Resume CleanUp
End Function
”’
”’
Private Function EscapeString(ByVal txt As String) As String
EscapeString = Replace(txt, “”””, “”””””)
End Function
—
3. チーフアーキテクトが教える:実運用における極限の知見
この手法をエンタープライズ環境で展開するにあたり、通常の参考書には載っていない「現場の罠と回避策」を共有する。
A. オブジェクトのライフサイクルとメモリリークの根絶
VBAにおける `Visio.Document` や `Visio.Shape` などのCOMオブジェクトの参照は、プロシージャ終了時に確実に解放(`Set obj = Nothing`)しなければならない。特にVisioのCOMインターフェイスは参照カウントのリークに対して敏感であり、Documentを解放し忘れるとVisioプロセス(`visio.exe`)がバックグラウンドに残り続け、メモリリークやファイルロック(「ファイルは別のユーザーによって使用されています」エラー)を引き起こす。
上記のコード例の通り、`CleanUp` ラベルを用いた確実なオブジェクト解放のパターンを厳守すること。
B. マルチスレッド環境およびシステム間連携における排他制御
この不可視メタデータは、外部のC# (.NET) 製アドインや、外部バッチ処理(VBScriptやPowerShellからのCOM操作)からも読み書きが可能だ。
もしVisio図面がバックグラウンドプロセス(`Visible = False`)で大量処理される場合、複数のプロセスが同時に同一の `.vsdx` を開いて `SolutionXRef` を書き込もうとすると、ファイルアクセスの競合が発生する。
そのため、外部連携時は必ずWindows APIのミューテックス(Mutex)等を利用したプロセス間排他制御を実装するか、ファイルオープン時のリトライロジックを挟むのがプロの条件となる。
C. レガシー環境(Visio 2010〜2016)における挙動の差異
旧来の `.vsd`(バイナリ形式)と、モダンな `.vsdx`(OpenXML形式)では、ShapeSheetの内部構造のシリアライズ方法が異なる。
特に `.vsd` 形式の古い図面を自動処理する場合、`SolutionXRef` セクションのメモリマップ構造が原因で、稀に予期せぬオートメーションエラー(エラー番号: `-2032461311` 等)が発生することがある。
エンタープライズシステムを構築する際は、処理対象のドキュメントフォーマットを強制的に `.vsdx` にコンバートするバリデーションをパイプラインの最初期に挟むべきだ。
—
4. 結びにかえて
表層的なUIのカスタマイズに頼る開発は、システムの寿命を縮める。ユーザーの目に触れない領域で静かに、しかし確実にシステムの状態を保持し続けるこうした低レイヤーのテクニックこそが、保守性に優れ、かつ堅牢なVisio自動化ソリューションを支える基盤となる。
アーキテクトよ、コードの奥底までを掌握し、真にエレガントなシステムを構築せよ。
