序文:Visioという名の「データの墓場」を統治せよ
現場の最前線で長年アーキテクチャを設計してきた者なら、Visioが単なるドローイングツールではなく、実態は「異種混合データのコンテナ」であることを痛いほど理解しているはずだ。
設計図面の中には、純粋なベクター図形、スキャンされた古いTIFF画像、さらにはExcelから無造作に貼り付けられたOLEオブジェクトが混在し、混沌(カオス)を形成している。これらの「異物」を正確に識別し、制御下に置くことは、大規模なドキュメント自動生成やシステム連携において避けては通れない関門だ。
今回は、Visio VBAにおいてこれらを峻別するための鍵となる`Shape.ForeignType`プロパティを軸に、メモリ管理、Windows API、そしてレガシー資産を現代のワークフローに繋ぎ止めるための「極限の知見」を伝授する。
—
1. `Shape.ForeignType` ― 異物の正体を暴く審判の目
VisioにおけるShapeオブジェクトは、その出自によって振る舞いが劇的に異なる。単なる`Shape.Type`のチェックだけでは、その内部に潜む「OLEの魔物」や「巨大なビットマップ」を特定するには不十分だ。
ここで我々が頼るべきは`Shape.ForeignType`プロパティである。これは、そのシェイプがVisioネイティブではない「外部(Foreign)」のデータ構造を持っている場合に、その詳細をビットマスク形式で返す。
主要なForeignType定数
- visTypeIsBitmap (0x0020): ラスター画像(BMP, JPG, PNG等)。
- visTypeIsOLE2 (0x8000): ExcelシートやWord文書などの埋め込み/リンクオブジェクト。
- visTypeIsMetafile (0x0010): WMF/EMFなどのメタファイル。
この値をビット演算で判定することにより、我々は図面上の「重い」オブジェクトをピンポイントで捕捉できる。
—
2. 実戦:異種混合シェイプ識別エンジン
以下のコードは、単なるループ処理ではない。OLEオブジェクトのインスタンス化に伴うオーバーヘッドを考慮し、メモリ解放を意識したプロフェッショナルな実装例だ。
‘
‘ @Description: 図面内のシェイプを走査し、ForeignTypeに基づいて識別・分類する
‘ @Author: Legendary Chief Architect
‘
Public Sub AnalyzeShapeComposition()
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim fType As Integer
Dim reportMsg As String
‘ 画面更新の停止(パフォーマンス最適化の鉄則)
Application.ScreenUpdating = False
Set vsoPage = ActivePage
For Each vsoShape In vsoPage.Shapes
‘ ForeignTypeの取得
fType = vsoShape.ForeignType
‘ 判定ロジック:ビットマスクによる厳密な評価
If (fType And visTypeIsOLE2) <> 0 Then
‘ OLEオブジェクト(Excel、Word等)
‘ OLEはメモリ消費の主犯。ProgIDを特定し、必要ならAPIで制御する
ProcessOLEObject vsoShape
Else向上If (fType And visTypeIsBitmap) <> 0 Then
‘ ビットマップ画像
Debug.Print “Bitmap Detected: ” & vsoShape.Name & ” Size: ” & vsoShape.Cells(“Width”).ResultStr(visNone)
ElseIf (fType And visTypeIsMetafile) <> 0 Then
‘ メタファイル(ベクターとラスターの中間)
Debug.Print “Metafile Detected: ” & vsoShape.Name
Else
‘ Visioネイティブのベクターシェイプ
‘ これこそがVisioが本来扱うべき軽量なデータ構造
End If
‘ 巨大な図面では、数千個のシェイプを処理する際に
‘ メモリの断片化が起きるため、DoEventsでOSに制御を戻すタイミングを計る
If vsoShape.ID Mod 100 = 0 Then DoEvents
Next vsoShape
Application.ScreenUpdating = True
MsgBox “分析完了。イミディエイトウィンドウを確認せよ。”, vbInformation
End Sub
‘——————————————————————————-
‘ OLEオブジェクトの特定と、リソース消費に対する警鐘
‘——————————————————————————-
Private Sub ProcessOLEObject(ByRef shp As Visio.Shape)
On Error Resume Next
Dim progID As String
‘ Objectプロパティへのアクセスは、背後でサーバーアプリケーションを
‘ 起動させる可能性があるため、極めて慎重に行う必要がある
progID = shp.Object.ProgID ‘ ここでExcel等がバックグラウンドで立ち上がる可能性がある
Debug.Print “— OLE Object Warning —”
Debug.Print “Shape Name: ” & shp.Name
Debug.Print “ProgID: ” & IIf(progID <> “”, progID, “Unknown”)
‘ OLE2オブジェクトは、参照を保持し続けるとプロセスが残るリスクがある
‘ 処理が終われば即座にNothingを叩き込むのがアーキテクトの作法だ
End Sub
—
3. 深淵の知見:OLEオブジェクトとWindows API
OLEオブジェクト(`visTypeIsOLE2`)の処理において、シニアエンジニアが最も恐れるのは「ゴーストプロセス」だ。Visio内でExcelオブジェクトをダブルクリックして編集した後、VBAでそのシェイプを操作しようとすると、COMの参照カウントが正しく管理されず、`Excel.exe`がメモリ上に居座り続ける現象は有名だ。
これを確実に制御するためには、Windows APIによるウィンドウハンドルの監視が必要になる場合がある。
Windows APIによるプロセス監視のヒント
OLEオブジェクトを活性化(Activate)させる必要がある場合、`FindWindow` APIを使用して、特定のProgIDに紐づくウィンドウが予期せずポップアップしていないか、あるいはハングアップしていないかを監視する。これは特に、レガシーなERPシステムから出力されたVisio図面を自動処理する際に必須となるテクニックだ。
‘ 標準モジュールの上部で宣言
If VBA7 Then
Declare PtrSafe Function FindWindow Lib “user32” Alias “FindWindowA” _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Else
Declare Function FindWindow Lib “user32” Alias “FindWindowA” _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
End If
—
4. パフォーマンスの重み:なぜ識別が必要なのか
なぜ我々はここまでして`ForeignType`にこだわるのか?それは、Visioドキュメントの「肥大化」と「描画遅延」の犯人を特定するためだ。
1. ビットマップの罠: 高解像度の写真を縮小して大量に貼り付けた図面は、ファイルサイズを指数関数的に増大させる。`visTypeIsBitmap`を検知し、外部ツールでリサイズした画像に差し替える、あるいはリンク形式に変更する自動化処理は、ファイルサーバーのストレージ容量を救う。
2. OLEの呪い: 100個のExcel埋め込みオブジェクトを持つVisioファイルを開くのは、100個のExcelインスタンスを管理するのと同義だ。これを`ForeignType`で検知し、静的なメタファイル(EMF)に変換(`Shape.Export`して再挿入)することで、ドキュメントのパフォーマンスを劇的に改善できる。
—
5. 結論:アーキテクトとしての矜持
Visio VBAをマスターするということは、オブジェクトブラウザの表面をなぞることではない。
そのシェイプが、メモリ上でどのようなフットプリントを残し、どのダイナミックリンクライブラリ(DLL)を呼び出しているのかを想像することにある。
`Shape.ForeignType`は、そのための第一歩に過ぎない。
この識別処理を基盤として、ドキュメントの「軽量化」「データ抽出」「レガシー脱却」といった、より高度な次元の自動化へと突き進んでほしい。
現場からは以上だ。幸運を祈る。
