【テクニカル・上級編】【中級】複数のステンシルからシェイプを検索・一括配置する自動化ツール – Visio VBA解析バイブル

スポンサーリンク
スポンサーリンク

階層化されたステンシル群からの動的シェイプ展開:メモリ管理とスケーラビリティの極致

業務自動化の現場において、単一のステンシルからシェイプを落とす程度のコードは、誰でも書ける。しかし、数百のステンシルファイルが散在するレガシーなエンタープライズ環境において、それらを動的に走査し、目的のマスターを特定し、かつシステムの安定性を一切損なわずに一括配置する……。ここから先は、単なる「プログラミング」ではなく「システム設計」の領域だ。

多くの開発者が陥る罠は、ループ内でのオブジェクトのライフサイクル管理の放棄である。ステンシルを次々と開いては閉じる処理を繰り返す際、適切に参照を解放しなければ、VBAのプロセスはメモリを食いつぶし、最終的にはVisioのプロセス自体がゾンビ化してクラッシュする。

本稿では、シニアエンジニアが実務で直面する「大量のステンシル管理」という課題に対し、極限まで最適化された設計思想と実装コードを提示する。

1. アーキテクチャの設計思想:決定論的なオブジェクト解放

VisioのCOMオブジェクトモデルは強力だが、その分、メモリ管理は開発者の規律に依存する。複数のステンシルをループで処理する場合、我々は「決定論的な破棄(Deterministic Destruction)」を徹底しなければならない。

致命的なアンチパターン

' 破綻する設計の典型例
For Each path In stencilPaths
    Set st = Documents.OpenEx(path, visOpenDocked)
    ' 目的のマスターを探す処理...
    ' st を Nothing にせず、次のループへ
Next

このコードでは、`st`(Stencilオブジェクト)の参照カウントが減らず、メモリ上にステンシルのハンドルが蓄積され続ける。これが数千回のループ、あるいは大規模な図面操作と組み合わさったとき、システムは死ぬ。

正解の設計

我々は、各ループの終端において、対象となる `Document` および `Stencil` オブジェクトを明示的に `Nothing` へ割り当て、参照カウントをゼロに制御する。これにより、ガベージコレクションの挙動に頼ることなく、ヒープメモリの断片化を防ぐ。

2. 実装:マルチステンシル・スキャンニング・エンジン

以下に、堅牢なエラーハンドリングとメモリ最適化を組み込んだ、プロフェッショナル仕様のコアロジックを示す。

Option Explicit

' 定数定義:マジックナンバーを排除し、保守性を高める
Private Const VISIO_ERROR_NOT_FOUND As Long = -1

