none
サブクラスでWM_CLOSEをキャンセルしたい RRS feed

  • 質問

  • WM_CLOSEをキャンセルしたい。
    http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/5fb02fd8-e121-4270-a2be-164624b970cc
    の続きになります。

    上記のスレッドで一応やりたかった事は出来たのですが
    その後調べてみたところEXCELでもフックが出来るようなので
    サブクラス化でコードを書いてみました。

    まずは前回のコードでフックが出来たメモ帳に対して実行してみましたが
    コードが悪いせいか動作しません。
    どなたかどこが悪いか教えていただけないでしょうか?

    コード的には
    http://wiki.livedoor.jp/smnb/d/Win32API
    のサブクラスの項目と違いが無いと思うのですが・・・

    よろしくお願いします。

    // フックを組み込む
    bool CALLBACK Hook(HWND HandleofTarget)
    {
     LPVOID lpMsgBuf;

     hTarget = HandleofTarget;

     //defProc = (WNDPROC)GetWindowLong(HandleofTarget, GWL_WNDPROC);
     defProc = (WNDPROC)(LONG_PTR)GetWindowLongPtr(hTarget, GWLP_WNDPROC);
     //GetWindowLongの戻り値が0で、中身を確認するとアクセス拒否が入っていました。

     if((__int3264)(LONG_PTR)defProc==0)
     {
      FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
         NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
      //★アクセス拒否が表示されます。
        MessageBox( (HWND)hTarget, (LPCTSTR)lpMsgBuf, TEXT("GetWindowLongPtrError"), MB_OK  );
        LocalFree( lpMsgBuf );

      return false;
     }

     if (SetWindowLong(hTarget, GWL_WNDPROC, (LONG)WndProc))
     {
      return true;
     }
     else
     {
      return false;
     }
    }

    2010年12月17日 8:41

回答

  • 以下のコードでようやく解除も出来ました。
    ようやく機能を満たすコードが出来ましたので質問をCloseしたいと思います。
    ご回答いただいた皆様ありがとうございました。
    他の人の参考になるように書いたコードを残しておきます。
    一個上で乗せたコードのUnHookを下のコードと差し替えれば動作します。

    LRESULT CALLBACK CallUnHookWndProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
     return SetWindowLongPtr(hTarget, GWLP_WNDPROC, (__int3264)(LONG_PTR)g_oldWndProc);
    }

    // フックを解除する
    bool CALLBACK UnHook(void)
    {
     HMODULE g_hInst = GetModuleHandle(TEXT("Hook"));
     g_hhook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallUnHookWndProc,g_hInst,idThread);
     if(!g_hhook)
     {
      return false;
     }

     g_hhook = NULL;

     //初期化済みフラグ解除
     g_bInitHook=false;

     return true;
    }

     

    • 回答としてマーク おれおれ 2011年1月7日 1:31
    2011年1月7日 1:31

