キーフックについて再度質問

解決


RITSU  2007-06-06 14:43:58  No: 26519

キーフックについて、再度質問させていただきます。

やりたいことは、グローバルキーフックを行い、例えばメモ帳で何かキーが押されたら「あいうえお」と入力させる、などをしたいのです。
そこで、それを実現するため次のようなコードを書いてみました。
雛形として以前ここで質問にお答え頂いたMr.XRAYさんのコードを元とさせて
いただいていますm(_)m

------------------------------DLLのコード----------------------------

library KeyHook;

uses
  Windows,SysUtils,
  Messages;

var
   hHookCallWndProc:integer;
   MainFormHandle: THandle;

//===================================================================
//  フックのコールバック関数
//  このDLLを使用したアプリにキーコードを送る。
//  メッセージIDはWM_APP+100を使用。
//===================================================================
function KeyWndProc(nCode:integer;wParam:integer;lParam:integer):
         integer; stdcall;
begin
     if nCode < 0 then begin
       Result := CallNextHookEx(hHookCallWndProc, nCode, wParam, lParam);
     end else begin;
       Result := CallNextHookEx(hHookCallWndProc, nCode, wParam, lParam);
       if nCode=HC_ACTION then begin
          Result:=-1;
          MainFormHandle := FindWindow('TForm1',nil); //(念のため)
          if lParam > 0 then //キーを押したとき
            PostMessage(MainFormHandle,WM_APP+100,Wparam,0); 
       end;
     end;
end;
//===================================================================
//  フック関数の登録
//  登録するフック関数はKeyBoardProc
//===================================================================
function  StartKeyHook(AppHandle:THandle): Boolean; stdcall;
var
  Ret:Integer;
begin
  MainFormHandle := AppHandle;
  Result:=False;
  Ret:=SetWindowsHookEx(WH_KEYBOARD,@KeyWndProc,HInstance,0);
  if Ret=0 then Exit else hHookCallWndProc:=ret;
  Result:=True;

end;
//===================================================================
//  フックの解除
//===================================================================
procedure StopKeyHook; stdcall;
begin
     UnhookWindowsHookEx(hHookCallWndProc);
end;
//===================================================================
//  外部からDLL内のメソッドを利用可能にするためのオマジナイ
//===================================================================
exports
      StartKeyHook,
      StopKeyHook;
begin

end.

--------------------------プログラムのコード-------------------------

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  protected
    procedure WndProc(var msg: TMessage);override;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

function StartKeyHook(Wnd: HWND): Boolean; stdcall; external 'KeyHook.dll';
procedure StopKeyHook; stdcall; external 'KeyHook.dll';

var
   hHookLib    : THANDLE;
   HookFlag    : Boolean;

{$R *.DFM}

//===================================================================
//  本Form破棄の時にはフックを解除
//===================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
     StopKeyHook;
     FreeLibrary(hHookLib);
end;
//===================================================================
//  Application側でメッセージ通知を受取る
//  ここではフォームのWndProcメソッドを使用
//  キーを感知したらアクティブウィンドウに「あいうえお」を送る。
//===================================================================
procedure TForm1.WndProc(var msg: TMessage);
begin
     if (msg.Msg=WM_APP+100) then begin

      //★★★★★
      StopKeyHook;
      Keybd_event(Byte('A'),0,0,0);
      Keybd_event(Byte('A'),0,KEYEVENTF_KEYUP,0);
      Keybd_event(Byte('I'),0,0,0);
      Keybd_event(Byte('I'),0,KEYEVENTF_KEYUP,0);
      Keybd_event(Byte('U'),0,0,0);
      Keybd_event(Byte('U'),0,KEYEVENTF_KEYUP,0);
      Keybd_event(Byte('E'),0,0,0);
      Keybd_event(Byte('E'),0,KEYEVENTF_KEYUP,0);
      Keybd_event(Byte('O'),0,0,0);
      Keybd_event(Byte('O'),0,KEYEVENTF_KEYUP,0);
      StartKeyHook(Form1.Handle);
      //★★★★★

     end;
     inherited WndProc(msg);
end;
//===================================================================
//  キーフックを有効にする
//===================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
     HookFlag :=False;
     try
       if hHookLib=0 then hHookLib := LoadLibrary('TabKeyHook.dll');
       StartKeyHook(Form1.Handle);
     except
     end;
end;
//===================================================================//  キーフックを無効にする
//===================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
     StopKeyHook;
end;

end.

以上、これで実行しメモ帳でキーを押すとどうもprocedure TForm1.WndProc(var msg: TMessage);
がループされているかのようにキャレットが固まってフリーズするような感じに
なってしまいます。

★★★で囲まれた部分を、beepとかshowmessageに変えてやれば、キーボードを押した時に
普通に一回それらが出て終わりなので、その部分以外は特に問題無いようです。
また、★の中で、StartKeyHook(Form1.Handle);を除いてそこで止めてしまえば
一回きりですが変換は上手く行くようです。

色々考えたのですが、どうしても分からずここで手が詰まっています。。
どなたかお分かりの方、宜しくお願いします。


RITSU  2007-06-06 14:46:24  No: 26520

すいません、一部改行が変になってしまいました。


RITSU  2007-06-06 23:33:40  No: 26521

環境はDelphi6Pro+WindowsXP(sp2)です。


SHIMAPEE  2007-06-08 12:08:27  No: 26522

