マルチメディアタイマとPerformanceCounter

 プログラムの中で一定時間でちょっとした処理を繰り返したい、という時にはタイマを使用して指定ウインドウに一定時間毎にメッセージを送るようにすると手軽に実現できます。ただ、このタイマは精度が低いため、本当に時間(周期)をあまり気にしない処理にしか使えないのが難点ですね。
 今回は、この通常のタイマより精度が高いマルチメディアタイマを使ってみることにします。また、このマルチメディアタイマの精度を計るために、マイクロ秒(環境によってはナノ秒?)単位で時間を測定できるPerformanceCounterも試してみました。

マルチメディアタイマ

 マルチメディアタイマは、起動されると指定した時間ごとにコールバック関数を呼び出します。コールバック関数は別スレッドで呼び出され、(後ほど測定しますが)周期はかなり安定しているようです。ほぼ1ミリ秒(以下)単位の精度が得られることが多いので、用途としてはゲームのBGM演奏などが考えられるでしょう。
 というか、実はマルチメディアタイマから呼び出されるコールバック関数内では、できることがMIDIデバイスの操作とウインドウへのメッセージ送信程度に制限されているので、ほとんどMIDI再生専用の機能だったりします(^^;。まあ、(今回のプログラムのように)コールバック関数内からウインドウにメッセージを送れば、「高精度の汎用タイマ」として使えないこともないんでしょうが・・・・。

 マルチメディアタイマを起動するには、呼び出し周期と精度(ともにミリ秒単位で指定)、コールバック関数のポインタ、コールバック関数に渡すデータのポインタ、フラグを引数にtimeSetEvent()を呼び出します。フラグの指定によっては、指定時間経過後に一度だけ動作するタイマを作成したり、イベントをセットしたりすることもできます。

  /* マルチメディアタイマ起動 */
  dwTimerID = timeSetEvent(5, 4, timerFunc, 0, TIME_PERIODIC);

 こうしてタイマを起動すると、指定した周期でtimeFunc()が呼び出されるので、そこにMIDIデバイスの制御などのタイマを使って行いたい処理を書くだけ。タイマを使い終わったら、timeKillEvent()で終了します。

 今回は、このタイマイベントでマルチメディアタイマの周期を測定するために「100回呼び出させる度にメインウインドウにメッセージを送る」ようにしました。

  /* マルチメディアタイマコールバック関数 */
  void CALLBACK timerFunc(UINT uiID, UINT uiNo, DWORD dwCookie, DWORD dwNo1, DWORD dwNo2) {

      static int i = 0;

      i++;

      /* 100回呼び出される度にメッセージ送信 */
      if (i == 100) {

          PostMessage(g_hwMain, WM_TIMER_100, 0, 0);

          i = 0;

      }

  }

PerformanceCounter

 メッセージ処理では、メッセージが送られてくる間隔を高精度で測定するためにQueryPerformanceCounter()を使用します。このAPIは、timeGetTime()以上の高精度カウンタで周波数をQueryPerformanceCounter()で取得できますが、私の環境(i845G+Pentium4 2.4B)では350万Hz程度でした。この周波数どおりなら、マイクロ秒単位まではほぼ正確に取得できる、といえそうですね。

 ただ、このカウンタの値、「64ビット」で返ってくるのでちょっと面倒かもしれません。今回はLARGE_INTEGER型に入れて処理しましたが、DWORD/intとの間で計算をする時は、途中であふれないよう注意が必要でしょう。

  case WM_TIMER_100:

      i++;

      /* 現カウント値取得 */
      QueryPerformanceCounter(&liNow);

      /* 前回からの経過時間をミリ秒単位で算出 */
      dwTime = (DWORD)(((liNow.QuadPart - liPrev.QuadPart) * 1000) / liFreq.QuadPart);

      /* 表示用文字列作成 */
      wsprintf(aszStr, "%d - %I64dHz\r\n%d", i, liFreq.QuadPart, dwTime);

      /* 文字列を表示 */
      SetWindowText(g_hwView, aszStr);

      /* 今回のカウントを保存 */
      liPrev = liNow;

      return 0;

 なお、PerformanceCounterは、CPU周波数が動的に変わる場合や複数のCPUがある場合などはそのままでは正確なカウントが行えず注意が必要なようです。最近は省電力のため動的にCPU周波数を変えるCPUが増えてきたので、この点も意識しておかないと思わぬ結果につながりそうですね・・・・。

プログラム

プログラムソース表示

 起動すると、マルチメディアタイマを起動し、PerformanceCounterの周波数と100回処理が行われるのに要した時間を表示します。タイマ起動時の周期や精度指定をいろいろ変えて試してみましょう。できれば、OSCPUが違う環境で試してみると面白いかもしれません。


プログラミング資料庫 > Windowsプログラミング研究室