Visio VBAの限界を突破せよ:`SetTimer`による極限のバックグラウンド監視アーキテクチャ
Visio VBAは、その直感的なUI操作性とは裏腹に、背後では「ドキュメント指向型COMオブジェクト」の迷宮が広がっている。多くの開発者が `Application.OnTime` のようなExcel的なイベント駆動を期待して絶望するが、Visioには標準で洗練されたタイマーイベントは存在しない。
だが、諦める必要はない。Windows APIを直接叩き、OSのメッセージループにフックをかける。これが、Visioを単なる作図ツールから「自律的な監視ノード」へと昇華させる唯一の道だ。
—
1. なぜ「擬似タイマー」が必要なのか
VBAの標準機能では、同期処理中にCPUを専有し、VisioのUIスレッドをフリーズさせるリスクがある。しかし、Windows APIの `SetTimer` を利用すれば、Visioのメインループを阻害せずに、非同期で「バックアップ」や「データ同期」のトリガーを発火させることが可能だ。
この実装における最大の敵は、「不適切な参照によるメモリリーク」と「Visioの不安定化」である。これを防ぐためのアーキテクチャを提示する。
—
2. 実装:Windows APIを活用したタイマーエンジン
標準モジュールに以下のコードを配置する。ポイントは、タイマーIDを静的に管理し、必ず `KillTimer` で解放するサイクルを構築することだ。
‘ 標準モジュール: modTimerManager
Option Explicit
If VBA7 Then
Private Declare PtrSafe Function SetTimer Lib “user32” (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib “user32” (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Public Declare PtrSafe Function GetForegroundWindow Lib “user32” () As LongPtr
Else
Private Declare Function SetTimer Lib “user32” (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib “user32” (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
End If
Private m_TimerID As LongPtr
‘ タイマー開始 (インターバルはミリ秒)
Public Sub StartMonitoring(ByVal intervalMs As Long)
If m_TimerID <> 0 Then StopMonitoring
m_TimerID = SetTimer(0, 0, intervalMs, AddressOf TimerCallback)
End Sub
‘ タイマー停止 (終了時に必ず呼ぶこと)
Public Sub StopMonitoring()
If m_TimerID <> 0 Then
KillTimer 0, m_TimerID
m_TimerID = 0
End If
End Sub
‘ コールバック関数
Public Sub TimerCallback(ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As LongPtr)
On Error Resume Next
‘ ここで同期処理やバックアップ処理を呼び出す
‘ 重要なのは「重い処理をここで直接書かない」こと。別プロシージャへ逃がす
Call PerformBackgroundSync
End Sub
—
3. 伝説的エンジニアが教える「避けるべき罠」
このアーキテクチャを実運用に乗せる際、以下の3点だけは死守せよ。
A. オブジェクトの明示的破棄
Visioの `Document` や `Page` オブジェクトをモジュールレベル変数で保持し続けるのは自殺行為だ。タイマー内では、必ず `Set obj = Nothing` を徹底し、参照カウントを管理せよ。
B. エラーハンドリングの徹底
コールバック関数 `TimerCallback` 内で未処理のエラーが発生すると、即座にVisioがクラッシュする。`On Error Resume Next` で囲み、エラーログを外部ファイルへ書き出す仕組みが不可欠だ。
C. 再入可能性(Reentrancy)の考慮
もしデータ同期処理が5秒かかり、タイマーが3秒ごとに設定されていたらどうなるか?処理が重なり、メモリが枯渇する。必ず「フラグ」を用いた排他制御を行うこと。
Private m_IsRunning As Boolean
Public Sub PerformBackgroundSync()
If m_IsRunning Then Exit Sub ‘ 二重実行防止
m_IsRunning = True
‘ ここに同期ロジックを記述
‘ 例: ActiveDocument.SaveAsEx …
m_IsRunning = False
End Sub
—
4. まとめ:システムを「制御」する視点
Visioをただの作図ツールとして使うのは、フェラーリで近所のコンビニに行くようなものだ。Windows APIを介したタイマー実装は、Visioを単体で自律的に動く「エージェント」へと進化させる。
- 安定性:常に `KillTimer` を忘れないこと。
- 拡張性:同期処理を別のDLLやコンソールアプリに委譲し、VBAは「指示役」に徹すること。
- 保守性:エラーログはVisio本体の外に出すこと。
このアーキテクチャは、レガシーな環境であっても、現代的な非同期処理の片鱗を再現できる。Visioの奥底に眠るCOMのパワーを引き出し、真の意味での「業務自動化」を成し遂げてほしい。健闘を祈る。