''' <summary>
''' 指定されたマスターシェイプを複数のステンシルから探索し、ページ上に配置する
''' </summary>
''' <param name="targetMasterName">探索するマスターの名前</param>
''' <param name="stencilPaths">ステンシルファイルのフルパスの配列</param>
''' <param name="targetPage">配置対象のページ</param>
''' <param name="startCoord">配置開始座標 (Visio Units)</param>
Public Sub BatchPlaceMasterShapes( _
    ByVal targetMasterName As String, _
    ByVal stencilPaths As Variant, _
    ByVal targetPage As Visio.Page, _
    ByVal startCoord As Visio.Point)

    Dim i As Long
    Dim docStencil As Visio.Document
    Dim stn As Visio.Stencil
    Dim master As Visio.Master
    Dim foundMaster As Visio.Master
    Dim shape As Visio.Shape
    Dim currentPos As Double
    
    ' パフォーマンス最適化:画面更新とイベントを一時停止
    ' これにより、描画負荷を軽減し、実行速度を劇的に向上させる
    On Error GoTo ErrorHandler
    Application.ScreenUpdating = False
    
    currentPos = startCoord.X
    
    ' 1. ステンシル・ループ処理
    For i = LBound(stencilPaths) To UBound(stencilPaths)
        Set docStencil = Nothing ' 初期化の徹底
        Set foundMaster = Nothing
        
        ' ステンシルのオープン (ReadOnlyで開くことで、ファイルロックのリスクを最小化)
        Set docStencil = Application.Documents.OpenEx(stencilPaths(i), visOpenDocked + visOpenReadOnly)
        
        If Not docStencil Is Nothing Then
            ' ステンシルオブジェクトの取得
            Set stn = docStencil.Stencil
            
            ' 2. マスターの探索
            ' マスターが存在するかを厳密にチェック
            Set foundMaster = FindMasterInStencil(stn, targetMasterName)
            
            If Not foundMaster Is Nothing Then
                ' 3. 配置処理
                ' マスターが見つかった場合、ページにドロップ
                ' 座標は、一列に並べるためのオフセット計算を行う
                Set shape = targetPage.Drop(foundMaster, currentPos, startCoord.Y)
                
                ' 配置後の座標微調整(必要に応じて)
                shape.CellsU("PinX").FormulaU = currentPos
                
                ' 次の配置位置を更新(例:シェイプ幅 + 0.5インチ)
                currentPos = currentPos + shape.CellsU("Width").ResultIU + 0.5
            End If
            
            ' 4. 重要:オブジェクトの明示的解放
            ' ループの各ステップで、参照を確実に断ち切る
            Set stn = Nothing
            Set docStencil.Masters = Nothing ' 全マスターの参照をクリア
            docStencil.Close
            Set docStencil = Nothing
        End If
    Next i

CleanUp:
    ' リソースの復元
    Application.ScreenUpdating = True
    Exit Sub

ErrorHandler:
    MsgBox "Critical Error in BatchPlaceMasterShapes: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

''' <summary>
''' ステンシル内から特定のマスターを検索する低レイヤー関数
''' </summary>
Private Function FindMasterInStencil(ByRef stn As Visio.Stencil, ByVal masterName As String) As Visio.Master
    Dim m As Visio.Master
    
    ' Stencil.Masters コレクションを走査
    ' 直接インデックス指定ではなく、名前による比較を行う
    For Each m In stn.Masters
        If StrComp(m.Name, masterName, vbTextCompare) = 0 Then
            Set FindMasterInStencil = m
            Exit Function
        End If
    Next m
    
    Set FindMasterInStencil = Nothing
End Function

3. 極限の知見:実務におけるプロフェッショナルの視点

3.1. Windows APIとの境界線

VBAの標準機能では、ファイルシステムへのアクセスや、プロセスの優先度制御において限界がある。もし、数千のステンシルを扱うシステムが、他の業務アプリケーション(ERPやCAD)と並行稼働している場合、`Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)` を用いて、ループの合間に意識的なスレッド休止を挟むことを検討せよ。これは、CPUのスパイクを防ぎ、OS全体のレスポンスを維持するための「優しさ」である。

3.2. 座標系の罠:Visio Units vs. Real-world Units

コード内で `shape.CellsU(“PinX”).FormulaU` を使用しているのは、`FormulaU`(Universal Unit)を使用するためだ。`Formula` を使用すると、ドキュメントの単位設定(インチ、ミリメートル等)に依存し、計算結果が狂うリスクがある。プロフェッショナルは、常に「ユニバーサルな単位」でロジックを構築する。

3.3. レガシー環境の保守:Late Bindingの選択

もし、このツールを配布する対象のVisioバージョンが混在している(Visio 2013からVisio 2021までなど)場合、参照設定(Early Binding)は避けるべきだ。`Object` 型を用いたLate Bindingを採用することで、ライブラリのバージョン不一致による「Missing Reference」エラーを回避できる。ただし、その代償としてインテリセンスは失われる。そのコストを支払う価値があるのは、配布の広範な互換性が必要な場合のみである。

結論

自動化ツールの品質は、成功した時の速度ではなく、「失敗しそうな状況でいかにエレガントに、かつ安全に止まれるか」によって決まる。

オブジェクトのライフサイクルを完全に掌握し、メモリの挙動を予測可能なものにすること。それが、シニアエンジニアとして、レガシーな基盤を支えるアーキテクトとしての責務である。

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