下記を対策するだけで最終的に実現したいことができるかどうかわかりませんが、

SetWindowsHookExでグローバルフックを仕掛ける場合、戻り値 hHookCallWndProc をフック関数 KeyWndProc に飛び込んでくる各プロセスで参照できるように工夫しなければなりません。

C++でしたら共有データセクション(#pragma data_seg)に置く方法が定番のようですが、Delphiではメモリマップドファイルを使う方法くらいしかないようです。

「Delphi メモリマップドファイル」などでgoogleすると参考になる情報が検索できます。


RITSU  2007-06-12 08:45:36  No: 26523

>SHIMAPEE様

回答ありがとうございます。メモリマップドファイルというものを
初めて知りました。

それでコードを書き直してみたのですが、やはり同じ箇所で上手く
行かないようです。とりあえず回避策として、間隔1のtimerを貼り付けて、
それを噛ませれば固まることは無くなったのですが、早く動かすと若干
挙動が変になるときもあります。

うーん・・なかなか難しいようで。。


SHIMAPEE  2007-06-17 09:13:41  No: 26524

少し試してみました。

全てのキーをフックすることには成功していませんが、例えばTABキーを
'aiueo'にすりかえることには成功しました。

なお、SetWindowsHookExとUnhookWindowsHookExを繰り返すのはオーバ
ヘッドが大きいのではないかと思い、フラグを使ってみました。
【DLL側】
・共有データにフラグを新設。
・フラグをTrueにするprocedureを新設してexportもしておく。
・フックを登録したらフラグをTrue。
・フック関数内でフラグTrueなら処理。処理を開始したらフラグをFalse。
【プログラム側】
・フラグをTrueにするprocedureをexternal宣言。
・StopKeyHookとStartKeyHookは削除。'aiueo'を入力したらフラグをTrue。
  
Windows XP SP2 + Delphi2007 + メモ帳と秀丸エディタで確認しました。
TABキーの取りこぼしはなさそうです。


RITSU  2007-06-19 07:57:30  No: 26525

ありがとうございます。
そのようにしたところ、上記の問題は解決しました。
フラグを使うのは良い方法みたいですね。
丁寧な説明で大変分かりやすかったです。


Fusa  2007-06-23 10:36:44  No: 26526

元スレ
フックしたキーコードをAppで受け取るには?
https://www.petitmonte.com/bbs/answers?question_id=4807

か、このスレか
どちらに書こうか非常に迷いましたがとりあえず、新しい方のこちらに書きます。

私も追随して勉強させていただいたのでようやく理解できました。
私にとって、キーフックに関して役立ったURLを書いておきます。

先のスレにも登場していましたが

Gen's 不定期ローテク講座 〜 using Hook
file:///C:/MyFolder/MyData/Programing/Delphi/MyDelphiFolder/OpenSource/★要チェックDelphiコード/フック/Gen's/Gen's%20不定期ローテク講座%20〜%20using%20Hook.html

ここは、Delphi初期のページですが、確かにばっちり書かれています。
しかし、微妙に脱線して読みにくかったりで、私は数年来理解できていませんでした。今日、はじめて理解できたよ、この難解なページが。かなりうれしい。

で、その理解の助けになったのが
DelWiki - Tips/フックとかやってみる
http://delwiki.info/?Tips%2F%A5%D5%A5%C3%A5%AF%A4%C8%A4%AB%A4%E4%A4%C3%A4%C6%A4%DF%A4%EB

もっと、理解の助けになったのが、文章はほとんどないけど
正確なサンプルソースを提供していただけているここ。

TechnoCity TIPS
http://hp.vector.co.jp/authors/VA003525/tips0.htm

応用はXRAYさんのところを利用するとよいでしょう。
サンプルプログラム集 フック関数の種類
http://homepage2.nifty.com/Mr_XRAY/Delphi/plSamples/KindOfHook.htm

よいページと情報を提供してくれている、みなさんに感謝。


Fusa  2007-06-23 10:38:30  No: 26527

おっと、リンク先間違った

Gen's 不定期ローテク講座 〜 using Hook
http://www2.biglobe.ne.jp/~sakai/usehook.htm

こちらですね。


Fusa  2007-06-23 10:47:14  No: 26528

たびたび、蛇足なんだけど
お世話になっている、この掲示板のあるこちらのフックサンプル

http://madia.world.coocan.jp/delphi/Tokusen/hook.htm
http://madia.world.coocan.jp/delphi/Tokusen/hook.lzh

こちらのソースは、メモリマップドファイルが使われていないので
不完全なコードになります。安定動作してくれない。

運良ければ動くだろうけど(Win9x系なら動くのか?)
俺の環境じゃ動きませんでした。(Win2K、D2006)

以上、ご参考までに。


RITSU  2007-06-29 08:24:16  No: 26529

あ・・久々に見たら書き込みが^^;

Fusaさん、多くのURLありがとうございます。
フック関連も、がんばって探せば参考になる良いページは色々あるようですね。

ちなみに、一番↑の私のコードですが、フラグを使わずとも、WH_KEYBOARD_LL
で書き換えてやったら、思い通りの動作をしました。
理由は良く分からないのですが、LLのと普通のとでは、こういった違いが起こるようです。
でも本当に勉強になりました。

皆さんのおかげで、多くの問題が解決して感謝です。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加