ExclusiveMMTimer(マルチメディアタイマーラッパ)

(2015年11月1日更新:以下、lc_timerCBの引数後ろ三つを、DWORDからDWORD_PTRへ修正。timeSetEventの第三引数が正しくreinterpret_castできないことがあったため)

Win32のnative環境で高精度タイマーと言えばマルチメディアタイマーだが、今回はこのマルチメディアタイマーのAPIを知らなくてもすぐにタイマーが利用できるようなラッパークラスを作成したのでメモ。
以下のような利点がある。

  1. コールバックとしては関数のみでなく、関数オブジェクトも指定することができる。どちらの場合でもCALLBACK接頭辞は必要ない。
  2. なので各種関数アダプタを使えば、つまり任意のインスタンスのメソッドをコールバック関数として指定することもできる。
  3. マルチメディアタイマーAPIを知らなくてもすぐ使える。
  4. 排他処理が組み込み済みなので、あるコールバック処理が終わらないうちに次のコールバックが始まることはない。
  5. マルチメディアタイマーなので高精度!

以下が、本ラッパークラスExclusiveMMTimer だ。

#pragma once
#include <iostream>
#include <functional>

#include <windows.h>
#include <mmsystem.h>

class ExclusiveMMTimer{
  //
  // Exclusive Multi-Media-Timer Wrapper    (C) 2009 nursmaul
  // version 1.0
  //
  // CB concept: void cb( ExclusiveMMTimer* mm )
  //
  // interval ごとに、別スレッドにてcbを呼びます。
  // 前回のcb呼び出しが終わらないうちに、また別のスレッドで
  // 次のcbが呼ばれないように、排他制御をサポートしています。
  //
  // このため、cbの処理の一番最後(cbでPostMessageしたならそのPostMessageの
  // ハンドラ処理の最後)において、本クラスのReleaseExclusive() を呼ぶようにします。 
  //
  //#include <windows.h>
  //#include <mmsystem.h>
  //#pragma comment( lib, "winmm.lib" ) と、
  // LONGLONG ExclusiveMMTimer::m_exclusive_counter;
  // を、CPPコードのどこかに挿入して下さい。
  //
protected:
  ExclusiveMMTimer( UINT interval )
    :m_interval( interval ), m_elapsed(0), m_last(0)
  { m_exclusive_counter=0; }
public:
  virtual ~ExclusiveMMTimer( void ){}
  template< class T >	static ExclusiveMMTimer* Create( T cb, UINT interval, UINT resolution ){ // ヘルパー関数だよ
    return new ExclusiveMMTimerSub< typename T >( cb, interval, resolution ); }
  static ExclusiveMMTimer* Create( UINT interval, UINT resolution );
  static LONGLONG ReleaseExclusive( void ){ return ::InterlockedDecrement64( &m_exclusive_counter ); }
  virtual void CallBack( void )=0;
  const LONGLONG Elapsed( void ) const { return m_elapsed; }
  const LONGLONG ElapsedStep( void ){ LONGLONG tmp = m_elapsed-m_last;  m_last = m_elapsed; return tmp;  }
public:
  static LONGLONG	m_exclusive_counter;// 排他処理用
  UINT		m_timer_id;
  UINT		m_interval;
  LONGLONG	m_elapsed;
  LONGLONG  m_last;
};
template< class CB > class ExclusiveMMTimerSub: public ExclusiveMMTimer
{
public:
  ExclusiveMMTimerSub( CB cb, UINT interval, UINT resolution )
    :m_cb( cb ), ExclusiveMMTimer( interval )
  {
    m_timer_id = ::timeSetEvent( interval, resolution, 
      (LPTIMECALLBACK)ExclusiveMMTimerSub::lc_timerCB, (DWORD_PTR)this, TIME_PERIODIC );
  }
  ~ExclusiveMMTimerSub( void ){ ::timeKillEvent( m_timer_id ); }
  void CallBack( void ){ m_cb( this ); m_elapsed += m_interval; }
  static void CALLBACK lc_timerCB( UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2){
    ExclusiveMMTimer* stb = reinterpret_cast<ExclusiveMMTimer*>( dwUser );
    if( ::InterlockedExchangeAdd64( &stb->m_exclusive_counter, 1 ) ==0 ){ stb->CallBack(); }
    return;
  }
private:
  CB			m_cb;
};


使い方は以下の通り。

#include "exclusivemmtimer.h"

#pragma comment( lib, "winmm.lib" )
LONGLONG ExclusiveMMTimer::m_exclusive_counter;

// タイマーのコールバック定義
class MyCallback{
public: MyCallback( void ){}
public: void operator()( ExclusiveMMTimer* mm ){
      // (事例1)
      //経過時間を出力表示
      std::cout<<"hi"<<mm->m_elapsed<< std::endl;

      // (事例2)
      // 本タイマーは、別スレッド処理として呼び出されるので、
      // 例えばWindowsのビューなどに定期描画更新をかけたい場合
      // などは、ここの時点で、以下のように処理をポストするとよい。
      // _v->PostMessage( WM_PAINT, 0, 0);// 描画
 
      // (事例3)
      // 排他処理完了を通知
      //(これを忘れると次のインターバルでCBに飛んでこない)
      ExclusiveMMTimer::ReleaseExclusive();
    } 
};

int main( void )
{
  ExclusiveMMTimer* mm = ExclusiveMMTimer::Create( MyCallback(), 1000, 1 );	
  while( mm->m_elapsed<5000 );// 5秒経ったら終了
  delete mm;
  return 0;
}