windows_LowLevelKeyboardProcでキーボード入力をフックしてコードを変更してみる
Windows環境 LowLevelKeyboardProcで入力されたキーコードを変更する
LowLevelKeyboardProcはwindows環境でsetWindowsHookEx関数と併用してキーボード入力があったときに呼び出されるコールバック関数になります。アクティブなウィンドウがなにかにかかわらずコールバックで呼び出されるので、これを利用して入力されたキーコードを変更するいたずらを実行してみたいと思います。
visual c++のフォームプロジェクトを作成して、以下の手順で修正します。
まず、setWindowsHookExでLowLevelKeyboardProcにコールバックさせる処理は以下のようになります。
Hook.cpp
#include "stdafx.h" #include "Hook.h" HHOOK hMyHook; LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wp, LPARAM lp) { KBDLLHOOKSTRUCT* kbs =(KBDLLHOOKSTRUCT*)lp; if (!(kbs->flags & LLKHF_INJECTED)) { INPUT input; input.type = INPUT_KEYBOARD; input.ki.dwFlags = (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) ? 0 : KEYEVENTF_KEYUP; if (kbs->vkCode >= 'A' && kbs->vkCode <= 'Z') { if (kbs->vkCode == 'Z') { input.ki.wVk = 'A'; } else { input.ki.wVk = kbs->vkCode + 1; } SendInput(1, &input, sizeof(input)); return -1; } else { return CallNextHookEx(hMyHook, nCode, wp, lp); } } else { return CallNextHookEx(hMyHook, nCode, wp, lp); } } bool MyHookStart(HWND hWnd) { HINSTANCE hInst; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); hMyHook = SetWindowsHookEx(WH_KEYBOARD_LL, //フック関数のタイプ (HOOKPROC)LowLevelKeyboardProc, //フックプロシージャのアドレス hInst, //フックプロシージャが入っているインスタンスハンドル 0); //フックされるスレッド 0ならすべてのスレッド if (hMyHook == NULL) { MessageBox(hWnd, "フック開始に失敗", "Error", MB_OK); return false; } else { return true; } } bool MyHookEnd(HWND hWnd) { if (UnhookWindowsHookEx(hMyHook) != 0) { return true; } else { MessageBox(hWnd, "フック解除に失敗", "Error", MB_OK); return false; } }
setWindowsHookExを呼び出してhookを開始しているのはMyHookStart関数内の以下の箇所になります。
hMyHook = SetWindowsHookEx(WH_KEYBOARD_LL, //フック関数のタイプ (HOOKPROC)LowLevelKeyboardProc, //フックプロシージャのアドレス hInst, //フックプロシージャが入っているインスタンスハンドル 0); //フックされるスレッド 0ならすべてのスレッド
SetWindowsHookExに渡している第一引数はフック関数のタイプとなり、今回はLowLevelkeyboardProcなのでWH_KEYBOARD_LLを渡しています。 フック関数のタイプは公式のドキュメントに一覧があります。
フックプロシージャが入っているインスタンスハンドルは自分自身なので、以下の処理でハンドルを取得しています。
hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
それからコールバックで呼び出す関数としてLowLevelKeyboardProcを定義しているので、これをSetWindowsHookExに渡しています、が公式のドキュメントではフックプロシージャが現在のプロセスに関連付けられているコード内に存在する場合、hMod パラメータでNULLを指定しなければなりませんとあるのでNULLを渡すのが正しそうです。
フック対象は全てのスレッドなので0を渡しています。
hookの終了は UnhookWindowsHookEx(hMyHook)
の箇所になり、MyHookEnd(HWND hWnd)関数内で呼び出しています。
それから、LowLevelKeyboardProc
関数内でキーコードの入力変換を行っておりa ~ zのキーの入力を一つづらすようにしています(a->b, b->c,,,,z->a)。
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wp, LPARAM lp) { KBDLLHOOKSTRUCT* kbs =(KBDLLHOOKSTRUCT*)lp; if (!(kbs->flags & LLKHF_INJECTED)) { INPUT input; input.type = INPUT_KEYBOARD; input.ki.dwFlags = (wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN) ? 0 : KEYEVENTF_KEYUP; if (kbs->vkCode >= 'A' && kbs->vkCode <= 'Z') { if (kbs->vkCode == 'Z') { input.ki.wVk = 'A'; } else { input.ki.wVk = kbs->vkCode + 1; } SendInput(1, &input, sizeof(input)); return -1; } else { return CallNextHookEx(hMyHook, nCode, wp, lp); } } else { return CallNextHookEx(hMyHook, nCode, wp, lp); } }
このヘッダーファイルは以下のようになります。
Hook.h
#pragma once #include<Windows.h> bool MyHookStart(HWND hWnd); bool MyHookEnd(HWND hWnd);
あとはフォーム内でボタンをクリックしてhookの開始、終了を呼び出せるようにしたら完成です。自分は以下のようにフォームを作成しました。
Form1.h
#pragma once #include "Hook.h" #pragma comment(lib, "User32.lib") namespace CppCLR_WinformsProjekt { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; /// <summary> /// Zusammenfassung f・ Form1 /// </summary> public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); // //TODO: Konstruktorcode hier hinzuf・en. // } protected: /// <summary> /// Verwendete Ressourcen bereinigen. /// </summary> ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label1; protected: private: /// <summary> /// Erforderliche Designervariable. /// </summary> System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Erforderliche Methode f・ die Designerunterst・zung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor ge舅dert werden. /// </summary> void InitializeComponent(void) { this->button1 = (gcnew System::Windows::Forms::Button()); this->button2 = (gcnew System::Windows::Forms::Button()); this->label1 = (gcnew System::Windows::Forms::Label()); this->SuspendLayout(); // // button1 // this->button1->Location = System::Drawing::Point(33, 53); this->button1->Name = L"button1"; this->button1->Size = System::Drawing::Size(101, 40); this->button1->TabIndex = 0; this->button1->Text = L"start"; this->button1->UseVisualStyleBackColor = true; this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click); // // button2 // this->button2->Location = System::Drawing::Point(152, 53); this->button2->Name = L"button2"; this->button2->Size = System::Drawing::Size(101, 40); this->button2->TabIndex = 1; this->button2->Text = L"stop"; this->button2->UseVisualStyleBackColor = true; this->button2->Click += gcnew System::EventHandler(this, &Form1::button2_Click); // // label1 // this->label1->AutoSize = true; this->label1->Font = (gcnew System::Drawing::Font(L"MS UI Gothic", 22)); this->label1->Location = System::Drawing::Point(37, 9); this->label1->Name = L"label1"; this->label1->Size = System::Drawing::Size(156, 30); this->label1->TabIndex = 2; this->label1->Text = L"HOOK OFF"; this->label1->Click += gcnew System::EventHandler(this, &Form1::label1_Click); // // Form1 // this->AutoScaleDimensions = System::Drawing::SizeF(6, 12); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(283, 105); this->Controls->Add(this->label1); this->Controls->Add(this->button2); this->Controls->Add(this->button1); this->Name = L"Form1"; this->Text = L"Form1"; this->ResumeLayout(false); this->PerformLayout(); } #pragma endregion private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { HWND hWnd = static_cast<HWND>(this->Handle.ToPointer()); if( MyHookStart(hWnd)) { this->label1->Text = L"HOOK ON"; } } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { HWND hWnd = static_cast<HWND>(this->Handle.ToPointer()); if (MyHookEnd(hWnd)) { this->label1->Text = L"HOOK OFF"; } } private: System::Void richTextBox1_TextChanged(System::Object^ sender, System::EventArgs^ e) { } private: System::Void label1_Click(System::Object^ sender, System::EventArgs^ e) { } }; }
あとはvisual studioでデバック実行したあとにhookを開始して、メモ帳などで入力を行うとキーコードが変更されて入力されることが確認できると思います。