(2015年11月1日更新:以下、lc_timerCBの引数後ろ三つを、DWORDからDWORD_PTRへ修正。timeSetEventの第三引数が正しくreinterpret_castできないことがあったため)
Win32のnative環境で高精度タイマーと言えばマルチメディアタイマーだが、今回はこのマルチメディアタイマーのAPIを知らなくてもすぐにタイマーが利用できるようなラッパークラスを作成したのでメモ。
以下のような利点がある。
- コールバックとしては関数のみでなく、関数オブジェクトも指定することができる。どちらの場合でもCALLBACK接頭辞は必要ない。
- なので各種関数アダプタを使えば、つまり任意のインスタンスのメソッドをコールバック関数として指定することもできる。
- マルチメディアタイマーAPIを知らなくてもすぐ使える。
- 排他処理が組み込み済みなので、あるコールバック処理が終わらないうちに次のコールバックが始まることはない。
- マルチメディアタイマーなので高精度!
以下が、本ラッパークラス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; }