開発環境: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' に変換できません。」
どのようにすればよいでしょうか? もしくは、ほかに良い方法がありましたら、ご教授ください。
よろしくお願いいたします。
要するにClassName::TimerProcはTIMERPROC型じゃないと言っている。
TimerProc関数をクラスメンバーから外す(グローバル関数にする)か、静的メンバー関数(static)にする。
よく見たら'overloaded-function' っていってるねぇ。
ClassNameは何から派生しているの?その定義によって答えが違うかも。
WPARAMとLPARAMをやめて
UINT_PTRとDWORDにしてみたらどうかな?
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を使うとこんな感じかな。
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()
{
// ここに来る
}
};
MFCだからあまり関係ないかもしれないけど、
SetTimerだとコールバック関数が呼び出し元のスレッドで
呼ばれるので同期の問題は考えなくてもよいけど、
メッセージループが回っていることが前提になっています。
ウインドウを持たないコンソールアプリでも使いたいのであれば
素直に別スレッドを作成してバックグラウンドで1秒待機するのがよいかと思います。
ツイート | ![]() |