すべての返信

  • [SetWindowLongPtr Function]

      http://msdn.microsoft.com/en-us/library/ms644898(VS.85).aspx

       (SetWindowLongの代わりにSetWindowLongPtrを使うことが推奨されます。)

     

    上記によると、

    1.UIPIに引っかかっている

     The SetWindowLongPtr function fails if the process that owns the window specified by the hWnd parameter is at a higher process privilege in the UIPI hierarchy than the process the calling thread resides in. 

      [アプリケーション開発者向け Microsoft® Windows 7 対応アプリケーションの互換性]

        http://msdn.microsoft.com/ja-jp/windows/dd871146

     

    2.そもそも、別ProcessにSetWindowLongPtrすることができない。

    Windows XP/2000:   The SetWindowLongPtr function fails if the window specified by the hWnd parameter does not belong to the same process as the calling thread.

    3. そもそも、別Processに属するWindowのSubclassingはやるべきではない

    should not subclass a window class created by another process.

     

    だそうです。

    最終的にどのような動作をするSoftwareをどのような目的で作ろうとしているか知りませんが、Security上の理由から別Processに属するWindowのSubclassingは推奨されるものではないでしょう。

    2010年12月17日 10:51
  • Kozzさん回答有難うございます。

     >最終的にどのような動作をするSoftwareをどのような目的で作ろうとしているか知りませんが、Security上の理由から別Processに属するWindowの>Subclassingは推奨されるものではないでしょう。

    最終的にはDLL化を行い、どんなプログラムでも終了が出来なくすることを目的としています。
    (C#からWndHnwdを渡し、当該ハンドルを持つアプリのCloseを停止/解除を行うプログラムです。)

    質問させていただいた他のスレッドでは「HCBT_DESTROYWND」をフックすることでキャンセルを行うことが出来ましたが
    Excelではそれが行えず、同スレッド内でサブクラス化の記述がありましたので調べてコードを書いてみました。
    (ExcelはWM_CLOSEで独自処理をおこなっているらしく「HCBT_DESTROYWND」をキャンセルすると異常終了してしまいます。)

    どのような方法を使えば出来るかはわからないのですが、今回の「SetWindowLongPtr」を使用する方法だと
    無理みたいですが、何か他に方法はないでしょうか?

     

     

    2010年12月18日 3:39
  • >何か他に方法はないでしょうか?

    残念ながらありません。

     

    [About Window Procedures]

      http://msdn.microsoft.com/en-us/library/ms633569(VS.85).aspx

      you cannot subclass a window or class that belongs to another application. All subclassing must be performed within the same process.

      他のアプリケーション(プロセス)に属するWindowやClassをサブクラス化することはできない。

      全てのサブクラス化は同じプロセス内で行わなければならない。

    と記述されています。やろうとしていることは禁止されている事項ですので、諦めたほうがよいと考えますが、如何でしょうか。

     

    >どんなプログラムでも終了が出来なくすることを目的

    >ExcelはWM_CLOSEで独自処理をおこなっているらしく

    Excelに限らず、殆どの一般的なApplicationは後処理をWM_CLOSEやWM_DESTORYで行います。一見動いているような状況でも、その後操作を行うと落ちることがあります。WM_CLOSEを握りつぶす(=なかったことにする)仕組みがWindowsで提供されていない以上、その要件は実現困難です。

    私も以前似たような要件で、Mouse Eventを握り潰したい事があったのですが、諦めた経緯があります。

    要件を再検討されたほうがよいでしょう。

    2010年12月18日 8:42
  • 次に行き着きそうなグローバルフックですが、C# では実現できません。(念のため)
    http://support.microsoft.com/kb/318804/
    "Global hooks are not supported in the .NET Framework"
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年12月19日 6:28
    モデレータ
  • 次に行き着きそうなグローバルフックですが、C# では実現できません。(念のため)
    http://support.microsoft.com/kb/318804/
    "Global hooks are not supported in the .NET Framework"

    質問者の方は逆にグローバルフックからこっちに来てますよ。ここ C++ のフォーラムですし。

    // WH_CALLWNDPROC を使って WM_CREATE 辺りにフック引っかけてサブクラス化、は妥当だと思いますが、やったことがないので私は今回の質問に対して助言できません。

    2010年12月19日 7:30
  • 質問者の方は逆にグローバルフックからこっちに来てますよ。ここ C++ のフォーラムですし。
    本当ですね。寝ぼけすぎました、すみません。orz
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年12月19日 8:26
    モデレータ
  • みなさま返信有難うございます。
    方法は分からないのですが、手持ちの他の人が作成したDLLでは
    希望する動きをする物があります。
    今回新規に作成し直すにするにあたりソースコードや
    情報等が無い為、現在調査しています。
    (動作的にはC#(.Net)から当該VC-DLLを呼び出す。
     その際に渡す情報はアプリのウインドウハンドル)

    ですので何か実際に実現する方法はあると思うのですが
    他に何か思い当たる事はありませんでしょうか?

    質問ばかりで申し訳ありませんがよろしくお願いいたします。

    2010年12月20日 6:43
  • >手持ちの他の人が作成したDLL

    知人でしたら直接聞くのが早いと思いますが、それは難しいのでしょうか。

     

    >最終的にはDLL化を行い

    確認ですが、現状のModule構成はどのようになっていますか?Hook ProcedureはDLLにありますよね?先にDLLにしてから調査したほうが良いでしょう。

    適当なApplicationを自分で作り、Hook DLLがApplicationに読み込まれているか確認してみて下さい。IDEのモジュールのタブにHook DLLは存在していますか?

    それから、Hookはいくつか種類がありますので、とりあえず全てのHookを仕掛けてみて利用できそうなHookや機会がないか調べてみてはいかがでしょうか。

    WH_CBT HookのHCBT_SYSCOMMANDが利用できそうかな、と思います。

    the return value must be 0 to allow the operation, or 1 to prevent it.だそうです。

     

    >どんなプログラムでも終了が出来なくすること

    ApplicationがUserにより閉じられるタイミングは、以下が思いつきます。

      1.Frame WindowのIconをDouble Clickする

      2.System Menuから”閉じる”を選択する

      3.Alt + F4を押す

      4.Applicationのファイルメニューから、”終了”や”閉じる”を選択する

      5.その他Applicationの独自実装

    4.や5.がWindow Messageを利用して実装されていない場合、現状の手段では対応は困難ですね。この場合は除外でしょうか。

    2010年12月22日 9:22
  • kozzさん返信ありがとうございます。

    >確認ですが、現状のModule構成はどのようになっていますか?Hook ProcedureはDLLにありますよね?先にDLLにしてから調査したほうが良いでしょう。
    DLLになっています。

    >適当なApplicationを自分で作り、Hook DLLがApplicationに読み込まれているか確認してみて下さい。IDEのモジュールのタブにHook DLLは存在していますか?
    C#からはDllImportで呼び出しをおこなっています。

    >それから、Hookはいくつか種類がありますので、
    >とりあえず全てのHookを仕掛けてみて利用できそうなHookや機会がないか調べてみてはいかがでしょうか。

    >WH_CBT HookのHCBT_SYSCOMMANDが利用できそうかな、と思います。
    それはコレでしょうか?
    前回のスレッドでは

    HCBT_DESTROYWNDをキャンセルしましたが、メモ帳ではOKでしたがOffice2010ではExcelが強制終了してしまいました。
    SPY++で見た感じではメモ帳ではWM_CLOSEは見えていますがWM_DESTROYは見えません
    一方のEXCEL2010ではWM_DESTROYが記録されています。

    http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/5fb02fd8-e121-4270-a2be-164624b970cc

    kozzさんが考えている「WH_CBT HookのHCBT_SYSCOMMAND」は上記の物とは違う方法でしょうか?

    そもそも、この辺のシステムコマンドに関して、十分に理解出来ていないので見当違いの事を
    記載していましたら、申し訳ありません。 

    2010年12月24日 1:38
  • いろいろと調べてみました。

    まず、手持ちのDLLですが「SetWindowsHookEx」は使用していない模様です。
     http://msdn.microsoft.com/ja-jp/library/aa384274(v=vs.85).aspx
    によると、SetWindowsHookExする際には32/64Bitで処理を分ける必要があるとの事ですが
    手持ちDLLは32/64BitOS環境でも動作しています。
    ですので、サブクラスなど他の手段で動作している模様です。

    次に、サブクラスを再度調べてみました。
    http://cboard.cprogramming.com/windows-programming/46065-adding-controls-when-dll-hooking.html
    のページのコードによると、外部のDLLから電卓に対して外からボタンを追加?している模様です。

    上記のコードを書き換えてやってみましたが、★の部分で永久ループしてしまっています。
    サンプルではDLL_PROCESS_ATTACHでメインルーチンを書いているのでこれが原因なのでしょうか?
    また、動いていないので分からないのですがこの方法でCloseをキャンセルできるのでしょうか?

    // フックを組み込む
    bool CALLBACK Hook(HWND HandleofTarget)
    {
     HWND hFind = NULL;
     DWORD wndProcessId;
     DWORD thisProcessId=GetCurrentProcessId();

     if(thisProcessId==0)
     {
      MessageBoxW((HWND)hTarget,TEXT("GetCurrentProcessId?"),TEXT("NG"),0);
      return false;
     }

     do
     {
      GetWindowThreadProcessId(HandleofTarget, &wndProcessId);
     
     } while (thisProcessId!=wndProcessId);//★ここで無限ループしている・・・。
                                       //なんでDLLのハンドルとターゲットのプロセスIDが一致するか調べているのか謎

     MessageBoxW((HWND)hTarget,TEXT("GetWindowThreadProcessId?"),TEXT("OK"),0);

     hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
     
     newProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(HandleofTarget, GWL_WNDPROC, (__int3264)(LONG_PTR)WndProc);

    MessageBoxW((HWND)hTarget,TEXT("SetWindowLongPtr?"),TEXT("abc"),0);
     while (MsgWaitForMultipleObjects(1, &hEvent, FALSE, INFINITE, QS_ALLEVENTS) != WAIT_OBJECT_0)
     {
      DoEvents();
     }

     return true;
     
    }

    • 編集済み おれおれ 2010年12月27日 11:39 一部コメント追加
    2010年12月27日 10:58
  • 相手のプロセスでコードを実行していますか?
    自分のプロセスの中でこのコードを実行しても意味がありません。
    そのまま実行していても、相手のウィンドウのプロセス ID と一致することはあり得ないからです。

    この手法は DLL Injection と呼ばれています。
    どういった原理なのかは一度、検索してみてください。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年12月29日 1:49
    モデレータ
  • Azuleanさん返信有難うございます。

    おかげさまで各種メッセージをキャンセルする所までは動作しました。
    参考にしたHPは
    http://homepage1.nifty.com/kazubon/progdoc/tclock/nagare1.html
    http://dsas.blog.klab.org/archives/50829204.html
    です。

    ただ、先には進んだのですがフックを解除するところで
    フック先のプログラムが強制終了してしまいます。

    フックを解除する際にUnHook内のUnhookWindowsHookEx(g_hhook)
    (書いたコードの一番下のメソッドです。)で解除していますが
    直後のMsgBoxを表示せずに終了し、フック対象のプログラムが強制終了してしまいます。
    拡張エラー情報が表示されないのでプログラム内で何がおきているか分かりません。
    その為どこが悪いか分からず困っています。
    どなたか問題ありそうな箇所は分かりませんでしょか?
    お手数ですがよろしくお願いいたします。

    ◆書いたコード

    // 共有領域(共有領域のデータは初期化してないとうまく確保されない?)
    #pragma data_seg("share")
    HWND hTarget   = NULL;
    static HHOOK g_hhook = NULL;
    bool    g_bInitHook = false;  // InitHook() has been called
    HANDLE  g_hInst;               // instanse handle

    #pragma data_seg()

    WNDPROC g_oldWndProc;          // window procedure

    //閉じるを禁止
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
     switch(message)
     {
     case WM_CLOSE:   //ウインドウあるいはアプリケーションをクローズされた
      return 1;
      break;
     case WM_SYSCOMMAND:
      {
       //右上のボタン系
       switch (wParam & 0xFFF0)
       {
       case SC_CLOSE:
        return 1;
        break;
       }
      }
     }
     return CallWindowProc(g_oldWndProc, hwnd, message, wParam, lParam);
    }

    void InitHook(HWND hwnd)
    {
     if(g_bInitHook) return;
     g_bInitHook = true;

     g_hInst = GetModuleHandle(TEXT("Hook"));
     g_oldWndProc = (WNDPROC)(LONG_PTR)GetWindowLongPtr(hwnd, GWL_WNDPROC);
     SetWindowLongPtr(hwnd, GWL_WNDPROC, (__int3264)(LONG_PTR)WndProc);
    }

    LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
     LPCWPSTRUCT pcwps = (LPCWPSTRUCT)lParam;

     if(nCode == HC_ACTION && pcwps && pcwps->hwnd)
     {
      if(!g_bInitHook && pcwps->hwnd == hTarget)
      {
       InitHook(pcwps->hwnd);
      }
     }
     return CallNextHookEx(g_hhook, nCode, wParam, lParam);
    }

    // フックを組み込む
    bool CALLBACK Hook(HWND HandleofTarget)
    {
     DWORD idThread;

     hTarget=HandleofTarget;

     HMODULE g_hInst = GetModuleHandle(TEXT("Hook"));

     // get thread ID of OfficeApp
     idThread = GetWindowThreadProcessId(HandleofTarget, NULL);
     if(!idThread)
     {
      MessageBoxW((HWND)hTarget,TEXT("GetWindowThreadProcessId?"),TEXT("FALSE"),0);
      return false;
     }

     // install an hook to thread of OfficeApp
     g_hhook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc,g_hInst,idThread);
     if(!g_hhook)
     {
      MessageBoxW((HWND)hTarget,TEXT("SetWindowsHookEx?"),TEXT("FALSE"),0);
      return false;
     }

     return true;
    }

    // フックを解除する
    bool CALLBACK UnHook(void)
    {
     LPVOID lpMsgBuf;

     MessageBoxW((HWND)hTarget,TEXT("UnHookStart"),TEXT(""),0);

     if(g_hhook != NULL)
     {
      MessageBoxW((HWND)hTarget,TEXT("UnhookWindowsHookEx"),TEXT(""),0);
     
      //★ここの処理で問題がおきている?
      UnhookWindowsHookEx(g_hhook);

      //★ここ以降のメッセージが表示されない
      FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
       NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );

      MessageBox( (HWND)hTarget, (LPCTSTR)lpMsgBuf, TEXT("SetWindowLongPtr"), MB_OK  );
      LocalFree( lpMsgBuf );
     }
     g_hhook = NULL;

     //初期化済みフラグ解除
     g_bInitHook=false;

     //★ここも出ない
     MessageBoxW((HWND)hTarget,TEXT("UnHookEnd"),TEXT(""),0);

     return true;
    }

     

    2010年12月29日 7:56
  • 以下のコードでようやく解除も出来ました。
    ようやく機能を満たすコードが出来ましたので質問をCloseしたいと思います。
    ご回答いただいた皆様ありがとうございました。
    他の人の参考になるように書いたコードを残しておきます。
    一個上で乗せたコードのUnHookを下のコードと差し替えれば動作します。

    LRESULT CALLBACK CallUnHookWndProc(int nCode, WPARAM wParam, LPARAM lParam)
    {
     return SetWindowLongPtr(hTarget, GWLP_WNDPROC, (__int3264)(LONG_PTR)g_oldWndProc);
    }

    // フックを解除する
    bool CALLBACK UnHook(void)
    {
     HMODULE g_hInst = GetModuleHandle(TEXT("Hook"));
     g_hhook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallUnHookWndProc,g_hInst,idThread);
     if(!g_hhook)
     {
      return false;
     }

     g_hhook = NULL;

     //初期化済みフラグ解除
     g_bInitHook=false;

     return true;
    }

     

    • 回答としてマーク おれおれ 2011年1月7日 1:31
    2011年1月7日 1:31