WideStudio Programming (4-2)
~メッセージループへの介入

一口にメッセージループに介入すると言っても、メッセージループの処理自体に介入するのは困難です。ここでは自前のメッセージ処理を追加したいだけですから、ウィンドウプロシージャを入れ替えて、自前の処理が必要なら実行するし、必要無ければ元のウィンドウプロシージャを呼び出して処理させる、というやり方(いわゆるサブクラス化)でいけるでしょう。

ウィンドウプロシージャの入れ替えは Windows API の SetWindowLong() をこんな風↓に使うことで可能です。

WNDPROC old_proc = SetWindowLong(hwnd,GWL_WNDPROC,
               (DWORD)新しいプロシージャになる関数へのポインタ);

戻り値は元のウィンドウプロシージャを呼び出すための情報です。ここで戻されるのは単純なウィンドウプロシージャのアドレスという訳ではないようで、WNDPROC型で宣言しておけば関数形式で呼び出すコードは書けますが、実行すると落ちます。自分で処理しないウィンドウメッセージは

CallWindowProc(old_proc,hWnd,message,wParam,lParam);

のように、CallWindowProc APIで元のウィンドウプロシージャを呼び出して処理させます。

Windowメッセージを追加で処理したい場合(ウィンドウをサブクラス化したい場合)というのは他にもあるかも知れず、また、一旦追加した処理を取り外したいということもあるかも知れません。また、グローバル関数やstaticメンバ関数ではなく、クラス(インスタンス)のメンバ関数を直接呼んで欲しい気もします。というわけで、タンゴレンではこの辺りの仕掛けを汎用化したクラス(CALWndProcHook)を作っています。クラスの宣言から重要なところをざっくり抜粋しておきます。

#include <windows.h>
#include <map>
class WSCbase;
class CALWndProcHook {
public:
    typedef LRESULT (CALLBACK WndProc_t)(
        HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam
        );
protected:
    CALWndProcHook() {}
    explicit CALWndProcHook(WSCbase* win,bool use_special=false) { RegisterHook(win,use_special); }
    virtual ~CALWndProcHook();

    HWND RegisterHook(WSCbase* win,bool use_special=false);
    void UnregisterHook();

    virtual bool MyWndProc(
        LRESULT& lResult,
        WSCbase* win,
        HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam
        ) = 0;

private:
    static LRESULT CALLBACK RawWndProc(
        HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam
        );

    struct WPReplacement {
        WSCbase*	_win;
        CALWndProcHook*	_pthis;
        WPReplacement() : _win(NULL), _pthis(NULL) {}
        WPReplacement(WSCbase* win,CALWndProcHook* pthis)
            : _win(win), _pthis(pthis) {}
    };
    static std::multimap<HWND,WPReplacement>	_wp_replacement_map;
    static std::map<HWND,WndProc_t*>	_old_wndproc_map;

};

このクラスは純粋仮想クラスです。必ず派生させて使います。純粋仮想関数MyWndProcを派生先クラスでオーバーライドして、MyWndProcの中には必要な処理のみ記述します。MyWndProcの戻り値によって、更に元のウィンドウプロシージャを呼び出す必要があるかどうかが判断されます。

private static 関数 RawWndProc() は、実際に置き換えられるウィンドウプロシージャです。RawWndProc()はHWNDをキーにした WPReplacement 構造体のstaticなマルチマップを使って、同じHWNDに対して登録されているすべての派生クラスのインスタンスのMyWndProc()を、呼び出します。

マップへのインスタンスの登録とウィンドウプロシージャの置き換え(同じHWNDに対する初回の呼び出し時のみ)はRegisterHook()メンバ関数を呼び出すことで行なわれますが、WSCbase*を引数に持つコンストラクタで実体化していれば、明示的に呼び出す必要はありません。

RegisterHook()はWSCbaseに結びついているHWNDを返してくるので、実は、前項のDragAcceptFiles()に渡しているhwndは、このRegisterHook()の戻り値を使っています。本当は、

    HWND hwnd = _pimpl->RegisterHook(win);
    DragAcceptFiles(hwnd,TRUE);

となっています。

より詳しいことが知りたい方は、 http://www.attocraft.jp/contents/archive/DragAndDrop_ver1.0.zip にソースを置いておきますので、よろしければご覧下さい。

と、ここまではVersion1.0のタンゴレンの実装ですが、このやり方には大きな問題が潜んでいました。その話は次のエントリにて。

(続く)