OnIdle が実行されない – プログラミング – Home

通知
すべてクリア

[解決済] OnIdle が実行されない


aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200402/04020059.txt
の関連です。よろしくお願いします。

タスクトレイに常駐し、これをクリックすると表示されるポップアップメニューからす
べての操作を行うアプリを作っています。
ユーザーインターフェイスとしてのウィンドウを必要としない(メッセージターゲット
としてのみ使用する)ので、Win2k 以降の新機能であるメッセージ専用ウィンドウとい
うのを使ってます(CAxWnd の CreateEx の際に、親ウィンドウを HWND_MESSAGE にして
います)。

このせいかどうかはわかりませんが、OnIdle が常時実行されてくれません。
アプリが実行されている間、絶えず実行していてほしいのですが、途中で止まってしま
い、タスクトレイアイコンをクリックするなどの操作を行わないと続行されません。

コードを追ってみたところ、CWinThread::Run 内で、PeekMessage でメッセージがある
ことを確認してから PumpMessage を呼んでいるにも関わらず、PumpMessage 内の
GetMessage でメッセージを待って止まってしまっているようです。

これは何か解決方法があるのか、メッセージ専用ウィンドウを使っていることが原因な
のか、それとも OnIdle に関しての理解が間違えているのか、もし手がかりをご存知で
したら、どうかご教示お願いいたします。

なお、以下が OnIdle 内のコードです。
概要としては、あるウィンドウの存在を常に監視し、現在自アプリが有効か無効か、有
効な場合に対象ウィンドウが存在するかしないか、をメインウィンドウに通知していま
す。

BOOL CAxApp::OnIdle(LONG lCount)
{
m_hwnd = ::FindWindow( m_strClass, NULL );
if( m_bEnabled )
{
if( m_hwnd )
{
m_status = status::Enabled;
}
else
{
m_status = status::NotFound;
}
}
else
{
m_status = status::Disabled;
}

static_cast< CAxWnd * >( m_pMainWnd )->SetStatus( m_status );

return CWinApp::OnIdle(lCount);
}


引用
トピックタグ
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

長くなるので書き込みを分割します。
今回作っているのは、以前 SDK で作ったものの MFC 版リメイクです。
SDK 版のメッセージループは以下のようになっていて、期待どおりに動作してくれまし
た。

while( TRUE )
{
if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
{
if( msg.message == WM_QUIT )
{
break;
}

TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
g_hwnd = FindWindow( g_szWndClass, g_szWndCaption );
if( g_hwnd != NULL )
{
if( g_bEnabled )
{
// アプリが有効なときの処理
}
}
else
{
if( g_bEnabled )
{
// ウィンドウが見つからないときの処理
}
}

if( ! g_bEnabled )
{
// アプリが無効なときの処理
}
}

Sleep( 1 );
}


返信引用
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

追記

・SDK 版でも、ウィンドウはメッセージ専用でした。
・環境は前スレ同様、VC++.NET 2003 + WinXP Pro、MFC 使用です。


返信引用
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

…CAxApp::OnIdle で無条件に TRUE を返せばいいだけのような気がしてきた。


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

CWinThread::Runのソースを読んで見ましたけれど、
これだとメッセージが無い時でもPumpMessageに行っちゃいますね。

で、メッセージ専用ウインドウの説明にブロードキャストメッセージは受け取らない
って言うのがあるんですが、アイドルメッセージってブロードキャストだったり
しますかねぇ。

OnIdleはある程度アイドル処理をしたら抜けるようにFALSEを返すので
結果的にメッセージがつみあがって無くてもループを抜けると思います。
で、

一旦抜けてもブロードキャストメッセージでアイドルメッセージが来るので
PumpMessageを抜けて云々と言う作りではないかと思います。
コードを見た感じだとアイドルメッセージが二発連続で来るか、
他のメッセージがきて、アイドルメッセージが来ないとOnIdleを呼ぶところに
行ってくれない気はしますけれど。


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

うわっ、早とちり。

アイドルメッセージってのは無くて、
WM_MOUSEMOVEかWM_NCMOUSEMOVEが来ていて、かつ一つ前のメッセージから内容が
変わっているか、WM_PAINTでもWM_SYSTIMERでもないメッセージがカレントのメッ
セージで判断してますね。

ブロードキャストメッセージの線ではないのかなぁ。


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

思うんですけれど、MFCのこの辺の作りってもしかしてメッセージ専用ウインドウを
考慮に入れて作られていないような気がしますね。

