こんにちは!Visio VBAの世界へようこそ。いつも業務自動化のために奮闘している皆さん、本当にお疲れ様です。
「マクロの記録」を卒業し、いよいよ本格的な図面処理に挑戦しようとすると、必ずぶつかる大きな壁があります。それが「大量のページを処理していると、途中でVisioが異常に重くなる、あるいは強制終了してしまう」という問題です。
特に、100ページを超えるような大規模な図面(VSDX)を開き、1ページずつPDFに出力するような処理を実行すると、PCのメモリ(RAM)がみるみる消費され、最終的にVisioが悲鳴を上げてしまいます。
「コードは間違っていないはずなのに、なぜ?」
そう思ったあなた、安心してください。これはあなたのプログラミングセンスのせいではありません。Visioが裏側で採用している「オブジェクトの管理ルール(COMの仕組み)」が原因なのです。
この記事では、Visio VBAの内部で起きているメモリの挙動をスッキリと図解的にひも解きながら、大規模処理でも絶対に落ちない「極限のオブジェクト解放テクニック」を、優しく丁寧に解説します。
ここをクリアすれば、あなたの書くVisio VBAの信頼性はプロレベルへと跳ね上がりますよ。さあ、一緒に一歩を踏み出しましょう!
—
1. なぜVisioはメモリを喰い尽くすのか?(原因の究明)
まずは、なぜメモリリーク(メモリが解放されずに溜まり続ける現象)が起きるのか、その仕組みを簡単なイメージで理解しましょう。
VBAとVisioの「見えない絆」
VBAで図面を操作するとき、私たちはコードの中で変数を作りますよね。
Dim doc As Visio.Document
Set doc = Documents.Open(“C:\Temp\HugeDrawing.vsdx”)
このとき、VBAの変数 `doc` は、実際の図面データを丸ごと抱えているわけではありません。図面データはVisio本体(メモリの奥深く)にあり、変数 `doc` はその場所を指し示す「名札(参照)」を持っているだけです。
+—>+Visio+Memory+(Real+Data))
(イメージ:VBAの変数は、メモリ上の本実体への「糸」のようなものです)
「ゾンビオブジェクト」の発生
問題は、私たちが処理を終えて変数の中身を気にしなくなった後です。
VBAは親切なようでいて、実は「もう使わないから片付けといてね」と明示的に指示(`Set 変数 = Nothing`)をしないと、裏でその糸を繋ぎっぱなしにする性質があります。
特にVisioは、以下のような親子関係(階層構造)を持っています。
- Application(Visio本体)
- Document(ファイル)
- Page(ページ)
- Shape(図形)
もし、ループ処理の中で「ページ」や「図形」を次々と変数に代入し、それを解放せずに次のループへ進むと、メモリの中は「誰も使っていないのに、糸がつながったままなので消せないオブジェクト(通称:ゾンビ)」で溢れかえってしまいます。これが、Visioが重くなる最大の原因です。
—
2. メモリを守るための「3つの黄金律」
大規模なPDF出力処理を安全に行うためには、以下の3つのルールを徹底します。
① 紐解くときは「下から上へ」
オブジェクトを解放(`Set = Nothing`)するときは、「子(末端)から親(根元)へ」の順番を意識します。
`Shape` ➔ `Page` ➔ `Document` の順番で糸を切っていくのが鉄則です。
② ループ内での「即時解放」
100ページあるなら、1ページ処理するたびに、そのページの変数(名札)を一度完全に初期化します。
「後でまとめて片付けよう」は通用しません。その都度片付けるのが、美しい部屋(メモリ)を保つコツです。
③ Windowsに一息つかせる `DoEvents`
PDFの出力処理は、非常に重いレンダリング処理です。VBAが全力疾走しすぎると、OS側のメモリ解放処理が追いつきません。
処理の合間に `DoEvents` という命令を挟むことで、VisioとWindowsに「今たまっているお片付け(描画やメモリ整理)を先にやっていいよ」と休憩時間を与えます。
—
3. 【実践コード】安全にPDF出力する「超堅牢マクロ」
それでは、これらのテクニックをすべて詰め込んだ、実戦用のVBAコードを見てみましょう。
このコードは、指定したフォルダにある大規模なVSDXファイルを安全に開き、各ページをPDFとして出力した後、1ミリのメモリも無駄にせず美しく終了します。
Option Explicit
”’
”’
Sub ExportLargeVisioToPDFSecurely()
Dim visApp As Visio.Application
Dim targetDoc As Visio.Document
Dim targetPage As Visio.Page
Dim pageCount As Integer
Dim i As Integer
Dim filePath As String
Dim pdfPath As String
‘ — 1. エラーハンドリングの設定 —
On Error GoTo ErrorHandler
‘ 画面更新やイベントを抑止して、処理の高速化とメモリ節約を図る
Set visApp = Visio.Application
visApp.ScreenUpdating = False
visApp.DeferRecalc = True
‘ 処理対象のファイルパス(環境に合わせて変更してください)
filePath = “C:\Temp\HugeDrawing.vsdx”
‘ — 2. ファイルのオープン —
‘ ReadOnly(読み取り専用)かつ、最小限の構成で開くのがメモリ節約のコツです
Set targetDoc = visApp.Documents.OpenEx(filePath, visOpenRO + visOpenNoWindow)
pageCount = targetDoc.Pages.Count
Debug.Print “総ページ数: ” & pageCount & ” の処理を開始します。”
‘ — 3. ページごとのループ処理(ここが心臓部です) —
For i = 1 To pageCount
‘ ページオブジェクトを毎回明示的に取得
Set targetPage = targetDoc.Pages.Item(i)
‘ 出力先PDFのパスを作成 (例: C:\Temp\Page_1.pdf)
pdfPath = “C:\Temp\Page_” & i & “.pdf”
‘ PDF出力の実行
‘ 高品質かつ、必要なページだけをピンポイントで出力します
targetPage.ExportAsFixedFormat visFixedFormatPDF, pdfPath, visDocExclIntentScreen
Debug.Print “ページ ” & i & ” のPDF出力を完了しました。”
‘ —————————————————-
‘ 【超重要】使い終わった子オブジェクトは「その場で即座に」解放する!
‘ —————————————————-
Set targetPage = Nothing
‘ OSとVisioにメモリ整理の時間を与える(おまじない以上の効果があります)
DoEvents
Next i
‘ — 4. 正常系の終了処理 —
Debug.Print “すべての処理が正常に完了しました。片付けを開始します。”
‘ ドキュメントを閉じる(保存はしない)
If Not targetDoc Is Nothing Then
targetDoc.Close
Set targetDoc = Nothing
End If
‘ アプリケーションの設定を元に戻す
visApp.ScreenUpdating = True
visApp.DeferRecalc = False
Set visApp = Nothing
MsgBox “PDF出力が安全に完了しました!”, vbInformation, “成功”
Exit Sub
‘ — 5. 万が一のためのエラーハンドリング —
‘ 途中でエラーが起きても、開いたファイルを放置せず必ず解放して閉じる仕組み
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “エラー終了”
‘ 安全な後片付けの連鎖(下から上へ)
If Not targetPage Is Nothing Then Set targetPage = Nothing
If Not targetDoc Is Nothing Then
‘ 警告ダイアログを出さずに閉じる
targetDoc.Close
Set targetDoc = Nothing
End If
If Not visApp Is Nothing Then
visApp.ScreenUpdating = True
visApp.DeferRecalc = False
Set visApp = Nothing
End If
End Sub
—
4. 初心者が陥りやすい「3つの罠」と対策
コードをコピペして動かすだけでも動作しますが、以下の「罠」を知っておくと、トラブルシューティングの能力が劇的に向上します。
罠①:「NothingにしたのにタスクマネージャーにVisioが残る!」
【原因】
VBAのコードの中で、以下のような書き方をしていませんか?
‘ 良くない例:親を飛ばして直接子にアクセスしている
Dim shapeName As String
shapeName = ActiveDocument.Pages(1).Shapes(1).Name
このように、変数に代入せずに `.`(ドット)で繋げて直接オブジェクトにアクセスすると、VBAは裏側で自動的に一時的なオブジェクト参照を作ります。そして、この「一時的な参照」は `Set Nothing` を書くことができないため、マクロが終わるまでメモリに残り続けます。
【対策】
面倒でも、必ず各階層のオブジェクトを変数に一度代入してから操作しましょう。
‘ 良い例:一つずつ階段を登るように取得し、最後はそれぞれNothingにする
Dim doc As Visio.Document
Dim pgs As Visio.Pages
Dim pg As Visio.Page
Set doc = ActiveDocument
Set pgs = doc.Pages
Set pg = pgs.Item(1)
‘ (処理を行う)
Set pg = Nothing
Set pgs = Nothing
Set doc = Nothing
罠②:開くときに「マクロ有効化」などの警告で処理が止まる
【原因】
`Documents.Open` メソッドは、図面を開くときにダイアログを表示することがあり、これが自動化の妨げになります。
【対策】
サンプルコードでも使用した `Documents.OpenEx` を使いましょう。
`visOpenRO`(読み取り専用)や `visOpenNoWindow`(非表示で開く)などのオプション(引数)を組み合わせることで、余計なメモリ消費を抑え、ダイアログによる停止を防ぐことができます。
罠③:PDF出力中にディスク容量や権限のエラーになる
【原因】
Cドライブの直下など、書き込み権限がない場所にPDFを出力しようとするとエラーになります。
【対策】
出力先フォルダは、デスクトップやマクロを実行しているブックと同じフォルダ(`ThisWorkbook.Path` など)を動的に取得するように工夫すると、環境を選ばずに動作する優しいツールになります。
—
5. まとめ:美しいコードは、PCにも優しい
ここまでの内容をクリアできれば、Visio VBAの基本と「最も奥深いメモリ管理」の本質はバッチリマスターできています!
最後に、今回の重要なポイントをおさらいしましょう。
1. オブジェクトは「使い捨て」の意識を持つ。
2. ループ処理の内部では、毎回 `Set = Nothing` で名札を外す。
3. `DoEvents` でVisioとOSに深呼吸をさせる。
4. エラーが起きても、逃げずに後片付けをする仕組み(`ErrorHandler`)を作る。
マクロの記録から一歩踏み出し、このように「メモリの気持ち」を考えたコードが書けるようになると、作成したツールの安定性は天と地ほど変わります。職場のメンバーからも「このツール、全然落ちなくて本当に使いやすい!」と絶賛されるはずです。
あなたの自動化ライフが、より快適で素晴らしいものになりますように。応援しています!
