Visio VBAの深淵:メモリリークを封殺する「オブジェクト・ライフサイクル」の極意
Visioというアプリケーションは、COM(Component Object Model)の設計思想が色濃く残る、極めて「気難しい」環境だ。大規模な図面処理を行う際、多くのエンジニアが「なぜか処理が進むほど動作が重くなる」「長時間稼働でVisioがクラッシュする」という壁に突き当たる。
その原因の9割は、コードの不備ではなく、オブジェクトの参照カウントに対する無頓着さにある。本稿では、レガシーを背負いながらも最先端の安定性を実現するための、オブジェクト変数管理の深淵を紐解く。
—
1. なぜ「Nothing代入」が神話ではなく必須なのか
VBAのガーベジコレクションは、参照カウント方式だ。`Set obj = Nothing` という記述を「気休め」と侮ってはならない。
Visioのオブジェクトモデルにおいて、`Application` -> `Document` -> `Page` -> `Shape` という階層を辿る際、暗黙的な参照が生成され続ける。特にループ内で`Shape`オブジェクトを次々と取得する場合、参照が解放されないままスタックされると、COMサーバー側のメモリリークを誘発し、最終的に「OLEオートメーションエラー」があなたを待っている。
致命的なアンチパターン
‘ 悪い例:参照が宙に浮き、メモリリークの温床となる
For Each shp In ActivePage.Shapes
‘ 処理中に内部で新たなオブジェクトが生成され、参照が残り続ける
Debug.Print shp.Name
Next
—
2. 実践:スコープ設計と「階層的解放」の作法
大規模処理を記述する際、私は常に「スコープを物理的に分断する」手法を採る。関数内ですべてを完結させず、処理単位でオブジェクトのライフサイクルを完結させる設計だ。
推奨される実装テンプレート
Public Sub ProcessLargeDrawing()
Dim vApp As Visio.Application
Dim vDoc As Visio.Document
Dim vPage As Visio.Page
‘ 参照の保持は最小限に。必要ならSetし、不要なら即座にNothingへ
Set vApp = Application
Set vDoc = vApp.ActiveDocument
Set vPage = vDoc.Pages(1)
‘ 処理本体を別プロシージャに委譲することで、変数のスコープを強制終了させる
Call TraverseShapes(vPage)
‘ 明示的な解放(階層の逆順で行うのがCOMの作法)
Set vPage = Nothing
Set vDoc = Nothing
Set vApp = Nothing
End Sub
Private Sub TraverseShapes(ByVal pg As Visio.Page)
Dim shp As Visio.Shape
‘ Shapeのループ処理はメモリを食うため、オブジェクトを使い回さない
Dim i As Long
For i = 1 To pg.Shapes.Count
Set shp = pg.Shapes(i)
‘ ここで重い処理を行う
DoSomethingWithShape shp
‘ ループ内で都度解放する(これが大規模処理の鉄則)
Set shp = Nothing
Next i
End Sub
—
3. レガシー環境を生き抜くための「Windows API」という劇薬
VisioのVBAがハングアップする際、その多くはVisioの描画スレッドが応答不能に陥っている。これを回避し、OSレベルでの介入を行うためには、`user32.dll`の力を借りるのがシニアエンジニアの嗜みだ。
特に、長時間処理中に「処理中」のステータスをOSに伝え、バックグラウンドの描画を一時的に抑止することで、メモリ消費を劇的に抑えることが可能だ。
‘ 描画の更新を一時停止するVisioの機能と組み合わせるのがベスト
‘ Application.ScreenUpdating = False を使用するが、
‘ 完全にハングを防ぐには、DoEventsを適切な頻度で挟むこと。
Public Sub SafeProcess()
Application.ScreenUpdating = False
‘ 処理中にOSのメッセージキューを捌く
‘ これを忘れるとWindows側から「応答なし」と判定される
DoEvents
‘ …処理ロジック…
Application.ScreenUpdating = True
End Sub
—
4. チーフアーキテクトからの提言
あなたが書いているそのコードは、数万個のシェイプを扱う過酷な環境に耐えうるものだろうか?
1. Late Bindingを使い分ける: パフォーマンス優先ならEarly Bindingだが、バージョン互換性を考慮するならLate Bindingも視野に入れる。ただし、その場合は`Set shp = Nothing`を徹底せよ。
2. エラーハンドリングの徹底: エラーが発生した際、`Set`されたオブジェクトが解放されずに終了するコードは「爆弾」と同じだ。必ず`Finally`ブロック(VBAでは`GoTo`ラベルによる後処理)を用意せよ。
3. COMコンポーネントの残骸を追う: タスクマネージャーでVisioのメモリ使用量を監視し、処理前後で増分が「0」に収束するまでチューニングを繰り返せ。
Visio VBAは古い言語だが、その制御の奥深さは現代のプログラミングにおいても色褪せない。メモリを支配する者は、プロセスを支配する。妥協なきコードこそが、あなたのシステムを最強のものへと昇華させる唯一の手段だ。
さあ、エディタを開け。あなたのコードに、魂を込める時間だ。
