トップ回答者
FindWindowEx、FindWindowが失敗する場合の対応方法を教えて下さい。

質問
-
煩わしいアプリケーション操作の自動化を目指して調査中です。その為に、アプリケーションをサブクラス化し注入したコードの中でウィンドウの検索を行っているのですが検索が失敗します。条件を指定しても、クラス名とウィンドウテキストを明示的に指定した場合に検索が成功しません。ウィンドウテキストとクラス名は、フックコード内でGetWindowText、GetClassNameを行って取得しファイルに書き出したものなので間違いは無いと思うのですが、思う様に検索が行えません。
FindWindowEx(
hwnd,
NULL,
"#32770",
"ファイルを開く"
);の様に使用しているのですが何か間違えている所は見当たりますでしょうか?更に、hwndの子ウィンドウなのにhwndにNULLを設定すると検索できたり、"ファイルを開く"を、""にして検索するとクラス名が"#32770"でもないウィンドウのウィンドウハンドルを取得したりと挙動が不安定です。
以上、どなたかアドバイス宜しくお願い致します。
回答
-
トップレベルウィンドウだからでは? ファイルダイアログみたいだし。
Spy++ などで hwnd のウィンドウの子ノードに存在してるのは確認しましたか?
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:35
-
(HWND)GetWindowLong(hWnd,GWL_HWNDPARENT)
で得られるのは確かに親ウインドウですが、ファイルDLGはWS_POPUPなので、
トップレベルに配置されます。
Hongliang さんの発言どおりに「SPY++で見れば」、トップレベルに配置されており、
かつ親がHWND_DESKTOPではないことが確認できるはずなのですが・・・(vv;)。
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:36
-
以下のコードはテストしたわけではないので参考です。誤りもあるかもしれません。
メモ帳などで試してみてください。
//-----クリップボードにUnicodeテキストをセットするくだり-------
BOOL res = ::OpenClipboard( 送信元HWND);//開く
::EmptyClipboard();//空にする
HANDLE h = ::GlobalAlloc( GHND | GMEM_SHARE, 確保サイズ);
WCHAR* ptr = ( WCHAR*)::GlobalLock( h);
::wcscpy( prt, L"何らかの文字列");// ptrに所定の文字列をコピーする
::GlobalUnlock( h);
::SetClipboardData( CF_UNICODETEXT, h);
::CloseClipboard();//----エディットにペースト----
::PostMessage( hwnd_edit, WM_PASTE, 0, 0);だめだったらMBCSに直してやり直してみてください。そのときはCF_TEXTです。
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:54
-
Windowsって使い方によっては不安定なのでしょうか・・・
内容を読んでいて感じたんですが、
Humpty-Dumptyさんがやろうとしているのは本来の道筋から外れた所で連携させようとしているから
色々問題が起こっているのだと感じます。
システム的に連携させる事を前提に各アプリを設計していれば、
今やろうとしているような方法をとる必要はなかったはずです。何を言いたいかというと「無理やり連携させようとしているわけだからうまく行かないケースがあっても
しかたないのでは?」という事です。
100%の動作を求めるなら連携を考慮に入れた設計にしておくべきだと思います。ユーザーオペレーションをなぞるような方法を取る場合、本来ならユーザーがオペレーションを
行なわないようなタイミングでイベントが起こる事もありえると思うのでそういう場合にうまく反応して
くれないというような事はあると思います。
その辺の部分の含めてきっちり問題ないタイミングでオペレーションをなぞったイベントを
起こせるのかと言う部分の話になると思うので不安定と言うのとは違うのではないかなぁと感じます。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:53
すべての返信
-
トップレベルウィンドウだからでは? ファイルダイアログみたいだし。
Spy++ などで hwnd のウィンドウの子ノードに存在してるのは確認しましたか?
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:35
-
(HWND)GetWindowLong(hWnd,GWL_HWNDPARENT)
で得られるのは確かに親ウインドウですが、ファイルDLGはWS_POPUPなので、
トップレベルに配置されます。
Hongliang さんの発言どおりに「SPY++で見れば」、トップレベルに配置されており、
かつ親がHWND_DESKTOPではないことが確認できるはずなのですが・・・(vv;)。
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:36
-
FindWindowEx()で子ウインドウを検索する場合、指定されたタイトル(キャプション)
を比較するためにGetWindowText()を使用するらしいのですが、この関数は、
自分以外のアプリケーションのタイトルは取得できないとあります。
この関数に失敗すると、FindWindowEx()も失敗に終わると考えられます。また、該当するウインドウが複数あった場合の動作の説明がありませんので、
その場合に失敗を戻すかもしれません。ひょっとすると、上記いずれかの状態に陥っているのかもしれません。
この仮説どおりだとEnumChildWindows()を使うしかないかもしれません。 -
FindWindowEx()で子ウインドウを検索する場合、指定されたタイトル(キャプション)
を比較するためにGetWindowText()を使用するらしいのですが、この関数は、
自分以外のアプリケーションのタイトルは取得できないとあります。
この関数に失敗すると、FindWindowEx()も失敗に終わると考えられます。
また、該当するウインドウが複数あった場合の動作の説明がありませんので、
その場合に失敗を戻すかもしれません。
ひょっとすると、上記いずれかの状態に陥っているのかもしれません。
この仮説どおりだとEnumChildWindows()を使うしかないかもしれません。
中澤さんの話を読んでいたら、おぼろげながら思い出しました。うろ覚えでたいへん申しわけないのですが、今から10年くらい前、やはり AutoCAD のファイルダイアログを外部から制御しようとして、FindWindowEx だとうまくいかず、EnumChildWindows を使ったらうまくいったような覚えがあります。
ソースコード(Delphi) を見れば一発で思い出せるんだろうけど、家帰ってもすぐに探し出せるかどうか(汗
ネット上でも議論した覚えがあるので、EnumChildWindows と AutoCAD で検索かけると、昔のリソースが残っていれば参考になる資料が出てくるかも知れません。
#なにせ10年以上も前の話なんで、役に立たないかも知れません。<(_ _;)>
ひらぽん http://blogs.yahoo.co.jp/hilapon/ -
アドバイス有難う御座います。
CreateProcessで作成したプロセスにフックプロシージャをインストールして、その中でウィンドウのサブクラス化を行っているので、プロセス境界を超えています。現に、GetWindowText関数は成功してファイル書き出しは行えています。また、Editボックスはダイアログ内に一つなので、いずれの状態も考えにくいです。
>EnumChildWindows()を使うしかないかもしれません。
結局の所、テキストとクラス名だけからではいかなる状況でも判断できるとまではいかないので、自分もそれしか無いかと考えています。メニューの様に一意な識別子位あってほしい物です。
-
> #なにせ10年以上も前の話なんで、役に立たないかも知れません。<(_ _;)>
出てきた・・・9年前でしたね。ちょうどこの頃、API 使って色々やってました。
当時は皆開発が本業じゃなかったので、かなりぐだぐだな意見が飛び交ってますが(汗http://izawa.web.infoseek.co.jp/cgi-bin/yybbs/32.html
http://izawa.web.infoseek.co.jp/cgi-bin/yybbs/33.html> ちなみに、FindWindowEx()が孫まで検索してくれることを期待していますか?。
> この件に関してもマニュアルには何の記述もありませんので、多分直下の子
> しか検索しないのではないでしょうか(未確認)。中澤さんの仰るとおり、FindWindowEx は子ウィンドウしか検索できず、EnumChildWindows で再帰的に子ウィンドウを検索して操作していたのを思い出しました。
ボタンやコンボボックスは同じクラスやIDになってたので、キャプションやサイズから判定して操作していましたね。
ひらぽん http://blogs.yahoo.co.jp/hilapon/ -
-
意味が分かりづらかったでしょうか、前回の発言のコードは
エディットに'0'、'1'の順番で文字入力があったように
エミュレートしています。従って当該のhwnd_editが真にEditコントロール
であればそのコントロールに"01"と表示される予定です。
さて、そうならなかった場合1.hwnd_editはエディットコントロールでない。
2.hwnd_editはエディットコントロールであり、文字もセットされたが
その後、誰かがもう一度WM_SETTEXTして、空欄にしている。などが考えられます。
上記の何れの場合でもWM_SETTEXTもクリップボード経由も効果がありません。 -
以下のコードはテストしたわけではないので参考です。誤りもあるかもしれません。
メモ帳などで試してみてください。
//-----クリップボードにUnicodeテキストをセットするくだり-------
BOOL res = ::OpenClipboard( 送信元HWND);//開く
::EmptyClipboard();//空にする
HANDLE h = ::GlobalAlloc( GHND | GMEM_SHARE, 確保サイズ);
WCHAR* ptr = ( WCHAR*)::GlobalLock( h);
::wcscpy( prt, L"何らかの文字列");// ptrに所定の文字列をコピーする
::GlobalUnlock( h);
::SetClipboardData( CF_UNICODETEXT, h);
::CloseClipboard();//----エディットにペースト----
::PostMessage( hwnd_edit, WM_PASTE, 0, 0);だめだったらMBCSに直してやり直してみてください。そのときはCF_TEXTです。
- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:54
-
こんにちは。
たまたま調べる必要があった個所と一部かぶってたので、少し調べてみました。
EditコントロールのウインドウスタイルにES_READONLY(読み取り専用属性)がついてると当然WM_CHARやWM_PASTEが効きませんがw
そもそもオープンファイルダイアログのファイル名のところに注目されているようなので、そんな可能性は除外するとして
(っていうかES_READONLYでもWM_SETTEXTやWM_GETTEXTはちゃんと効きますのでw)
また、(Set/Get)WindowTextとかだと別プロセスでは子ウインドウに対してはたしかうまくいかないはずですが、そんな可能性もWM_SETTEXTやWM_GETTEXT使えば問題ないので除外するとして…w
単にちゃんとウインドウハンドルが取れていないのでは(?)という感じがするのですが
仲澤さんの仰った「ファイルDLGはWS_POPUP」で、トップレベルがどんな場合でも確実に保証される、かつ 、 あくまで「別アプリ」のオープンファイルダイアログのファイルパス入力するEditコントロールのテキストを変更する、というのならば
例えばEnumWindows系を多段で使って特定する、といった風になるのかな・・・?
私はXP sp2ですが、概念的にはこんな感じでやったら別アプリからの変更は成功しました。
#include <tchar.h>
TCHAR tc[300];
BOOL CALLBACK EnumChildProc( HWND hw, LPARAM ){
::GetClassName(hw,tc,300);
if (_tcscmp( tc, _T("Edit" ) ) ) return 1;
::SendMessage(hw,WM_SETTEXT,0,(LPARAM)tc); //ここに来れればOK・・・のはず
return 0;
}
BOOL CALLBACK EnumThreadProc( HWND hw, LPARAM ){
::GetClassName(hw,tc,300);
if ( _tcscmp( tc,_T("#32770" ) ) ) return 1;
::SendMessage(hw,WM_GETTEXT,300,(LPARAM)tc);
if ( _tcscmp( tc, _T("ファイルを開く" ) ) ) return 1;
::EnumChildWindows( hw, EnumChildProc, 0 ); //ダイアログハケーン↑
return 0;
}
こんな感じの必要物を適当に用意しておいて、あとはどっかで
::EnumThreadWindows( ::GetWindowThreadProcessId( 目的のダイアログと同じスレッド内のウインドウハンドル ,NULL), EnumThreadProc, 0 );とかなんとかでも呼び出せば可能でした。
ただし、オープンファイルダイアログが複数発生中とかいう状況があり得るのなら、これだけでは不足ですw また、コストを考えればしょっちゅうアクセスするなら何か保存しておいて負担を軽減する、とかは状況に応じて・・・・w
とりあえず、Enum~Windows系を使って出来るかチェックしてみてください。
>仲澤さん
調べてみたらひとつ激烈に気になりましたのでちょこっと確認しておきたいのですがw
クリップボードとのデータの行き来そのものはUNICODEとANSIの互換性は自動的に保ってくれてるようですが
GetClipboardDataとかSetClipboardDataとかが CF_UNICODETEXT, CF_TEXT でしか変化できない…つまり汎用の定数がないとしたら
そしてかつクリップボード制御用関数を自分でつくっておいて使いまわす、としたら、どっちを使うかによって関数を別々に作っておくとかがベストなのでしょうか?
-
クリップボードの原則は
1.設定側の「善意」で提供された複数のフォーマットのデータの内
2.取得側の「能力」で取得できるフォーマットでペーストする
となります。どうするかはプログラマの決めることです。また、提示の例ですが、そのような手順で可能なのは承知しています。
Humpty-Dumptyさんの例はフックプロシージャ内の特定イベントで
ファイルDLGをサブクラス化した上で、かつ、そのサブクラス内から
又は、サブクラスのプロシージャを知っている他のアプリから、
エディットに文字列を設定しようとするもので、設定できるタイミング等が
微妙だと理解しています。以上の点で提示の例とはやや趣が異なるかもしれません。 -
Windowsって使い方によっては不安定なのでしょうか・・・
内容を読んでいて感じたんですが、
Humpty-Dumptyさんがやろうとしているのは本来の道筋から外れた所で連携させようとしているから
色々問題が起こっているのだと感じます。
システム的に連携させる事を前提に各アプリを設計していれば、
今やろうとしているような方法をとる必要はなかったはずです。何を言いたいかというと「無理やり連携させようとしているわけだからうまく行かないケースがあっても
しかたないのでは?」という事です。
100%の動作を求めるなら連携を考慮に入れた設計にしておくべきだと思います。ユーザーオペレーションをなぞるような方法を取る場合、本来ならユーザーがオペレーションを
行なわないようなタイミングでイベントが起こる事もありえると思うのでそういう場合にうまく反応して
くれないというような事はあると思います。
その辺の部分の含めてきっちり問題ないタイミングでオペレーションをなぞったイベントを
起こせるのかと言う部分の話になると思うので不安定と言うのとは違うのではないかなぁと感じます。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。- 回答としてマーク Humpty-Dumpty 2010年4月25日 6:53
-
うう~~むw
改めて読んでみて、状況がよく分かってないことに気付いたのですがw
えっと、今自分がいじれる側をAとして、設定されることになるファイルDLGを持ってるプロセス側をBとします。
このとき
Bはコードは「見れるorいじれる」のですか?
それができるならばわざわざ「Editコントロール」を介する必要がないか、もっと行くと「何らかの一括処理を行う結果が生じる」ならば、オープンファイルダイアログを使用する必然性すらないのでは…?と思うので、Bはいじれないしコードを見れないが、しかし複雑な処理を行っていてその機能を自分で作ることなしにどうしても使いたい、という状況ではないか、と私は考えたのですが
この通りだとすると、ファイルDLGをサブクラス化するという行動をするのも、Aが行う、ということになっているのでしょうか?
上記の方法でほとんどの場合は「可能ではあるか、あるとき列挙が失敗してもFALSEが返るだけでおおよそ危険はないとは思います」が、不確定要素がある中で消去法で考えたという意味合いもあるためこれらの事が明確になれば、もっといい方法が浮かび上がりそうな気がします。
-
七空白さん
> Bはコードは「見れるorいじれる」のですか?
> それができるならばわざわざ「Editコントロール」を介する必要がないか、もっと行くと「何らかの一括処理を行う結果が生じる」ならば、オープンファイルダイアログを使用する必然性すらないのでは…?(以下略)たぶん以下のスレッドから話が続いているのだと思います。
> Windowsって使い方によっては不安定なのでしょうか・・・
個人的な感想としては詳細な仕様が判らないため断定できませんが、Windows よりも むしろ FindWindowEx とかの方が不安定?もしくはこちらの意図しない挙動になっているような気がします。
私の経験上、また色々な記事を検索して思ったのですが、FindWindowEx とかよりも EnumChildWindows を使った方がいいように思えます。
ひらぽん http://blogs.yahoo.co.jp/hilapon/ -
読み返していて気が付いた事があるんですが、
そもそもファイルを開くのコモンダイアログのファイル名を入力するエディットコントロールが
>Spy++で確認したところ、DLG->Editで親ウィンドウがDLGとなっている様です。
なんて事があるんでしょうか?
色々SPYで調べてみましたが、古いタイプのダイアログでも問題のエディットコントロールはDLG->Combobox32->Combobox->Editになってます。
なんか違う物を相手にしているのではないかと言う気がしています。
御本人のPCだけ違うと言うのも変な話ですし。
もしくは問題のダイアログがコモンダイアログとは似て非なるものなのか。あと、ファイルを開くのコモンダイアログが別アプリでも開いている場合は多分区別がつかないと思います。
それについて承知の上でやっているなら問題は無いと思いますけれど。追記:
エディットコントロールを検索する時にウィンドウテキスト""で正解なんですかねぇ。
MSDNでFindWindowExを見てみると指定しないときは、NULLを渡すように書かれています。
空文字列を渡すのではなくてNULLポインタを渡すのが正解なのではないかと思うのですが。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。 -
お世話になっております。少し消化不良です。しばしお待ち下さい。
>NULLポインタを渡すのが正解なのではないかと
GetWindowTextの戻り値が空文字でしたので、同じ様に設定したところ上手く検索出来ています。NULLよりも空文字の設定の方が都合がよかったのでそちらを使っています。
現在、ダイアログボックスをサブクラス化したプロシージャの方にWM_COPYDATAを使用して受け取った文字列群を順に検索する様に書いていますが、WM_COPYDATAのパスのコードを有効にすると、ダイアログに接触した時点(クリックなど)でアプリケーションが落ちるという妙なバグに遭遇して困り果てています。
>あんまりHumpty-Dumptyさんがいない間に沢山投稿するのもあれかもなので
いえいえ、ご自由にご歓談下さい。
それでは、失礼いたします。
-
WM_COPYDATAでの文字列受け渡しは、単にSETTEXTやクリップボード経由と比べればそんなにはお勧めできないかもしれません。
これはUnicodeとANSIの違いをOSが吸収とかしてくれることがない(そのまま渡すだけ)ので自分で送信側と受信側のつじつまを合わせないと、文字化けや文字が欠けたりといったことが起こり得ます。
もちろん、そこさえ組んでしまえば長い文字列を渡しても問題はないでしょうが
その他にも、グローバルアトム(これもおすすめできない)を使う方法や
メモリマップドファイル (これは「ある資源を共有し頻繁に書き換える」といった用途にはおすすめできます。でも同期という点では最善にはならないかな ただしWM_COPYDATAとかだと頻繁な破棄の必要があると思うので、パフォーマンス的にはこっちのがいいかもしれません。)やパイプを使う方法なども考えられます。
ただ、それ以前に
(これは私自身知らなかったのですが)ファイルダイアログの"#32770" は2つあって、フックプロシージャ内の第1引数サイドはテキストが見えない方です。GetParentするとファイルを開くの方のハンドルが得られますが
http://msdn.microsoft.com/ja-jp/library/cc410977.aspx
この辺は大丈夫でしょうか?
あと関係ないけどCreateProcessW 側の第2引数ってconstやポインタだとまずいんですねw (なのでリテラルはアウト)
配列で確保してから渡すことで出来ましたが、無駄に苦戦してしまいましたw なんでこんな仕様なんだろうw
で、WM_COPYDATAですが
実際には実験といってもごちゃごちゃにならないように専用クラスにしてたりでもうちょい複雑ですが、意味的な要点だけ
サーバ側から(メンドイのでウインドウ探し出すまでは省略します)
//「ファイルを開く」の#32770のウインドウの子ウインドウを検索する関数内↓
BOOL CALLBACK EnumChildProc( HWND hw, LPARAM){
static TCHAR c[300];
GetClassName(hw,c,300);
if (!_tcscmp(c,_T("#32770"))) { //フックプロシージャに通じるウインドウ向け
COPYDATASTRUCT data={1, 6, "hello"}; //ANSI限定
SendMessageA(hw,WM_COPYDATA,(WPARAM)wnd,(LPARAM)&data); //ここで送信
return 0;
}
else if (_tcscmp(c,_T("Edit"))) return 1;
SendMessage(hw,WM_SETTEXT,0,(LPARAM)_T("You've got it."));
return 1;
}とかやって
クライアントの方が
#define IDD_CUSTOM 101
#define IDC_BUTTON1 1004
#define IDC_EDIT1 1015とか適当に数値作っといて
OPENFILENAMEをopとすると、3つのメンバは
op.Flags = OFN_FILEMUSTEXIST |CC_ENABLEHOOK|OFN_EXPLORER |OFN_ENABLESIZING |OFN_ENABLEHOOK|OFN_ENABLETEMPLATE;
op.lpfnHook = OFNHookProc;
op.lpTemplateName = MAKEINTRESOURCE(IDD_CUSTOM);こんな感じにして
リソースが
IDD_CUSTOM DIALOG DISCARDABLE 0, 0, 187, 94
STYLE DS_3DLOOK | DS_CONTROL | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS
FONT 9, "MS Pゴシック"{
DEFPUSHBUTTON "OK", IDOK, 80, 50, 50, 14
PUSHBUTTON "Button",IDC_BUTTON1,20,50,50,14
EDITTEXT IDC_EDIT1, 140, 50, 150, 40, ES_AUTOHSCROLL|WS_VSCROLL|ES_MULTILINE
LTEXT "",1119,0,0,186,40,WS_CHILD | WS_GROUP
}で
UINT CALLBACK OFNHookProc(HWND hdlg, UINT msg, WPARAM wp,LPARAM lp){
switch(msg){
case WM_COPYDATA:{
COPYDATASTRUCT* a=(COPYDATASTRUCT*)lp;
if (a->dwData!=1) return FALSE;
SetDlgItemTextA(hdlg, IDC_EDIT1, (char*)a->lpData);
}
return FALSE;・
・
・
とか試してみましたが、問題なく出来ました。
この辺の概要はOKでしょうか?
ここまでの内容が全部大丈夫だとすると
「文字列群を順に検索する」のアルゴリズムに問題があるのかもしれません。
-
お世話になっております。
少し上記の内容とは状況が違う様に思います。今現在取り扱っているアプリケーションは自分で手を加える事が出来ないアプリケーションである事が前提です。なのでGetOpenFileNameを使ってファイル名を取得しても使い道がありません。アプリケーションで定義されたダイアログボックスの呼び出しに応じてEditにファイルのフルパスを設定し、開くボタンを押すという操作を実現したいという要求です。
ダイアログボックスとはいえ結局はウィンドウのはしくれなので、メインウィンドウ同様の方法でフック出来る筈です。また同様の方法でサブクラス化も可能な筈です。どちらもここまでは出来たのですが、ダイアログボックスをサブクラス化するプロシージャの処理が何故か上手く動かないという状況です。
文字列の検索に問題が無い事は立証済みです。
-
>今現在取り扱っているアプリケーションは自分で手を加える事が出来ないアプリケーションである事が前提です。
なるほど、やはりそうでしたか
んじゃ2010年4月22日 8:18の投稿の私の読みは当たってはいたようですが、この情報自体は私は初耳ではないかと思いましたw
・・・私が見落としてるだけで、どっかに書いてありましたっけ?
特定の状況を除きダイアログボックスを普通のウィンドウと同等に扱う事も、もちろん可能と思いますが
過去の質問をいくつか見させていただきましたが、仮にすべての質問が同じ今問題となっているアプリについての話題であるとしても、何をどうやっているのかがほとんどコードで示されていないので、どのような手法によって実現しようとしている中で
> ダイアログボックスをサブクラス化するプロシージャの処理が何故か上手く動かない
のかが不明瞭です。うまくいっている、のならばある程度絞られるとは思いますが、うまくいっていないとなると莫大な可能性があります。エスパーじゃなければ最小限の手間で解決するのは困難ではないかとw
と、いうわけで
私がそれによって解法を提示できるという保証は全くありませんが、識者の方が答えられるように、ある程度のコードを提示されてはいかがでしょうか?
-
コードは全部張り付けるわけにいかないので、概要だけでは駄目ですか?
①WH_CALLWNDPROCフックをdllからSetWindowsHookExを使って呼び出す。
②CallWndProcプロシージャはGetWindowtextを使って目的のタイトルのウィンドウを捕獲出来た時だけサブクラス化を行う。
③サブクラス化はGetWindowLongとSetWindowLongを使ってウィンドウプロシージャを自分で用意したウィンドウプロシージャに入れ替える。
④自分で用意したプロシージャにはWM_ENTERIDLEを受け取るとWH_CALLWNDPROCフックをSetWindowsHookExを使って呼び出す様に書いてある。
⑤④で使用するフックプロシージャはGetWindowtextを使って目的とするオープンファイルダイアログを捕獲出来た時だけサブクラス化を行うように書いてある。
⑥サブクラス化はGetWindowLongとSetWindowLongを使ってウィンドウプロシージャを自分で用意したダイアログ用ウィンドウプロシージャに入れ替える。
⑦ダイアログ用ウィンドウプロシージャにはWM_COPYDATAで行う指示を書いてある。
⑧WM_COPYDATAで行う指示はdwDataが100の時だけ起動する様に書いてある。
症状:WM_COPYDATAの区画をコメントアウトすると正常にウィンドウ操作を続けられる。コメントアウトしない場合、WM_COPYDATAをサーバー側アプリから送信してもWM_COPYDATAの区画に何故か入らず処理が起動しなく正常にウィンドウ操作をつづけられる、もしくは、ウィンドウ操作を行おうとダイアログボックスファイル表示部をクリックしただけでアプリケーションが落ちる。
そもそも、ダイアログボックスのサブクラス化がサポートされていなくて出来ないという可能性もあるのではないかと思うのですが如何でしょうか?
-
>③サブクラス化はGetWindowLongとSetWindowLongを使ってウィンドウプロシージャを自分で用意したウィンドウプロシージャに入れ替える。
となると、そこがあれかもしれません。
dll使って別のから・・・とかは試せていませんが、とりあえずカスタムコモンダイアログ用のプロシージャの書き方に沿ってはいますでしょうか?
沿っていたとしても、少なくともそういう風に変化させると同じアプリ内から単純にやった、ということであってもおかしい挙動になった場合を確認しました。(これがただしくない書き方であるだけで回避法が存在する、かもしれませんが)
さきほどのコードに
UINT CALLBACK OFNHookProc(HWND hdlg, UINT msg, WPARAM wp,LPARAM lp){
switch(msg){
case WM_INITDIALOG:
SetDlgItemText(hdlg, IDC_EDIT1, _T("文字列を入力してください"));
SetWindowLongPtr(hdlg,GWLP_WNDPROC,(LONG_PTR)OFNHookProc2); //切り替え
return TRUE;・
・
・
UINT CALLBACK OFNHookProc2(HWND hdlg, UINT msg, WPARAM wp,LPARAM lp){
switch(msg){
case WM_COPYDATA:{
COPYDATASTRUCT* a=(COPYDATASTRUCT*)lp;
if (a->dwData!=1) return FALSE;
SetDlgItemTextA(hdlg, IDC_EDIT1, (char*)a->lpData);
SetWindowText(GetParent(hdlg),_T("WAWAWA"));
}
return FALSE;}
return OFNHookProc(hdlg,msg,wp,lp); //通常は元の処理をしてもらう
}という風な切り替えの流れを付加してやってみると、WM_COPYDATAの捕捉は成功するものの
なぜかダイアログの背景描画が機能しなくなりました。
ダイアログのサブクラス化というのは、もともと私が上記に示したようにOPENFILENAMEのメンバをいじって、起動時にそういうやつにする、というのは解説があるので大丈夫、あるいは通常の方法だと思うのですが、作った後でさらに変化させるのが正しいやり方かどうかは分かりません。ので、そういう意味に於いて
>ダイアログボックスのサブクラス化がサポートされていなくて出来ない
可能性は十分あるのでは?というのが現時点の知識内での私の考えです。
-
そうですか・・・ちなみに、試してみようと思うのですが、
#include <tchar.h>
TCHAR tc[300];
BOOL CALLBACK EnumChildProc( HWND hw, LPARAM ){
::GetClassName(hw,tc,300);
if (_tcscmp( tc, _T("Edit" ) ) ) return 1;
::SendMessage(hw,WM_SETTEXT,0,(LPARAM)tc); //ここに来れればOK・・・のはず
return 0;
}
BOOL CALLBACK EnumThreadProc( HWND hw, LPARAM ){
::GetClassName(hw,tc,300);
if ( _tcscmp( tc,_T("#32770" ) ) ) return 1;
::SendMessage(hw,WM_GETTEXT,300,(LPARAM)tc);
if ( _tcscmp( tc, _T("ファイルを開く" ) ) ) return 1;
::EnumChildWindows( hw, EnumChildProc, 0 ); //ダイアログハケーン↑
return 0;
}はLPARAMに設定したい文字列を渡す形にして表示できますか!?
-
-
こちらでは、表示されません。参考までにコードをのっけるとこんな感じです。
BOOL CALLBACK EnumChildProc1(HWND hWnd, LPARAM str){
GetClassName(hWnd,text,100);
if(strstr(text,"Edit")==NULL){
return 1;
}
GetWindowText(hWnd,text,100);
if(strstr(text,"")==NULL){
return 1;
}
FILE *fp;
fp=fopen("ここまできてるねーーー.txt","a");
GetClassName(hWnd,text,100);
fprintf(fp,"%s\n",text);
fclose(fp);
SendMessage(hWnd,WM_SETTEXT,0,str);
}BOOL CALLBACK EnumThreadProc1(HWND hWnd, LPARAM str){
GetClassName(hWnd,text,100);
if(strstr(text,"#32770")==NULL){
return 1;
}
GetWindowText(hWnd,text,100);
if(strstr(text,"ファイルを開く")==NULL){
return 1;
}
EnumChildWindows(hWnd,EnumChildProc1,str);
return 0;
}コードの中で、
EnumThreadWindows(GetWindowThreadProcessId(toplevelwindow,NULL),EnumThreadProc1,
MAKELPARAM("表示されました",NULL));OSはXPのsp3ですが、OSバージョンの違いによるものでしょうか???ちなみに、マルチバイト文字に設定してコーディングしています。理由とか想像付きますか?
-
もしも、もしもですよ?他の部分が正常だとして
EnumThreadWindows(GetWindowThreadProcessId(toplevelwindow,NULL),EnumThreadProc1,
MAKELPARAM("表示されました",NULL));を
EnumThreadWindows(GetWindowThreadProcessId(toplevelwindow,NULL),EnumThreadProc1,
LPARAM("表示されました"));と書き変えてうまくいったとしたら(w)
そのあとでMAKELPARAMのところにマウスカーソルを持って行って、右クリック→宣言へ移動などとすると
#define MAKELPARAM(l, h) ((LPARAM)(DWORD)MAKELONG(l, h))
となってるかと思いますが、ここでMAKELONGの定義をさらに見ると、上記コードが何を意味してるのかが分かるかもしれませんw
ちなみに、私の環境では
MessageBoxA(0,(char*)MAKELPARAM("表示されました",NULL),0,0);
というコードを書いたときに期待通りたった一行で見事に「アプリが落ち」ましたが、上記を修正してうまくいった場合は、逆に今うまくいかなかった理由はこれとまったく同じです。
-
このコードを見ただけでは不明な点はいくらかありますが、貼り付けがうまくいった、のならば、普通に考えるならEnumWindows系やWM_SETTEXT自体だけでそんなことになるとは考えにくいので、おそらく別の個所に原因がある、あるいは目的のダイアログがなんらかの特殊な内部処理を行っている可能性もあります。が
今回のMAKELPARAMのように、コードがなければこちらからは原因がほとんど特定できないような問題がもし他にあるというなら、それの見当をつけることはほとんどできません。
ひとまず私はしばらくお楽しみ(自分のアプリの方)のスレ・研究・調査・コーディングなどに傾倒したいのでw
後のことはどなたかお願いします(w)
-
七空白さん、ここまでお付き合い頂きまして有難うございます。アドバイス頂いた皆様も感謝です。
結局のところ可能な物を相手にしているのかどうなのかという事がいまだに解りません。仕様上(Win32のMSDN)は実現可能に見えてしまいます。が、実際の所、自分は動作させる事が出来ていません。
自分の行いたかった事は、手を加える事が出来ない他アプリの一連のGUI操作を別のアプリから行う事によるGUI操作の自動化です。その為にオープンファイルダイアログも操作する必要性がありましたが、ここが思う様にいきませんでした。
もしよろしければ、この課題に挑戦された方が他にいらっしゃいましたら、自分は出来た、自分は出来なかった等の結果だけでも教えて頂けませんでしょうか?
以上、ご協力宜しくお願い致します。
-
思うのですが、操作対象にしているプログラムの内容を全て理解した上で
行なっているのでは無いという事であれば、100%うまく行く方法を確定する事はできないと思います。
確かにフレームワークとして用意されている物の上で動いているわけですが、
細かい実装等に関しては極端な話実装する側でどうにでもなります。実装する側が自分のプログラムが他者にサブクラス化されて使われる事を
想定してプログラムを作成しているとは考えにくく、基本的に自アプリの仕様が
満たされていればOKという考え方の元に実装されているケースが多いと思います。
そうした状況の中でアプリ側が想定していない操作を外部から加えた場合に
うまく動作する保証は有りませんし、アプリを作成した側も保証しないでしょう。
疑いだしたら限が有りませんが、サブクラス化した事で潜在バグが顕在化する
ケースもありえますから操作対象がデバッグできない状況ではどうしようもない
のではないでしょうか。御本人の書き込みにあったダイアログ上のコントロールの構成の違いも気になります。
見た目が同じでも全く同じ物が使われているという保障にはなりませんから
単一の方法でいつもうまく行くかと言うとこれもかなり怪しいと思います。ある意味無理やり連携させようとしているわけなのでうまく行かないケースがあっても
不思議は無いというのが私の考えです。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。 -
自分の行いたかった事は、手を加える事が出来ない他アプリの一連のGUI操作を別のアプリから行う事によるGUI操作の自動化です。その為にオープンファイルダイアログも操作する必要性がありましたが、ここが思う様にいきませんでした。
もしよろしければ、この課題に挑戦された方が他にいらっしゃいましたら、自分は出来た、自分は出来なかった等の結果だけでも教えて頂けませんでしょうか?しばらく静観しておりましたが、また出てきました。以下、的外れな意見でしたらご容赦ください。
以前 「AutoCAD の外部操作」 にチャレンジし、実際にファイルダイアログや印刷ダイアログを操作していた立場から言わせて頂きます。もう販売しておりませんが、AD_Commander というシェアウェアを作っておりました。AutoCAD をプロセス外から監視し操作を行うアプリですが、連続操作の際、AutoCAD のバージョンによってはファイルダイアログが出てきますので、ダイアログを走査してコントロールのハンドルを取得した後、PostMessage でボタンダウンを投げたり、コンボボックスの選択項目変更操作もしていました。ちなみにこのアプリでは、サブクラス化まではしておりません。
当時は 「カスタマイズ環境が提供されていない AutoCAD LT を外部からカスタマイズする」 という構想のもと、多くのパワーユーザーがオンラインで議論していたのですが、これも 「AutoCAD」 という多くのユーザーがいる明確なターゲットがあったからこそ、活発な議論や動作実験が出来ていた訳でして、10年近く経って環境も大きく変わったいま、当時のやり方がそのまま通用するとは到底思っておりません。
せめて
> 手を加える事が出来ない他アプリ
が一体なんなのか判ればいいのですが・・・・これが判らなければ、検証のしようもないしチャレンジする気にもなれないといったところです。
大体ただですら他アプリのGUI操作の自動化なんてしてる人も少ないでしょうし、出来ているなら簡単には情報を開示しない様に思えます。また市販されていない、もしくは入手が難しいソフトであれば、ここで情報を集めるのは極めて難しい気がしておりますが、いかがでしょうか。
ひらぽん http://blogs.yahoo.co.jp/hilapon/ -
既に書いていますけれど、
フレームワーク内で全て行なっているかどうかは実装した所にしかわかりません。
開発方法だけの話をしてしまうとWin32APIを駆使してかなり深いつくりをしているパターンと
MFCのフレームワークを使って本当に素直な実装をしているパターンで考えてみると
素直なつくりをしたものでは通用する方法が深いつくりをしているものでは通用しないケースが
ありえるという話です。
こういう言い方をしてしまうとみもふたも有りませんが、乱暴な言い方をしてしまうと
実装する側でフレームワークを逸脱するような実装をしていてもアプリの動作だけを
見る限りでは分からないはずです。フレームワークの実装を前提に連携を組み込もうと
してもフレームワークから逸脱した作りのアプリでは期待した動作が得られないと
考えるのは不自然な考え方では無いと思います。ファイルを開くのダイアログにしてもアプリ側からフックを掛けて処理を変更するようなケースで
他のアプリからさらにフックされる事を想定せずに作りこんでいるケースもあると思います。
アプリケーション側に連携を意識したインターフェイスがあれば、
確実な動作も期待できると思いますが、それ以外の方法で連携しようとした場合は
アプリ側が想定しないケースに陥る可能性が少なからず存在します。偶々フレームワークをフルに使った薄い実装をしているケースであればうまく動くけれど、
フレームワークを逸脱するような実装をしているケースではうまく動かないと言う事は
ありえるという話だと思います。メモ帳やワードパッドにしてもどのように実装しているのかは結局Microsoftにしかわかりません。
そういう意味では絶対にうまく行く汎用的な方法は無く、汎用的と思われる方法でうまく行くものと
行かない物があるのは仕方ないと言う話になると思います。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。