キーボードフック

投稿者: | 2012/11/15

システムフックの方法が分かったのであとはフックプロシージャでキーボードの仮想キーを保存してやればいい、ということでやってみたけど実際やってみるとけっこうはまりポイントがいっぱいあった。

 

とりあえず処理フローは以下の通り。ほぼシステムフックのときと同じ。

  1. BeginHook()でフック
  2. ローレベルキーボードフックでキーボードのkey downなのかkey upなのかと仮想キーの番号を取得し、これを文字列として共有領域に設定した配列に書き込み。
  3. EndHook()で貯めたフック情報を書き込み。フック終了

ローレベルキーボードフックコード

#include "stdafx.h"

#define DLL_API extern "C" __declspec( dllexport )

#define MAX_KEY 512
#define MAX_KEY_LEN 32

// 共有領域
#pragma data_seg(".shareddata")
HHOOK hMyHook=0;
int key_count = 0;
char key_str_array[MAX_KEY][MAX_KEY_LEN] = { {""} };
int prev_key_state = 0;
int prev_key_num = 0;
#pragma data_seg()

HMODULE hInst;

// -------------------------------------------------
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                         )
{
     if(ul_reason_for_call==DLL_PROCESS_ATTACH){
          hInst = hModule;
     }

     return TRUE;
}

// -------------------------------------------------
// コマンドを記録
// -------------------------------------------------
void RegistKey(int key_state, int key_num, char* key){

     // まったく同じコマンドは受け付けない
     if(prev_key_state==key_state && prev_key_num==key_num) return;

     if(key_countvkCode);
     break;
     }
}

// -------------------------------------------------
// キーリストの保存
// -------------------------------------------------
void WriteKeyList(){
     if(key_count==0)     return;

     FILE* fp = NULL;
     errno_t err = fopen_s(&fp, "key_list.txt", "w");
     if(err==0){

          for(int i=0; i               fwrite(key_str_array[i], strlen(key_str_array[i]), 1, fp);
          }

          fclose(fp);
     }
}

// -------------------------------------------------
LRESULT CALLBACK MyHookProc(int nCode, WPARAM wParam, LPARAM lParam){
     if(nCode==HC_ACTION){

          // hook
          RegistKey(wParam, lParam);
     }
    return CallNextHookEx(hMyHook, nCode, wParam, lParam);
}
// -------------------------------------------------
DLL_API bool BeginHook(){

     hMyHook = SetWindowsHookEx(
          WH_KEYBOARD_LL,        // フックタイプ
          (HOOKPROC)MyHookProc,  // フックプロシージャのアドレス
          hInst,                 // ローカルフックではNULL。システムフックではDLLのハンドル。
          0);                    // フックされるスレッド

     if(hMyHook == NULL){
          MessageBoxA(NULL, "Failed to set hook!", "Error", MB_OK);
          return false;
     }

     return true;
}

// -------------------------------------------------
DLL_API void EndHook(){

     WriteKeyList();
     if(UnhookWindowsHookEx(hMyHook) != 0){
//          MessageBoxA(NULL, "Delete hook.", "OK", MB_OK);
     }
     else{
          MessageBoxA(NULL, "Failed to delete hook!", "Error", MB_OK);
     }
}

コードではSHIFTなどをずっと押していると何度もプロシージャが呼び出されてしまうので全く同じキーは受け付けないようにした。(例えばKEY_DOWNでかつVK_SHIFTの組み合わせが連続で来たら無視する、といった感じ。)
上記のコードはDLL側のコードだが実行ファイル側は前回書いたシステムフックとまったく同じでいい。

はまりポイントがいっぱいあったので列挙しておく。

  • #pragma data_segで囲んだ共有領域ではポインタは扱えない。
    基本的にプロセスAからプロセスBのメモリにアクセスできないからだ。仕方ないから固定配列を使うことにした。
    http://msdn.microsoft.com/ja-jp/library/h90dkhs0(v=vs.90).aspx
  • キーボードフックすると2度プロシージャを通る
    これはkey downとkey upの分がプロシージャにくるからだ。これではキーの同時押しが扱えない。(大文字とかコピーペーストとか。)調べてみるとSetWindowsHookExの第一引数に指定するフックタイプにキーボードフックの別タイプWH_KEYBOARD_LLがある。これでより詳細なキーボードフックができる。
    フックタイプのついて
    http://msdn.microsoft.com/ja-jp/library/cc430103.aspx
  • WH_KEYBOARD_LLだとWH_KEYBOARDと違ってフックプロシージャ引数の扱いが全く違う
    wParamにはWH_KEYBOARDでは仮想キーが入っているけどWH_KEYBOARD_LLではWM_KEYDOWNやWM_KEYUPなどが書き込まれている。また注意点があって英数字などはWM_KEYDOWNやWM_KEYUPで入ってくるけどALTはWM_SYSKEYDOWNとWM_SYSKEYUPという値で入ってくる。英数字キーと同様に扱おうとしてはまった。
    http://msdn.microsoft.com/ja-jp/library/cc429971.aspx

これで一応必要な情報をキーボードフックで得ることができた。次はこの保存したコードを読み込んで自動で実行したい。
続きは次回で。


コメントを残す

メールアドレスが公開されることはありません。