この作りって何も操作して無くても何がしかのメッセージが来る事を前提にして
いるような気がしますし。それがブロードキャストで送られてきているものかどうか
はわかりませんけれど。
APIで作成している方はOnIdleの処理はそもそも無いわけで、
そう考えると元々メッセージ専用ウインドウを想定していないって言うか。


返信引用
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

レスありがとうございます。

>CAxApp::OnIdle で無条件に TRUE を返せばいいだけのような気がしてきた。
こういうことはしちゃダメだそうです。
http://www.microsoft.com/japan/msdn/library/default.asp?
url=/japan/msdn/library/ja/vccore/html/_core_tips_for_improving_time.2d.critica
l_code.asp

>これだとメッセージが無い時でもPumpMessageに行っちゃいますね。
OnIdle が FALSE を返すと、メッセージがなくても PumpMessage に行っちゃうんです
ね。

>アイドルメッセージってのは無くて、
WM_KICKIDLE ってのがありますね。アイドル状態に入ることを通知するメッセージらし
いです。

>WM_MOUSEMOVEかWM_NCMOUSEMOVEが来ていて、かつ一つ前のメッセージから内容が
>変わっているか、WM_PAINTでもWM_SYSTIMERでもないメッセージがカレントのメッ
>セージで判断してますね。
IsIdleMessage の説明を見ると、WM_MOUSEMOVE、WM_NCMOUSEMOVE、WM_PAINT、
WM_SYSTIMER 等は頻繁に発生するメッセージなので、それらの処理が終わるたびに
OnIdle が呼ばれてアイドル処理を行うことによって生ずるパフォーマンスの低下を防い
でいるようですね。

>メッセージ専用ウインドウを考慮に入れて作られていない
OnIdle の説明によれば、
>このメンバ関数は、ユーザー インターフェイス スレッドでだけ使用されます。
あやややや…
CWinApp::OnIdle なんだからユーザーインターフェイススレッドかワーカースレッドか
と言えば、MFC の分類上は前者。でも今回のケースはユーザーインターフェイスがな
い。
ということは
>この作りって何も操作して無くても何がしかのメッセージが来る事を前提
ということなのかもしれませんねぇ。

#でもスレッド面倒くせぇなぁ

ところで、この件と関係あるのかないのかわかりませんが、時々 0xCxxx あたりのメッ
セージを何種類か受け取っているようです。これって RegisterWindowMessage で登録さ
れたメッセージですよね。
何やってんだろうなぁ…


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

>>アイドルメッセージってのは無くて、
>WM_KICKIDLE ってのがありますね。アイドル状態に入ることを通知するメッセージらし
>いです。

