BCBでグローバルフック(システムフック)を使用する


※以下の内容はメモ的内容であるため、動作については一切補償しません。従って、 このプログラムによって生じた、いかなる損害についても責任を負いかねますので予めご了承ください。

ランチャソフトなどのアプリケーションを開発していると、 アプリケーションにフォーカスが無いときでもマウスの動作などによって、 アプリケーションを前面に移動させたいなどの要求が出てきます。 この時には、自分のWindowとは関係のないWindowMessageを監視する必要がありグローバルフック(システムフック)を使用する必要がでてきます。

今回はBorland C++ Builder 6(BCB6)を使用した、グローバルフック(システムフック)の例を示します。 今回作成したものは、キーボード入力によりホストアプリケーションを閉じる物です。

グローバルフック(システムフック)の実装方法

1.DLL上にフックプロシージャを実装する。
2.共有メモリ上にフックプロシージャのハンドル及びホストアプリケーションのハンドルを格納する。
3.ホストアプリケーションからフックをセットする。

ここで、VC++とは異なり、Borland C++ Builder 6(BCB6)を用いる際のポイントは 2.の共有メモリ上にハンドルを格納する部分です。 これはVC++が#pragma data_seg文と定義ファイル(*.def)内のSECTIONSを用いる事により簡単に共有メモリを用意できるのに対し、 Borland C++ Builder 6(BCB6)ではできないからです。 そこで、今回はメモリマップドファイルを用いて共有メモリを用意して実装を行いました。

ホスト側ソース

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

HANDLE hHookLib;
typedef bool(*PSETHOOK)(void* Handle);
typedef bool(*PFREEHOOK)(void);
PSETHOOK  fpSetHook;
PFREEHOOK fpFreeHook;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    hHookLib=LoadLibrary("DLLProject.dll");
    fpSetHook =(PSETHOOK)GetProcAddress(hHookLib,"_SetHook");
    fpFreeHook =(PFREEHOOK)GetProcAddress(hHookLib,"_FreeHook");
    (*fpSetHook)(Application->Handle);
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
    (*fpFreeHook)();
    FreeLibrary(hHookLib);
}
//---------------------------------------------------------------------------

DLL側ソース

#include <windows.h>

//DLLの関数を外から呼び出せるようにする。
#define EXPORT extern "C" __declspec(dllexport)
EXPORT bool SetHook(void *Handle);
EXPORT bool FreeHook(void);


//共有メモリの構造体定義
struct SHARED_DATA{
    HWND  HostWinHandle;
    HHOOK m_hHook;
};

//関数のプロトタイプ宣言
LRESULT CALLBACK HookProc(int p_nCode, WPARAM p_wParam, LPARAM p_lParam);
int MapFileMemory(HANDLE *hMap,SHARED_DATA **Shared);
void UnMapFileMemory(HANDLE hMap,SHARED_DATA *Shared);


//DLL内変数
static char *MapFileName="_Test_Shear_Memory";
static HANDLE hMapFile;
static HINSTANCE g_hDllInst = NULL;
static bool fResult;
static HANDLE hMap;
static SHARED_DATA *Shared;


#pragma argsused
//=============================================================================
//  DLLのエントリーポイント
//=============================================================================
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, LPVOID lpvReserved)
{
    switch (fwdreason){
    case DLL_PROCESS_ATTACH:
        g_hDllInst = hinstDLL;
        hMapFile=CreateFileMapping((HANDLE)0xffffffff,NULL,PAGE_READWRITE,0,sizeof(SHARED_DATA),MapFileName);
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        if(hMapFile!=NULL)
            CloseHandle(hMapFile);
        break;
    }
    return TRUE;

}
//---------------------------------------------------------------------------

//=============================================================================
//  フック関数の登録
//  登録するフック関数はSetHook
//=============================================================================
EXPORT bool SetHook(void *Handle)
{
    //メモリマップドファイル使用準備
    MapFileMemory(&hMap,&Shared);
    if(Shared==NULL)
        return false;

    //共有メモリの設定とフック関数の登録
    Shared->HostWinHandle=Handle;

    //フックをインストール
    hared->m_hHook = ::SetWindowsHookEx( WH_KEYBOARD
            , (HOOKPROC)HookProc
            , g_hDllInst
            , 0  );
    if( !Shared->m_hHook )
        return false;

    //メモリマップドファイル使用終了処理
    UnMapFileMemory(hMap,Shared);

    return true;
}
//=============================================================================
//  フックの解除
//=============================================================================
EXPORT bool FreeHook()
{
    bool bRes;

    //メモリマップドファイル使用準備
    MapFileMemory(&hMap,&Shared);
    if(Shared==NULL)
        return false;



    if( Shared->m_hHook )
        bRes = ::UnhookWindowsHookEx( Shared->m_hHook );

    Shared->m_hHook = NULL;

    //メモリマップドファイル使用終了処理
    UnMapFileMemory(hMap,Shared);

    return bRes;
}

//=============================================================================
//  フック関数
//  今回の内容はキーボード入力を感知して呼び出し元アプリケーションを終了する
//=============================================================================
LRESULT CALLBACK HookProc(int p_nCode, WPARAM p_wParam, LPARAM p_lParam)
{
    //メモリマップドファイル使用準備
    MapFileMemory(&hMap,&Shared);
    if(Shared==NULL)
        return false;

    if( p_nCode < 0 || p_nCode == HC_NOREMOVE ){
        fResult=::CallNextHookEx( Shared->m_hHook, p_nCode, p_wParam, p_lParam );
        UnMapFileMemory(hMap,Shared);
        return fResult;
    }

    ::PostMessage( Shared->HostWinHandle, WM_CLOSE, 0,0);
    ::CallNextHookEx( Shared->m_hHook, p_nCode, p_wParam, p_lParam );

    UnMapFileMemory(hMap,Shared);
    return TRUE;
}


//=============================================================================
//   共有メモリアクセスのための準備
//
//   成功すると0を返す.失敗すると負数を返す.
//   負の値で処理を分岐できるようになっているが,このコードでは未使用.
//=============================================================================
int MapFileMemory(HANDLE *hMap,SHARED_DATA **Shared)
{
    *hMap=OpenFileMapping(FILE_MAP_ALL_ACCESS,false,MapFileName);
    if(*hMap==NULL)
        return -1;
    *Shared=(SHARED_DATA*)MapViewOfFile(*hMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SHARED_DATA));
    if(*Shared==NULL){
        CloseHandle(*hMap);
        return -2;
    }
    return 0;
}

//=============================================================================
//   共有メモリアクセスの後始末
//   割当てたメモリがあればビューを解除
//   ハンドルがあればクローズ
//=============================================================================
void UnMapFileMemory(HANDLE hMap,SHARED_DATA *Shared)
{
    UnmapViewOfFile(Shared);
    CloseHandle(hMap);
}

参考

フックのしくみ
グローバルフックWH_GETMESSAGE
メモリマップドファイルを使ったデータの共有
MapViewOfFile ファイルマッピングオブジェクトをメモリ上にマップする

履歴

2008/06/29

HookProcでUnMapFileMemoryが呼び出されない場合があったことを修正した。 また、HookProcで使用する変数を呼び出し時の負荷軽減のためにグローバル化した。