SetTimerを使用して一定時間後にコールバック関数を呼ぶには?


ナル  2010-01-07 19:20:22  No: 71222

開発環境:VS2008 / MFC / VC++ です。

ある画面を持たないライブラリが、一定時間(1秒)後にメンバ変数であるBOOL値をFALSEに
変更したいのですが、SetTimerの使い方が分かりません。

void ClassName::FuncA()
{
  SetTimer( NULL, 0, 1000, (TIMERPROC)TimerProc );
  //第1引数:画面を持たないのでNULL
  //第2引数:第1引数がNULLの場合、意味を持たないらしいので0
  //第3引数:1000ミリ秒でタイムアウト
  //第4引数:1000ミリ秒後に呼ばれるコールバック関数
}

void CALLBACK ClassName::TimerProc( HWND hwnd , UINT msg, WPARAM wp, LPARAM lp )
{
  m_val = FALSE;
  //1秒後にメンバ変数をFALSEにする。
}

私の意図としては、FuncAでタイマをSetし、1秒経過後にコールバック関数のTimerProcが呼ばれて、
m_valにFALSEを代入するようにしたいのですが、下記のコンパイルエラーが出てしまいます。

「error C2440: '型キャスト' : 'overloaded-function' から 'TIMERPROC' に変換できません。」

どのようにすればよいでしょうか?  もしくは、ほかに良い方法がありましたら、ご教授ください。
よろしくお願いいたします。


maru  2010-01-07 20:10:12  No: 71223

要するにClassName::TimerProcはTIMERPROC型じゃないと言っている。
TimerProc関数をクラスメンバーから外す(グローバル関数にする)か、静的メンバー関数(static)にする。


maru  2010-01-07 20:14:11  No: 71224

よく見たら'overloaded-function' っていってるねぇ。
ClassNameは何から派生しているの?その定義によって答えが違うかも。


ryo  2010-01-07 21:19:12  No: 71225

WPARAMとLPARAMをやめて
UINT_PTRとDWORDにしてみたらどうかな?


maru  2010-01-07 22:16:07  No: 71226

ClassNameが画面を持たないと言っているので
SetTimer( NULL, 0, 1000, (TIMERPROC)TimerProc );
は::SetTimer関数のはず。とすると、TimerProc関数は
VOID CALLBACK TimerProc(
  HWND hwnd,         // ウィンドウのハンドル
  UINT uMsg,         // WM_TIMER メッセージ
  UINT_PTR idEvent,  // タイマの識別子
  DWORD dwTime       // 現在のシステム時刻
);
の形になっている必要があり、クラスに属することが出来ない(グローバル関数にする必要がある)。
従ってこのままでは、或るクラスの或るインスタンス(オブジェクト)のメンバー変数である、
m_valにFALSEを代入することは不可能。

そこで、ひと工夫。
タイマー関数はSetTimer側で指定するのでクラスと特定できる。
インスタンスはタイマー番号とインスタンスのマップを作成しておくことで特定する。
具体的に書くと、

class ClassName
{
//以下を追加
private:
// タイマー番号とインスタンスアドレスのペア
  struct tagTimerNumberAndInstance
  {  INT_PTR    numberOfTimer;
    ClassName*  pInstance;
  };
  static tagTimerNumberAndInstance  m_TimerNumberAndInstance;
public:
  static void CALLBACK TimerProc(
    HWND hwnd,         // ウィンドウのハンドル
    UINT uMsg,         // WM_TIMER メッセージ
    UINT_PTR idEvent,  // タイマの識別子
    DWORD dwTime       // 現在のシステム時刻
  );
}

// ここからソースファイル側
ClassName::tagTimerNumberAndInstance  ClassName::m_TimerNumberAndInstance = {0};

void ClassName::FuncA()
{
  m_TimerNumberAndInstance.numberOfTimer = ::SetTimer(0, 0, 1000, TimerProc);
  if (m_TimerNumberAndInstance.numberOfTimer)
  {
    m_TimerNumberAndInstance.pInstance = this;
  }
}

VOID CALLBACK ClassName::TimerProc(
  HWND hwnd,         // ウィンドウのハンドル
  UINT uMsg,         // WM_TIMER メッセージ
  UINT_PTR idEvent,  // タイマの識別子
  DWORD dwTime       // 現在のシステム時刻
)
{
  m_TimerNumberAndInstance.pInstance->m_val = FALSE;
  ::KillTimer(0, m_TimerNumberAndInstance.numberOfTimer);  // タイマーを停止
  m_TimerNumberAndInstance.numberOfTimer = 0;
  m_TimerNumberAndInstance.pInstance = 0;
}

複数のインスタンスから同時にタイマーを使用するんだったらタイマー番号とインスタンスアドレスのペアは複数扱えるようにしておく必要がある。

イベント等を使えばもっとスマートやり方がありそうだけどSetTimerを使うとこんな感じかな。


仲澤@失業者  2010-01-07 22:57:08  No: 71227

static メンバー経由でもできますね。
ただしインスタンスは1こしか許されないけど(^^;)。

class Freq
{
protected:
    UINT_PTR m_Timer_ID;  // タイマーID
public:
    //  タイマーのの開始
    void TimerStart()
    {
        // static のタイムアウト関数にthisを渡す
        f_TimeOut(( HWND)( -1), 0, ( UINT_PTR)this, 0);
        //  タイマーを開始する
  m_Timer_ID = ::SetTimer( NULL, 0, 1000_sec, ( TIMERPROC)f_TimeOut);
    }
    // static のタイマーコールバック
protected:
    static VOID CALLBACK f_TimeOut(
        HWND     hwnd,
        UINT     uMsg,      // WM_TIMER メッセージ
        UINT_PTR idEvent,  // タイマの識別子
        DWORD    dwTime)  // 現在のシステム時刻
    {
        static Freq *    st_Instance;
        if( ( int)hwnd == -1){
            st_Instance = ( Freq *)idEvent;
            return;
        }
        st_Instance->Inst_TimeOut(); // インスタンスのタイマーコールバックを呼ぶ
    }
    // インスタンスのタイマーコールバック
    void  Inst_TimeOut()
    {
           // ここに来る
    }
};


subaru  2010-01-08 02:47:17  No: 71228

MFCだからあまり関係ないかもしれないけど、
SetTimerだとコールバック関数が呼び出し元のスレッドで
呼ばれるので同期の問題は考えなくてもよいけど、
メッセージループが回っていることが前提になっています。

ウインドウを持たないコンソールアプリでも使いたいのであれば
素直に別スレッドを作成してバックグラウンドで1秒待機するのがよいかと思います。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加