質問者
Flash中のボタンとSendInputの関係

質問
-
この記事はEmbarcaderoフォーラムでも投稿しています。同じ目的で双方の方々にご相談した経緯がありますので、双方に投稿させていただきました。
あるFlashを含むWebページの自動操作を行う際の、SendInputのマウスボタン操作が反応しない問題があることを、フォーラムで提起しました。色々試行錯誤した結果、一つの解決策を見出したので報告します。解決策が他にあるかどうかや、この方法が最善であるかどうかはわかりません。なんとなく泥臭い方法です。
Flashplayer 中のDropDownListのList展開後のメンバー選択クリックには、待ち時間が必要のようで、その待ち時間稼ぎにために通常のSleep()関数では、SendInputのマウスボタンコマンドが何故か反応せず、これに対してプロセス自体をi一時終了し、一定時間後タイマーイベントで再開するようにすれば、SendInputが反応した(200~300ms)。
この現象は、Sleep関数ではプロセスが開放されず、Flashのイベント処理に十分な時間が割り当てられないためと考えられる(想像)。Sleep関数の間砂時計になることからもこれを示唆するようだ。
このような現象が生じるボタンは他にも1箇所あった。このため都合2個のタイマーを設定した(1個でもできるだろうが、簡単のため2個おいた)。
マウス移動はSendInputでもSetCursorPosでも同じでした。
以下のコードはC++BuilderXE2ですが、vc++2010でも考え方は同じと考えられます。主要部だけの抜粋。void TForm1::page3proc()
{
int x,y, ix,iy,n,i;
// DropDownListボタンまでの処理SetCursorPos(x,y); // DropDownList ボタン
// mousemove(x,y);SetCursorPos(ix,iy); // List Item を選択する位置
// mousemove(ix,iy);Timer1->Interval=300; //タイマーイベントまでの時間ms
Timer1->Enabled=true;
timpos=1;}
void TForm1::timret1(void)
{
int x,y, n,i;
int sx,sy;
int mx,my;
int selx,sely;mouseclick(); // List Item select ボタン
// 次の処理
}
// タイマーによる時間待ち
void __fastcall TForm1::ontimer1(TObject *Sender)
{
if(timpos==1) {
timret1();
Timer1->Enabled=false;
}
}
- 編集済み sige2 2012年9月3日 14:57
すべての返信
-
ここに書いておくべきだったです。そうですWebページはCppWebBrouserで同じアプリのFormに貼り付けています。
まだSleepの内部動作を調べていないですが、そういうことですか。動作をみていると、その感じが伺えました。Flashの動作がどうなっているかとも関係するのでしょうね。
windowsMessageループというかキューですね。formのキューは1個で、Sleepの動作は、プロセスを占有してしまうのでしょうね(まだ調べていません)。flashとキューが共用であっても、時系列的に処理されるのであれば、できそうに思いますが、そうでないところから、flashは何か独立の系列で動作するような要素があるのでしょうか。(想像ですみません)
Sleep関数で、wait中はプロセスを開放し、終了後再開するようなマルチスレッド向けのSleepfreeのような関数があればよいのですが、ちょっと探した範囲ではありませんでした。
- 編集済み sige2 2012年9月3日 15:20
-
メッセージループというのは Windows プログラミングの世界では昔からよく使われる言葉・概念・仕組みと言えるものです。
もし、ご存知でないのであれば、適当にとらえるのではなく、きちんと調べてください。(GetMessage TranslateMessage DispatchMessage など)Sleep はスレッドを完全に止めます。メインスレッドでそれを実行すると、根底にあるメッセージループが動かないので、メッセージループが回ることが前提となっている処理(タイマー、描画処理、マウス入力、キーボード入力など)は動きません。
また、長時間(最近は数秒)、メッセージを受け取らない状態が続くと、Windows から「応答なし」とみなされます。Flash がどのような実装になっているかは知るところではないですが、メッセージループが回らないとダメな何かがあるんでしょう。(タイマーなど)
ちゃんとメッセージループが動くような仕組みにしなきゃいけないだけのことです。
(タイマーで続きを実行するのは、次のタイマー到達までメッセージループが回るので、問題を解消するのでしょう)// RAD ツールな C++ とはいえ、C++ で作る以上は、メッセージループを知っておいて欲しい気もする。
- 編集済み AzuleanMVP, Moderator 2012年9月3日 15:44
-
メッセージループの中にキューがあるのですね。解釈を間違っていました。Windowsのwindow操作の基本となるおおよそのメッセージループの概念はわかっているつもりですが(今回の作業で知った)、Sleepの概念を正確に理解していませんでした。今回わかりました。Flashは仰せのようにメッセージループが回っている何らかの必要性があるのでしょうね。作業中は用語としては正確には捉えていませんでしたが、動作からなにかそのような要因があるのではと推察していました。それで解決策を考えた。
メッセージループを止めないSleepがあればよいのですが・・・。マルチスレッド(マルチプロセス?)で作ればよいのかも知れませんが深入りになりそうなのでトライしませんが。
-
私なら、動作を情報化します。
したいことは、「マウス カーソルを動かす。(X座標, Y座標)」「キーボード入力をする。(文字列)」「マウス ボタンを押し込む。(ボタン)」「押し込んでいるマウス ボタンを放す。(ボタン)」の4つだと思います。それぞれの実行後に「tミリ秒待つ」を入れ、こんな感じ。
enum 動作 { カーソル位置, キー入力, ボタン押す, ボタン放す, } class コマンド { 動作 動作; object[] 引数; int 待ち時間; } List<コマンド> コマンドリスト = new List<コマンド>() { // リストを選択する new コマンド(動作.カーソル移動, new object[] { 100, 100 }, 1000), new コマンド(動作.ボタン押す, null, 1000), new コマンド(動作.カーソル移動, new object[] { 90, 120 }, 1000), new コマンド(動作.ボタン放す, null, 1000), // 選択後の動作 }; void コマンド実行() { foreach (コマンド cmd in コマンドリスト) { switch (cmd.動作) { case 動作.カーソル移動: PInvoke.SetCursorPos((int)cmd.引数[0], (int)cmd.引数[1]); break; } Thread.Sleep(cmd.待ち時間); // oops! } }
コードは C# をイメージしていますが、C++ Builder でも考え方は同じです。
Sleepの動作は、プロセスを占有してしまうのでしょうね(まだ調べていません)。
Sleep の仕事は、スレッドに仕事をさせないことです。プロセスを占有することはありません。むしろその逆です。この説明はAzuleanさんの「スレッドを完全に止める」と相反するように見えますが、スレッドとプロセスが違うということに気がつけば、納得できると思います。
Jitta@わんくま同盟