2006年01月22日
川俣晶の縁側ソフトウェア技術雑記 total 15639 count

C++/CLIとSetWindowsHookEx APIを使ったキーボードフックの試行

Written By: 川俣 晶連絡先

 NyaRuRuさんが■[.NET]C++/CLIなら面倒は無い?で、私の書いた「余談」に意見を述べていただきました。

 そこで、実際にC++/CLIでキーボードフックのプログラムを書いてみました。

 昔のソースを引っ張り出して、ちょっと調べて出てきた断片的な情報をつなぎ合わせて書いたものなので、これで完全かどうかは分かりません。また、正しいかどうか、適切かどうかも分かりません。ま、とりあえず動くことは動いた……ということで (汗。

ソース一式 §

 とりあえず動いた状態のツリー丸ごと(一部削除)です。

 Debugディレクトリの実行ファイルを走らせると動きます。

Visual Studio 2005用サンプルソース

 正常に動作した場合、別ウィンドウのウィンドウアプリケーションにフォーカスを設定してTABキーを押すとBeepが鳴ります。

コンセプト §

 面倒なことは全て優秀なマーシャラーにお任せ、ということで難しいことは何も考えていません。

 基本的には、以下の2段構造です。

  • フックDLLを起動するC++/CLIのコンソールアプリケーション
  • C++/CLIで書いたmanagedなフックDLL本体 (C++/CLIのクラスライブラリのプロジェクト)

 自分で書いたコードは、基本的に全てmanagedコードとして動作します。unmanagedコードとの境界は、自動的に挿入されるC++/CLIのマーシャラーがすべて受け持ちます。

問題点 §

 実行を終了させてもフックDLLをどこかのプロレスが持ち続けて解放しない、という現象が起きるようです。DLLをどこでロード、アンロードするかはシステムに任せっきりなので、そのあたりをもうちょっと煮詰める必要があるでしょう。ただし、アーキテクチャ的に解決可能な問題なのか、それともできないのかは分かりません。

サンプルソースの主要部分 §

// hookdll.h

#pragma once

using namespace System;

namespace hookdll {

static HHOOK hhook;

extern "C" LRESULT CALLBACK KeyboardProc( int code, 

           WPARAM wParam, LPARAM lParam )

{

    //Console::WriteLine("here!");

    if( code < 0 ) return CallNextHookEx( hhook, code, wParam, lParam );

    if( wParam == VK_TAB ) {

        MessageBeep(0);

    }

    //return 0;

    return CallNextHookEx( hhook, code, wParam, lParam );

}

    public ref class Hooker

    {

    public:

        void static SetHook()

        {

           MessageBeep(0); // 音が出ることを確認するために、ここで1回鳴らしておく

           HINSTANCE hInstance;

           BOOL result = GetModuleHandleEx(

                         GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS

                         |GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,

                (LPCTSTR)KeyboardProc,

                (HMODULE*)&hInstance );

            Console::WriteLine("hInstance={0}",(int)hInstance);

            Console::WriteLine("result of GetModuleHandleEx={0}",result);

            hhook = SetWindowsHookEx( WH_KEYBOARD, 

                (HOOKPROC)KeyboardProc, hInstance/*hInst*/, NULL );

            Console::WriteLine("hhook={0}",(int)hhook);

        }

        void static ResetHook()

        {

            BOOL result = UnhookWindowsHookEx( hhook );

            Console::WriteLine("result of UnhookWindowsHookEx={0}",result);

        }

    };

}

感想 §

 半ば予想されたこととはいえ、あまりにもあっさりしたコードで実現したのはびっくり。

 しかも、10年以上前に書いたフックのソースをちょいちょいと手直ししただけなのに。

 しかし、休日出勤してるのに、こんなものを書いてる暇はないのだ! 仕事をしなさい→私。

(2006年1月25日追記)上記のコードには欠陥 §

 上記のコードには欠陥があることをNyaRuRuさんが明らかにしました。

 下記参照。