zoukankan      html  css  js  c++  java
  • 使用钩子函数[4] 钩子链和 CallNextHookEx 的返回值

    SetWindowsHookEx 函数的第一个参数表示钩子类型, 共有 14 种选择, 前面我们已经用过两种:
    WH_KEYBOARD、WH_MOUSE.

    系统会为每一种类型的钩子建立一个表(那就是 14 个表), 譬如某个应用程序启动了键盘钩子, 我们自己的程序也启动了键盘钩子, 同样是键盘钩子就会进入同一个表. 这个表(可能不止一个, 可能还会有鼠标钩子等等)就是传说中的"钩子链".

    假如某个钩子链中共进来了三个钩子(譬如是: 钩子A、钩子B、钩子C 依次进来), 最后进来的 "钩子C" 会先执行.
    是不是先进后出? 我觉得应该说成: 后进先出! 这有区别吗? 有! 因为先进来的不一定出得来.
    最后进了的钩子会最先得到执行, 先前进来的钩子(钩子A、钩子B)能不能得到执行那还得两说, 这得有正在执行的 "钩子C" 说了算.
    如果 "钩子C" 的函数中包含了 CallNextHookEx 语句, 那么 "钩子A、钩子B" 就有可能得以天日; 不然就只有等着相应的
    UnhookWindowsHookEx 来把它们带走(我想起赵本山的小品...).

    这时你也许会想到: 这样太好了, 我以后就不加 CallNextHookEx , 只让自己的钩子"横行"; 但如果是你的钩子先进去的呢?
    所以 Windows 建议: 钩子函数要调用 CallNextHookEx, 并把它的返回值当作钩子函数自己的返回值.

    CallNextHookEx 同时要给钩子链中的下一个(或许应该叫上一个)钩子传递参数(譬如在键盘消息中按了哪个键). 一个键盘钩子和鼠标钩子的参数一样吗? 当然不一样, 所以它们也不在一个 "链" 中啊; 同一个链中的钩子的类型肯定是一样的.

    再聊聊钩子函数的返回值:
    在这之前, 钩子函数的返回值, 我们都是遵循 Windows 的惯例, 返回了 CallNextHookEx 的返回值.
    如果 CallNextHookEx 成功, 它会返回下一个钩子的返回值, 是个连环套;
    如果 CallNextHookEx 失败, 会返回 0, 这样钩子链也就断了, 只有当前钩子还在执行任务.

    不同类型的钩子函数的返回值是不同的, 对键盘钩子来讲如果返回一个非 0 的值, 表示它处理完以后就把消息给消灭了.
    换句话说:
    如果给键盘的钩子函数 Result := 0; 说明消息被钩子拦截并处理后就给 "放" 了;
    如果给键盘的钩子函数 Result := 1; 说明消息被钩子拦截并处理后又给 "杀" 了.

    在下面的例子中, 我们干脆不使用 CallNextHookEx (反正暂时就我一个钩子), 直接给返回值!

    这是接下来例子的演示动画:


    动画中, 我在三种状态下分别给 Memo 输入了字母 a
    当没启动钩子时, Memo 是可以正常输入的;
    当钩子函数返回 0, 钩上以后, 先执行了钩子函数的功能(返回键值), 字母 a 也能输入成功;
    当钩子函数返回非 0 值(譬如1), 钩上以后, 就只执行了钩子函数的功能(返回键值), 可怜的 Memo 不知道发生了什么.

    但这里又有了新问题: 钩子函数返回键值时怎么...不是一个?
    先提醒: 在前面用 Beep 测试时, 你有没有发现那个声音也不只一次, 这是一个道理.
    因为按一次键就会发出两个消息: WM_KEYDOWN、WM_KEYUP, 我们没有指定拦截哪个, 就都拦截了.
    那怎么区分这两个消息呢? 秘密在键盘钩子函数的第三个参数 lParam 里面, 等下一个话题再研究吧.
    //示例代码:
    unit Unit1;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        Button2: TButton;
        Memo1: TMemo;
        procedure FormDestroy(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
      end;
    
      {钩子函数声明}
      function MyKeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    var
      hook: HHOOK;
    
    function MyKeyHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT;
    begin
      Form1.Memo1.Lines.Add(IntToStr(wParam)); {参数二是键值}
      Result := 0; {分别测试返回 0 或非 0 这两种情况}
    end;
    
    {派出钩子}
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      hook := SetWindowsHookEx(WH_KEYBOARD, MyKeyHook, HInstance, GetCurrentThreadId);
      Memo1.Clear;
      Text := '钩子启动';
    end;
    
    {收回钩子}
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      UnhookWindowsHookEx(hook);
      Text := '钩子关闭';
    end;
    
    {如果忘了收回钩子...}
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      if hook<>0 then UnhookWindowsHookEx(hook);
    end;
    
    end.
    
    小秘密: 发现没有, 这次在 SetWindowsHookEx 时, 第二参数(函数地址), 没有使用 @、也没有用 Addr, 怎么也行呢?
    因为函数名本身就是个地址.
  • 相关阅读:
    grab jpegs from v4l2 devices
    c helloworld on zynq
    minicom installation and configuration on ubuntu
    DBA的做法
    sqlserver锁表、解锁、查看销表
    linux内核分析(网课期末&地面课期中)
    Linux内核分析实验八------理解进程调度时机跟踪分析进程调度与
    LINUX内核分析第七周——可执行程序的装载
    Linux内核分析实验六
    Linux内核分析实验五
  • 原文地址:https://www.cnblogs.com/del/p/1083011.html
Copyright © 2011-2022 走看看