あったんですねー。アイドル突入メッセージ(何か変(^^;)
これがブロードキャストメッセージだったりするのだろうか?
これがブロードキャストメッセージなら今の動きもわかるような。
いまいちな話では有りますけれど、
WM_TIMERを定期的に発生させるってのはだめですかねぇ。
これならウインドウハンドル狙い撃ちに出来るからブロードキャストに
ならないのではないかと思うのですけれど。
姑息な手ですね。(^^;


返信引用
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

>これがブロードキャストメッセージだったりするのだろうか?
ブロードキャストポストしてる場所は見つからないですね。というか、スレッドのメッ
セージキューが空になったときにポストされるメッセージなので、すべてのトップレベ
ルウィンドウで受信されてしまうブロードキャストメッセージでは都合が悪いかと。

>WM_TIMERを定期的に発生させるってのはだめですかねぇ。
その手は考えてました。
でも、前レスのURLの所に

>OnIdle を強制的に繰り返し呼び出すために、誤ったメッセージを生成したり、
>OnIdle をオーバーライドして常に TRUE を返したりしないでください。スレッドをス
>リープ状態にできなくなります。この場合も、タイマや独立したスレッドを使用する
>方が適切です。

と。
まぁ、誤ったメッセージとは言えないと思うんですが、本来不必要なメッセージですし
ね。

やはり
>何も操作して無くても何がしかのメッセージが来る事を前提にしている
これなんでしょうかね。
>このメンバ関数は、ユーザー インターフェイス スレッドでだけ使用されます。
この件もありますし。
UIスレッドなら、自アプリを何も操作しなくても、そう長時間にわたって何もメッセー
ジが来ないっていうケースはあまりないかと思われますので。

CWinThread::Run をオーバーライドして、SDK 版のメッセージループの再現に挑戦した
いと思います。それが難しいようならスレッド化で。

ありがとうございました。
また行き詰ったらよろしくお願いします。


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

>>これがブロードキャストメッセージだったりするのだろうか?
>ブロードキャストポストしてる場所は見つからないですね。というか、スレッドのメッ
>セージキューが空になったときにポストされるメッセージなので、すべてのトップレベ
>ルウィンドウで受信されてしまうブロードキャストメッセージでは都合が悪いかと。

ここだけに反応しておきますと、
これはOSがブロードキャストしているのではないかと私は想像しています。
一定期間でこれを出す事でアイドルが長期間呼び出されなくなる事を抑止しているのでは
と考えました。実際問題として通常のアプリと言えど、マウスの移動もキーボードの入力
もないという状況は考えられると思うので。
ですから、OSが発行しているブロードキャストメッセージが受けられないとアイドル処理
に問題が出てしまう。ということで辻褄が合うのではと。


返信引用
aetos
(@aetos)
Noble Member
結合: 6年前
投稿: 1480
Topic starter  

あー…なんか話が食い違っちゃってますかね?
WM_KICKIDLE は「メッセージキューが空になったときにポストされるメッセージ」らし
いので、スレッドごとにポストされるタイミングは違いますから、ブロードキャストさ
れることはないと思います。
ブロードキャストしちゃったら、すべてのUIスレッドが同じタイミングで(キューにメ
ッセージがあろうとなかろうと)アイドル処理に入ってしまうのではないかと。
#実際にはこいつは ON_UPDATE_COMMAND_UI のトリガーのようにも思えますが

PATIOさんがおっしゃっているのは、
>WM_MOUSEMOVEかWM_NCMOUSEMOVEが来ていて、かつ一つ前のメッセージから内容が
>変わっているか、WM_PAINTでもWM_SYSTIMERでもないメッセージがカレントのメッ
>セージで判断してますね。
これらのメッセージのことでしょうか?

これらをトリガーとしてアイドル処理に入るのではなく、
>IsIdleMessage の説明を見ると、WM_MOUSEMOVE、WM_NCMOUSEMOVE、WM_PAINT、
>WM_SYSTIMER 等は頻繁に発生するメッセージなので、それらの処理が終わるたびに
>OnIdle が呼ばれてアイドル処理を行うことによって生ずるパフォーマンスの低下を防
>いでいるようですね。
だと思われますが。
こいつらはむしろ正反対で「アイドル処理に入らないように」しています。

ところで、定期的にアイドル状態にするためにOSがメッセージをポストするってのは
>OnIdle を強制的に繰り返し呼び出すために、誤ったメッセージを生成したり、
>OnIdle をオーバーライドして常に TRUE を返したりしないでください。スレッドをス
>リープ状態にできなくなります。この場合も、タイマや独立したスレッドを使用する
>方が適切です。
これに反しているんじゃないかな、と思ったり。


返信引用
PATIO
(@patio)
Famed Member
結合: 4年前
投稿: 2660
 

CWinThreadとかのソースを見ているとアイドル処理でFALSEが帰ってくると
メッセージが無くてもアイドルループから抜けてしまうので
その場合にアイドルループへ戻るには何らかのメッセージが必要なわけで、
これをどうにかするためにブロードキャストメッセージが来ているのではという
予想だったんですが、確かにそうなるとアイドルループが直ぐ終わってしまいますね。

アイドルループから抜けると、CWinThread::IsIdleMessageがTRUEを返さないと
フラグがONにならないため、メッセージループから抜けてもアイドルループに入らないと
思います。それでWM_PAINT、WM_SYSTIMER以外のメッセージが来たら一旦アイドルループ
に戻すためにこんな処理が必要なんだろうと考えていました。
WM_MOUSEMOVE、WM_NCMOUSEMOVEにしても通知座標が違っていれば、同じ扱いみたいです。
要はユーザーオペレーションとして有効なメッセージを処理したらアイドルループに
入れるようにするためのフラグをONにしているのだと思ったんです。
ただ、状況としてはUIがあるウインドウでもマウスの移動やキーボードの入力が全く無い
状態と言うのは想定できると思うのでメッセージ専用ウインドウの場合に引っかかった
部分を脱出するために何らかのメッセージがあるのではと予想しました。

確かにWM_KICKIDLE は違うみたいですね。狙い撃ちみたいですし。
そうなると、アイドルフラグがOFFの時にメッセージが無い状態から脱出するためには
やっぱり、ユーザーのオペレーション関連のメッセージが必要なのかなぁ。


返信引用

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました