none
FindWindowEx、FindWindowが失敗する場合の対応方法を教えて下さい。 RRS feed

  • 質問

  • 煩わしいアプリケーション操作の自動化を目指して調査中です。その為に、アプリケーションをサブクラス化し注入したコードの中でウィンドウの検索を行っているのですが検索が失敗します。条件を指定しても、クラス名とウィンドウテキストを明示的に指定した場合に検索が成功しません。ウィンドウテキストとクラス名は、フックコード内でGetWindowText、GetClassNameを行って取得しファイルに書き出したものなので間違いは無いと思うのですが、思う様に検索が行えません。

    FindWindowEx(
           hwnd,
           NULL,
           "#32770",
           "ファイルを開く"
           );

    の様に使用しているのですが何か間違えている所は見当たりますでしょうか?更に、hwndの子ウィンドウなのにhwndにNULLを設定すると検索できたり、"ファイルを開く"を、""にして検索するとクラス名が"#32770"でもないウィンドウのウィンドウハンドルを取得したりと挙動が不安定です。

    以上、どなたかアドバイス宜しくお願い致します。

    2010年4月19日 11:50

回答

  • トップレベルウィンドウだからでは? ファイルダイアログみたいだし。

    Spy++ などで hwnd のウィンドウの子ノードに存在してるのは確認しましたか?

    2010年4月19日 12:54
  • (HWND)GetWindowLong(hWnd,GWL_HWNDPARENT)

    で得られるのは確かに親ウインドウですが、ファイルDLGはWS_POPUPなので、
    トップレベルに配置されます。
    Hongliang さんの発言どおりに「SPY++で見れば」、トップレベルに配置されており、
    かつ親がHWND_DESKTOPではないことが確認できるはずなのですが・・・(vv;)。

    2010年4月20日 2:16
  • 以下のコードはテストしたわけではないので参考です。誤りもあるかもしれません。
    メモ帳などで試してみてください。
    //-----クリップボードに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です。

    2010年4月22日 0:55
  • Windowsって使い方によっては不安定なのでしょうか・・・

    内容を読んでいて感じたんですが、
    Humpty-Dumptyさんがやろうとしているのは本来の道筋から外れた所で連携させようとしているから
    色々問題が起こっているのだと感じます。
    システム的に連携させる事を前提に各アプリを設計していれば、
    今やろうとしているような方法をとる必要はなかったはずです。

    何を言いたいかというと「無理やり連携させようとしているわけだからうまく行かないケースがあっても
    しかたないのでは?」という事です。
    100%の動作を求めるなら連携を考慮に入れた設計にしておくべきだと思います。

    ユーザーオペレーションをなぞるような方法を取る場合、本来ならユーザーがオペレーションを
    行なわないようなタイミングでイベントが起こる事もありえると思うのでそういう場合にうまく反応して
    くれないというような事はあると思います。
    その辺の部分の含めてきっちり問題ないタイミングでオペレーションをなぞったイベントを
    起こせるのかと言う部分の話になると思うので不安定と言うのとは違うのではないかなぁと感じます。

     


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2010年4月22日 5:38

すべての返信

  • トップレベルウィンドウだからでは? ファイルダイアログみたいだし。

    Spy++ などで hwnd のウィンドウの子ノードに存在してるのは確認しましたか?

    2010年4月19日 12:54
  • アドバイス有難う御座います。

    (HWND)GetWindowLong(hWnd,GWL_HWNDPARENT)

    で調べた結果、hwnd(別のトップレベルウィンドウのハンドル)が一応、親ウィンドウのハンドルになっています。それもトップレベルウィンドウと呼ぶなら、そこはFindWindowを使うべきなのですね?

    また、もう一方の問題ですが、具体的に書くと、ウィンドウテキストの無いEditボックスのウィンドウハンドルを取得したいのですが、クラス名"Edit"、ウィンドウテキスト""で検索結果が失敗する理由は解りますでしょうか?

    2010年4月20日 0:32
  • (HWND)GetWindowLong(hWnd,GWL_HWNDPARENT)

    で得られるのは確かに親ウインドウですが、ファイルDLGはWS_POPUPなので、
    トップレベルに配置されます。
    Hongliang さんの発言どおりに「SPY++で見れば」、トップレベルに配置されており、
    かつ親がHWND_DESKTOPではないことが確認できるはずなのですが・・・(vv;)。

    2010年4月20日 2:16
  • アドバイス有難う御座います。確認出来ました。

    >ファイルDLGはWS_POPUPなので、

    知りませんでした。助かります。あと、Editボックスの件についても何か解りますでしょうか?

    2010年4月20日 4:31
  • FindWindowEx()で子ウインドウを検索する場合、指定されたタイトル(キャプション)
    を比較するためにGetWindowText()を使用するらしいのですが、この関数は、
    自分以外のアプリケーションのタイトルは取得できないとあります。
    この関数に失敗すると、FindWindowEx()も失敗に終わると考えられます。

    また、該当するウインドウが複数あった場合の動作の説明がありませんので、
    その場合に失敗を戻すかもしれません。

    ひょっとすると、上記いずれかの状態に陥っているのかもしれません。
    この仮説どおりだとEnumChildWindows()を使うしかないかもしれません。

     

    2010年4月20日 6:26
  • FindWindowEx()で子ウインドウを検索する場合、指定されたタイトル(キャプション)
    を比較するためにGetWindowText()を使用するらしいのですが、この関数は、
    自分以外のアプリケーションのタイトルは取得できないとあります。
    この関数に失敗すると、FindWindowEx()も失敗に終わると考えられます。

    また、該当するウインドウが複数あった場合の動作の説明がありませんので、
    その場合に失敗を戻すかもしれません。


    ひょっとすると、上記いずれかの状態に陥っているのかもしれません。
    この仮説どおりだとEnumChildWindows()を使うしかないかもしれません。


    中澤さんの話を読んでいたら、おぼろげながら思い出しました。

    うろ覚えでたいへん申しわけないのですが、今から10年くらい前、やはり AutoCAD のファイルダイアログを外部から制御しようとして、FindWindowEx だとうまくいかず、EnumChildWindows を使ったらうまくいったような覚えがあります。

    ソースコード(Delphi) を見れば一発で思い出せるんだろうけど、家帰ってもすぐに探し出せるかどうか(汗
    ネット上でも議論した覚えがあるので、EnumChildWindows と AutoCAD で検索かけると、昔のリソースが残っていれば参考になる資料が出てくるかも知れません。

    #なにせ10年以上も前の話なんで、役に立たないかも知れません。<(_ _;)>


    ひらぽん http://blogs.yahoo.co.jp/hilapon/
    2010年4月20日 7:14
  • アドバイス有難う御座います。

    CreateProcessで作成したプロセスにフックプロシージャをインストールして、その中でウィンドウのサブクラス化を行っているので、プロセス境界を超えています。現に、GetWindowText関数は成功してファイル書き出しは行えています。また、Editボックスはダイアログ内に一つなので、いずれの状態も考えにくいです。

    >EnumChildWindows()を使うしかないかもしれません。

    結局の所、テキストとクラス名だけからではいかなる状況でも判断できるとまではいかないので、自分もそれしか無いかと考えています。メニューの様に一意な識別子位あってほしい物です。

    2010年4月20日 7:20
  • ちなみに、FindWindowEx()が孫まで検索してくれることを期待していますか?。
    この件に関してもマニュアルには何の記述もありませんので、多分直下の子
    しか検索しないのではないでしょうか(未確認)。
    んで、おっしゃっている「Edit」は実はDLGのコンボボックスの子だったり
    してませんでしょうか。少なくともXPのファイルDLGの唯一のEditは
    DLG->Combobox32->Combobox->Editになってますけど。

    2010年4月20日 8:38
  • アドバイス有難う御座います。

    Spy++で確認したところ、DLG->Editで親ウィンドウがDLGとなっている様です。

    謎です・・・。

     

    2010年4月20日 9:02
  • > #なにせ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/
    2010年4月20日 9:04
  • リンク参考にさせて頂きます。

    最初の検索をFindWindowに変えた結果、FindWindowExも正しく動作する時もあるしNULLを返す時もある。極めて不安定な挙動の為にEnumChildWindowsで書きなおす必要がありそうです。

    それ以外に、もう一つ質問してもよろしいでしょうか?

    上記、Editボックスにテキストを設定する方法が解りません。WM_SETTEXTが正しく反映されませんがどなたか良い方法をご存知の方いらっしゃいますか?

    2010年4月20日 9:55
  • WM_SETTEXTはSendMessage()してますか?。
    それでもだめな場合はクリップボード経由でのテキストの
    ペーストを検討しなければなりません。

    2010年4月21日 2:57
  • SendMessageしても反応がありませんでした。何故なんでしょう???

    クリップボード経由でのテキストのペーストを教えて下さい。

    2010年4月21日 4:50
  • その前に

    EnableWindow( hwnd_edit, TRUE);
    SendMessage( hwnd_edit, WM_CHAR, WPARAM( 0x0030), MAKELPARAM( 1, 0x000B));
    SendMessage( hwnd_edit, WM_CHAR, WPARAM( 0x0031), MAKELPARAM( 1, 0x0002));
    で、反応("01"と表示)するか調べたほうが良いかもしれません。
    これに反応しないようだと、そのhwnd_editが「Edit」であるかどうかが怪しくなります。

    2010年4月21日 5:23
  • hwnd_editが「Edit」になっている時があります。ファイル書き出しして確認しています。その時でもWM_SETTEXTは動作していません。

    上記を確認し直すべきでしょうか?お手数ですが何を確認しようとしているか教えていただけますか?

    2010年4月21日 6:13
  • 意味が分かりづらかったでしょうか、前回の発言のコードは
    エディットに'0'、'1'の順番で文字入力があったように
    エミュレートしています。従って当該のhwnd_editが真にEditコントロール
    であればそのコントロールに"01"と表示される予定です。
    さて、そうならなかった場合

    1.hwnd_editはエディットコントロールでない。
    2.hwnd_editはエディットコントロールであり、文字もセットされたが
      その後、誰かがもう一度WM_SETTEXTして、空欄にしている。

    などが考えられます。
    上記の何れの場合でもWM_SETTEXTもクリップボード経由も効果がありません。

    2010年4月21日 6:49
  • ファイルに書き出して照合しているのですが、ハンドルがEditでない事が全てでは無いです。(NULLの時もあります)ファイルダイアログのファイル名・パスを入力する所がEditボックスでないという意味ですか?

    一応、クリップボード経由の方法も試してみたいのですがご教授頂けませんでしょうか?また、上記の論理を教えて頂けると有難いです。

    Windowsって使い方によっては不安定なのでしょうか・・・

    2010年4月21日 9:54
  • 以下のコードはテストしたわけではないので参考です。誤りもあるかもしれません。
    メモ帳などで試してみてください。
    //-----クリップボードに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です。

    2010年4月22日 0:55
  • こんにちは。

     

    たまたま調べる必要があった個所と一部かぶってたので、少し調べてみました。

    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 でしか変化できない…つまり汎用の定数がないとしたら

    そしてかつクリップボード制御用関数を自分でつくっておいて使いまわす、としたら、どっちを使うかによって関数を別々に作っておくとかがベストなのでしょうか?

    2010年4月22日 3:46
  • クリップボードの原則は
    1.設定側の「善意」で提供された複数のフォーマットのデータの内
    2.取得側の「能力」で取得できるフォーマットでペーストする
    となります。どうするかはプログラマの決めることです。

    また、提示の例ですが、そのような手順で可能なのは承知しています。
    Humpty-Dumptyさんの例はフックプロシージャ内の特定イベントで
    ファイルDLGをサブクラス化した上で、かつ、そのサブクラス内から
    又は、サブクラスのプロシージャを知っている他のアプリから、
    エディットに文字列を設定しようとするもので、設定できるタイミング等が
    微妙だと理解しています。以上の点で提示の例とはやや趣が異なるかもしれません。

    2010年4月22日 4:22
  • Windowsって使い方によっては不安定なのでしょうか・・・

    内容を読んでいて感じたんですが、
    Humpty-Dumptyさんがやろうとしているのは本来の道筋から外れた所で連携させようとしているから
    色々問題が起こっているのだと感じます。
    システム的に連携させる事を前提に各アプリを設計していれば、
    今やろうとしているような方法をとる必要はなかったはずです。

    何を言いたいかというと「無理やり連携させようとしているわけだからうまく行かないケースがあっても
    しかたないのでは?」という事です。
    100%の動作を求めるなら連携を考慮に入れた設計にしておくべきだと思います。

    ユーザーオペレーションをなぞるような方法を取る場合、本来ならユーザーがオペレーションを
    行なわないようなタイミングでイベントが起こる事もありえると思うのでそういう場合にうまく反応して
    くれないというような事はあると思います。
    その辺の部分の含めてきっちり問題ないタイミングでオペレーションをなぞったイベントを
    起こせるのかと言う部分の話になると思うので不安定と言うのとは違うのではないかなぁと感じます。

     


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2010年4月22日 5:38
  • うう~~むw

    改めて読んでみて、状況がよく分かってないことに気付いたのですがw

     

    えっと、今自分がいじれる側をAとして、設定されることになるファイルDLGを持ってるプロセス側をBとします。

    このとき

    Bはコードは「見れるorいじれる」のですか?

    それができるならばわざわざ「Editコントロール」を介する必要がないか、もっと行くと「何らかの一括処理を行う結果が生じる」ならば、オープンファイルダイアログを使用する必然性すらないのでは…?と思うので、Bはいじれないしコードを見れないが、しかし複雑な処理を行っていてその機能を自分で作ることなしにどうしても使いたい、という状況ではないか、と私は考えたのですが

     

    この通りだとすると、ファイルDLGをサブクラス化するという行動をするのも、Aが行う、ということになっているのでしょうか?


    上記の方法でほとんどの場合は「可能ではあるか、あるとき列挙が失敗してもFALSEが返るだけでおおよそ危険はないとは思います」が、不確定要素がある中で消去法で考えたという意味合いもあるため

    これらの事が明確になれば、もっといい方法が浮かび上がりそうな気がします。

    2010年4月22日 8:18
  • 七空白さん

    > Bはコードは「見れるorいじれる」のですか?
    > それができるならばわざわざ「Editコントロール」を介する必要がないか、もっと行くと「何らかの一括処理を行う結果が生じる」ならば、オープンファイルダイアログを使用する必然性すらないのでは…?(以下略)

    たぶん以下のスレッドから話が続いているのだと思います。

    http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/4e8ccfc1-98a3-46fe-84cb-03aa47ab2dac/

    > Windowsって使い方によっては不安定なのでしょうか・・・

    個人的な感想としては詳細な仕様が判らないため断定できませんが、Windows よりも むしろ FindWindowEx とかの方が不安定?もしくはこちらの意図しない挙動になっているような気がします。

    私の経験上、また色々な記事を検索して思ったのですが、FindWindowEx とかよりも EnumChildWindows を使った方がいいように思えます。


    ひらぽん http://blogs.yahoo.co.jp/hilapon/
    2010年4月22日 8:40
  • ひらぽんさん

    どうもどうもw ありがとうございます。

    なるほど、ある程度ちょっかいだせてもサブクラス化そのものは本人(そのアプリ)じゃないと出来ない、ということでしたか。

    ってことは、Bの方いじれるってことかな?w

    そうなると、サーバアプリ… 改善案が出るかどうかはAとBを切り離す理由(小)、エディットの中身をいじりたい理由(大)次第っぽいですね。

    あんまりHumpty-Dumptyさんがいない間に沢山投稿するのもあれかもなので、私は一端ROMりますw

    2010年4月22日 9:23
  • 読み返していて気が付いた事があるんですが、

    そもそもファイルを開くのコモンダイアログのファイル名を入力するエディットコントロールが

    >Spy++で確認したところ、DLG->Editで親ウィンドウがDLGとなっている様です。

    なんて事があるんでしょうか?
    色々SPYで調べてみましたが、古いタイプのダイアログでも問題のエディットコントロールは

    DLG->Combobox32->Combobox->Editになってます。

    なんか違う物を相手にしているのではないかと言う気がしています。

    御本人のPCだけ違うと言うのも変な話ですし。
    もしくは問題のダイアログがコモンダイアログとは似て非なるものなのか。

    あと、ファイルを開くのコモンダイアログが別アプリでも開いている場合は多分区別がつかないと思います。
    それについて承知の上でやっているなら問題は無いと思いますけれど。

    追記:
    エディットコントロールを検索する時にウィンドウテキスト""で正解なんですかねぇ。
    MSDNでFindWindowExを見てみると指定しないときは、NULLを渡すように書かれています。
    空文字列を渡すのではなくてNULLポインタを渡すのが正解なのではないかと思うのですが。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2010年4月22日 10:14
  • お世話になっております。少し消化不良です。しばしお待ち下さい。

    >NULLポインタを渡すのが正解なのではないかと

    GetWindowTextの戻り値が空文字でしたので、同じ様に設定したところ上手く検索出来ています。NULLよりも空文字の設定の方が都合がよかったのでそちらを使っています。

    現在、ダイアログボックスをサブクラス化したプロシージャの方にWM_COPYDATAを使用して受け取った文字列群を順に検索する様に書いていますが、WM_COPYDATAのパスのコードを有効にすると、ダイアログに接触した時点(クリックなど)でアプリケーションが落ちるという妙なバグに遭遇して困り果てています。

    >あんまりHumpty-Dumptyさんがいない間に沢山投稿するのもあれかもなので

    いえいえ、ご自由にご歓談下さい。

    それでは、失礼いたします。

    2010年4月22日 12:14
  • 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でしょうか?

    ここまでの内容が全部大丈夫だとすると

    「文字列群を順に検索する」のアルゴリズムに問題があるのかもしれません。

    2010年4月23日 1:11
  • お世話になっております。

    少し上記の内容とは状況が違う様に思います。今現在取り扱っているアプリケーションは自分で手を加える事が出来ないアプリケーションである事が前提です。なのでGetOpenFileNameを使ってファイル名を取得しても使い道がありません。アプリケーションで定義されたダイアログボックスの呼び出しに応じてEditにファイルのフルパスを設定し、開くボタンを押すという操作を実現したいという要求です。

    ダイアログボックスとはいえ結局はウィンドウのはしくれなので、メインウィンドウ同様の方法でフック出来る筈です。また同様の方法でサブクラス化も可能な筈です。どちらもここまでは出来たのですが、ダイアログボックスをサブクラス化するプロシージャの処理が何故か上手く動かないという状況です。

    文字列の検索に問題が無い事は立証済みです。

    2010年4月24日 6:52
  • >今現在取り扱っているアプリケーションは自分で手を加える事が出来ないアプリケーションである事が前提です。

    なるほど、やはりそうでしたか

    んじゃ2010年4月22日 8:18の投稿の私の読みは当たってはいたようですが、この情報自体は私は初耳ではないかと思いましたw

    ・・・私が見落としてるだけで、どっかに書いてありましたっけ?

     

    特定の状況を除きダイアログボックスを普通のウィンドウと同等に扱う事も、もちろん可能と思いますが

     

    過去の質問をいくつか見させていただきましたが、仮にすべての質問が同じ今問題となっているアプリについての話題であるとしても、何をどうやっているのかがほとんどコードで示されていないので、どのような手法によって実現しようとしている中で


    ダイアログボックスをサブクラス化するプロシージャの処理が何故か上手く動かない

     

    のかが不明瞭です。うまくいっている、のならばある程度絞られるとは思いますが、うまくいっていないとなると莫大な可能性があります。エスパーじゃなければ最小限の手間で解決するのは困難ではないかとw

     

    と、いうわけで

    私がそれによって解法を提示できるという保証は全くありませんが、識者の方が答えられるように、ある程度のコードを提示されてはいかがでしょうか?

    2010年4月24日 7:33
  • コードは全部張り付けるわけにいかないので、概要だけでは駄目ですか?

    ①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の区画に何故か入らず処理が起動しなく正常にウィンドウ操作をつづけられる、もしくは、ウィンドウ操作を行おうとダイアログボックスファイル表示部をクリックしただけでアプリケーションが落ちる。

    そもそも、ダイアログボックスのサブクラス化がサポートされていなくて出来ないという可能性もあるのではないかと思うのですが如何でしょうか?

    2010年4月24日 10:04
  • >③サブクラス化は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のメンバをいじって、起動時にそういうやつにする、というのは解説があるので大丈夫、あるいは通常の方法だと思うのですが、作った後でさらに変化させるのが正しいやり方かどうかは分かりません。ので、そういう意味に於いて

     

    >ダイアログボックスのサブクラス化がサポートされていなくて出来ない

     

    可能性は十分あるのでは?というのが現時点の知識内での私の考えです。

    2010年4月24日 11:05
  • そうですか・・・ちなみに、試してみようと思うのですが、

    #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に設定したい文字列を渡す形にして表示できますか!?

    2010年4月25日 9:01
  • はい、単にめんどかったからtcって書いただけで、任意の文字列を渡せます。

    ちゃんとEditコントロールを特定することができていれば

     

    ::SendMessage(hw,WM_SETTEXT,0,(LPARAM)_T("任意の文字列" ));

     

    とか、あるいは別の変数でも何でも、文字として整合性があれば可能です。この場合、目的のアプリケーションがUnicodeを使うのかANSIを使うのかを気にしなくても、OSが吸収してくれます。

    ただ、この場合当然一度につき一つの文字列しか送れないことになりますので、ベストかどうかは処理内容によりますね。

    2010年4月25日 9:24
  • こちらでは、表示されません。参考までにコードをのっけるとこんな感じです。

    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バージョンの違いによるものでしょうか???ちなみに、マルチバイト文字に設定してコーディングしています。理由とか想像付きますか?

    2010年4月25日 10:09
  • もしも、もしもですよ?他の部分が正常だとして

     

    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);

    というコードを書いたときに期待通りたった一行で見事に「アプリが落ち」ましたが、上記を修正してうまくいった場合は、逆に今うまくいかなかった理由はこれとまったく同じです。

    2010年4月25日 10:18
  • 有難う御座います。上記変更で貼り付けはうまくいきました・・・が、ウィンドウの操作を行うと同時にアプリケーションが落ちてしまうのですが、これはいったい何故なのですか???

    2010年4月26日 10:07
  • このコードを見ただけでは不明な点はいくらかありますが、貼り付けがうまくいった、のならば、普通に考えるならEnumWindows系やWM_SETTEXT自体だけでそんなことになるとは考えにくいので、おそらく別の個所に原因がある、あるいは目的のダイアログがなんらかの特殊な内部処理を行っている可能性もあります。が

    今回のMAKELPARAMのように、コードがなければこちらからは原因がほとんど特定できないような問題がもし他にあるというなら、それの見当をつけることはほとんどできません。

    ひとまず私はしばらくお楽しみ(自分のアプリの方)のスレ・研究・調査・コーディングなどに傾倒したいのでw

    後のことはどなたかお願いします(w)

    2010年4月26日 11:45
  • 七空白さん、ここまでお付き合い頂きまして有難うございます。アドバイス頂いた皆様も感謝です。

    結局のところ可能な物を相手にしているのかどうなのかという事がいまだに解りません。仕様上(Win32のMSDN)は実現可能に見えてしまいます。が、実際の所、自分は動作させる事が出来ていません。

    自分の行いたかった事は、手を加える事が出来ない他アプリの一連のGUI操作を別のアプリから行う事によるGUI操作の自動化です。その為にオープンファイルダイアログも操作する必要性がありましたが、ここが思う様にいきませんでした。

    もしよろしければ、この課題に挑戦された方が他にいらっしゃいましたら、自分は出来た、自分は出来なかった等の結果だけでも教えて頂けませんでしょうか?

    以上、ご協力宜しくお願い致します。

    2010年4月26日 23:32
  • 思うのですが、操作対象にしているプログラムの内容を全て理解した上で
    行なっているのでは無いという事であれば、100%うまく行く方法を確定する事はできないと思います。
    確かにフレームワークとして用意されている物の上で動いているわけですが、
    細かい実装等に関しては極端な話実装する側でどうにでもなります。

    実装する側が自分のプログラムが他者にサブクラス化されて使われる事を
    想定してプログラムを作成しているとは考えにくく、基本的に自アプリの仕様が
    満たされていればOKという考え方の元に実装されているケースが多いと思います。
    そうした状況の中でアプリ側が想定していない操作を外部から加えた場合に
    うまく動作する保証は有りませんし、アプリを作成した側も保証しないでしょう。
    疑いだしたら限が有りませんが、サブクラス化した事で潜在バグが顕在化する
    ケースもありえますから操作対象がデバッグできない状況ではどうしようもない
    のではないでしょうか。

    御本人の書き込みにあったダイアログ上のコントロールの構成の違いも気になります。
    見た目が同じでも全く同じ物が使われているという保障にはなりませんから
    単一の方法でいつもうまく行くかと言うとこれもかなり怪しいと思います。

    ある意味無理やり連携させようとしているわけなのでうまく行かないケースがあっても
    不思議は無いというのが私の考えです。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2010年4月27日 3:33
  • 自分の行いたかった事は、手を加える事が出来ない他アプリの一連のGUI操作を別のアプリから行う事によるGUI操作の自動化です。その為にオープンファイルダイアログも操作する必要性がありましたが、ここが思う様にいきませんでした。

    もしよろしければ、この課題に挑戦された方が他にいらっしゃいましたら、自分は出来た、自分は出来なかった等の結果だけでも教えて頂けませんでしょうか?

    しばらく静観しておりましたが、また出てきました。以下、的外れな意見でしたらご容赦ください。
    以前 「AutoCAD の外部操作」 にチャレンジし、実際にファイルダイアログや印刷ダイアログを操作していた立場から言わせて頂きます。

    もう販売しておりませんが、AD_Commander というシェアウェアを作っておりました。AutoCAD をプロセス外から監視し操作を行うアプリですが、連続操作の際、AutoCAD のバージョンによってはファイルダイアログが出てきますので、ダイアログを走査してコントロールのハンドルを取得した後、PostMessage でボタンダウンを投げたり、コンボボックスの選択項目変更操作もしていました。ちなみにこのアプリでは、サブクラス化まではしておりません。

    当時は 「カスタマイズ環境が提供されていない AutoCAD LT を外部からカスタマイズする」 という構想のもと、多くのパワーユーザーがオンラインで議論していたのですが、これも 「AutoCAD」 という多くのユーザーがいる明確なターゲットがあったからこそ、活発な議論や動作実験が出来ていた訳でして、10年近く経って環境も大きく変わったいま、当時のやり方がそのまま通用するとは到底思っておりません。

    せめて

    > 手を加える事が出来ない他アプリ

    が一体なんなのか判ればいいのですが・・・・これが判らなければ、検証のしようもないしチャレンジする気にもなれないといったところです。

    大体ただですら他アプリのGUI操作の自動化なんてしてる人も少ないでしょうし、出来ているなら簡単には情報を開示しない様に思えます。また市販されていない、もしくは入手が難しいソフトであれば、ここで情報を集めるのは極めて難しい気がしておりますが、いかがでしょうか。


    ひらぽん http://blogs.yahoo.co.jp/hilapon/
    2010年4月27日 5:57
  • お世話になっております。

    アプリケーション名をここで言ってしまうと誰か解る人にわ解る様な専用用途のウィンドウアプリケーションで、ルーチンワークの様な使い方をしています。なので言えません。

    上記は、"汎用的なものは作れない"との結論でしょうか?メモ帳、ワードパッドと全てのアプリケーションのUI操作を同一視出来ないとなると、こちらとしても少し士気が下がってしまう所なのですが・・・。

    同一視出来ない理由を2~3挙げて頂けると有難いです。

    2010年4月27日 8:59
  • 既に書いていますけれど、

    フレームワーク内で全て行なっているかどうかは実装した所にしかわかりません。
    開発方法だけの話をしてしまうとWin32APIを駆使してかなり深いつくりをしているパターンと
    MFCのフレームワークを使って本当に素直な実装をしているパターンで考えてみると
    素直なつくりをしたものでは通用する方法が深いつくりをしているものでは通用しないケースが
    ありえるという話です。
    こういう言い方をしてしまうとみもふたも有りませんが、乱暴な言い方をしてしまうと
    実装する側でフレームワークを逸脱するような実装をしていてもアプリの動作だけを
    見る限りでは分からないはずです。フレームワークの実装を前提に連携を組み込もうと
    してもフレームワークから逸脱した作りのアプリでは期待した動作が得られないと
    考えるのは不自然な考え方では無いと思います。

    ファイルを開くのダイアログにしてもアプリ側からフックを掛けて処理を変更するようなケースで
    他のアプリからさらにフックされる事を想定せずに作りこんでいるケースもあると思います。

    アプリケーション側に連携を意識したインターフェイスがあれば、
    確実な動作も期待できると思いますが、それ以外の方法で連携しようとした場合は
    アプリ側が想定しないケースに陥る可能性が少なからず存在します。

    偶々フレームワークをフルに使った薄い実装をしているケースであればうまく動くけれど、
    フレームワークを逸脱するような実装をしているケースではうまく動かないと言う事は
    ありえるという話だと思います。

    メモ帳やワードパッドにしてもどのように実装しているのかは結局Microsoftにしかわかりません。

    そういう意味では絶対にうまく行く汎用的な方法は無く、汎用的と思われる方法でうまく行くものと
    行かない物があるのは仕方ないと言う話になると思います。


    解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。
    2010年4月27日 9:19