PowerPoint VBAの「時」を支配する:ミリ秒精度のスライドショー監視アーキテクチャ
PowerPoint VBAにおいて、Excelのような`Application.OnTime`は存在しない。これは、イベント駆動型アーキテクチャを設計する者にとって、最初にして最大の壁だ。
多くの初学者は`DoEvents`をループに組み込んだ泥臭い実装に逃げるが、それは「プロ」の仕事ではない。CPUリソースを食いつぶし、スライド遷移の同期を乱すだけの低質なコードは即座に排除すべきだ。
今回は、Windows API(`timeSetEvent`)を駆使し、PowerPointの`Presentation`オブジェクトイベントと融合させることで、スライドショー実行中の経過時間をミリ秒単位で正確にトラッキングする、堅牢な監視メカニズムを解説する。
—
1. なぜ「マルチメディアタイマー」なのか
VBAの`Timer`関数は精度が低く、`Sleep` APIはスレッドを停止させるためプレゼンテーションの操作性を阻害する。ミリ秒単位の正確性を担保しつつ、スライドショーの描画パイプラインに干渉しないためには、Windowsのマルチメディアタイマーが最適解となる。
このアプローチの利点は以下の通りだ:
- 非同期実行: メインスレッド(UIスレッド)をブロックせずにバックグラウンドで計測。
- 高分解能: CPUの割り込みを利用するため、OSレベルの精度を確保。
- 低負荷: イベントドリブンであるため、ポーリングのようなCPUスパイクが発生しない。
—
2. 実装の要:Class Moduleによるイベント監視
まずは、`WithEvents`を用いてスライドショーの開始・終了をフックするコントローラーを構築する。
Class Module: `SlideShowMonitor`
Option Explicit
‘ PowerPointのイベントを監視するためのクラス
Public WithEvents App As Application
Private Sub App_SlideShowBegin(ByVal Wn As SlideShowWindow)
‘ スライドショー開始時にタイマーを起動
StartTimer
End Sub
Private Sub App_SlideShowEnd(ByVal Pres As Presentation)
‘ スライドショー終了時にタイマーを停止し、結果をログへ
StopTimer
End Sub
—
3. Windows APIによるタイマー制御の実装
次に、標準モジュールでAPIを宣言し、タイマーのコールバックを管理する。重要なのは、メモリリークを防ぐために`timeKillEvent`を確実に呼ぶことだ。
Standard Module: `TimerEngine`
Option Explicit
‘ Windows Multimedia APIの定義
Private Declare PtrSafe Function timeSetEvent Lib “winmm.dll” (ByVal uDelay As Long, ByVal uResolution As Long, ByVal lpFunction As LongPtr, ByVal dwUser As LongPtr, ByVal uFlags As Long) As Long
Private Declare PtrSafe Function timeKillEvent Lib “winmm.dll” (ByVal uEventID As Long) As Long
Private m_TimerID As Long
Private m_StartTime As Double
‘ ミリ秒単位での計測開始
Public Sub StartTimer()
m_StartTime = Timer
‘ 100ms周期でコールバック(精度調整はここで行う)
m_TimerID = timeSetEvent(100, 1, AddressOf TimerCallback, 0, 1)
End Sub
‘ タイマー停止とリソース解放
Public Sub StopTimer()
If m_TimerID <> 0 Then
timeKillEvent m_TimerID
m_TimerID = 0
End If
End Sub
‘ コールバック関数:ここを起点に計測データを蓄積する
Public Sub TimerCallback(ByVal uID As Long, ByVal uMsg As Long, ByVal dwUser As Long, ByVal dw1 As Long, ByVal dw2 As Long)
‘ ここで経過時間を計算し、グローバル変数やDBへ書き出す
‘ ※注意: VBAのコールバック内でオブジェクト生成を行うとクラッシュする
‘ データ蓄積は配列やバッファへ単純代入すること
Debug.Print “Elapsed: ” & Format(Timer – m_StartTime, “0.000”) & “s”
End Sub
—
4. 堅牢な運用のためのアーキテクトの視点
オブジェクトライフサイクルの管理
`App`変数の初期化を忘れると、イベントは一切発火しない。メインの初期化ルーチンで必ず`Set Monitor.App = Application`を実行し、スコープを維持せよ。クラスモジュールのインスタンスが破棄されないよう、静的オブジェクトとして保持するのがコツだ。
メモリの最適化
`TimerCallback`内では、重い処理やオブジェクトの生成(`ActivePresentation.Slides(x)`など)は厳禁だ。VBAはガベージコレクションが非同期プロセスに弱いため、コールバック内の過剰なリクエストは「Access Violation」による強制終了を招く。計測結果はプリミティブ型の配列に格納し、プレゼン終了後に後処理を行う設計にすべきだ。
レガシー環境への配慮
もし、32bit/64bitの混在環境で開発している場合、`PtrSafe`属性と`LongPtr`型の活用は必須となる。API宣言を誤れば、実行時にメモリ破壊を引き起こす。常に環境差分を吸収するラッパーを構築しておくことが、プロフェッショナルとしての最低限の流儀である。
—
結論:コードは「静寂」であるべきだ
優れた自動化コードとは、実行中にユーザーの存在を感じさせないものだ。このタイマー実装は、プレゼンターのパフォーマンスを一切邪魔することなく、正確なログを刻み続ける。
PowerPointのイベントモデルとWindows APIの境界線を掌握し、制御下に置く。これこそが、VBAを単なるマクロから「業務基盤」へと昇華させる唯一の道である。コードは魂を込めて書け。それが、我々の世界における唯一の掟だ。
