写个截图程序之 快捷键

时间:2022-01-15 15:23:11

上一篇写个截图程序之 鼠标绘制区域写了截图绘制的过程,当然截图程序没有快捷键就非常的不方便了,所以不得不去看Hook怎么写的,但是c#方面的查了很多,也下了几个源代码,基本上都是给了个代码或者是给了个过程出来,很是郁闷。以前一个做C++的朋友留了本C++的书,于是把书的第一章.winodws消息机制。最后一章 windows HOOK编程看了一遍。大概知道了是怎么回事,然后又在网上找文章看,大家可以看一下。

做钩子的步骤可以看一下:Hook钩子 C#实例  ,这里就不写具体的过程了 。对于这里只是总结一下。具体的过程我也是复制代码的。

钩子主要就是回调函数

public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

怎么来写。

       由于以前也没弄过着方面的,主要就是里面的三个参数是什么意思,刚开始写的时候我把这三个参数的值打印出来,得到的lParam总是一个固定的数,于是很郁闷,想了很久也不知道是怎么回事,在看到结构体

public struct KeyMSG
   {
       public int vkCode; 
       public int scanCode;
       public int flags; 
       public int time; 
       public int dwExtraInfo; 
   }
的时候才想明白lParam是一个指针地址,C#写多了,真是固定思维害死人啊。不废话了

开始写键盘钩子

// 安装钩子
       [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
       public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
       // 卸载钩子
       [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
       public static extern bool UnhookWindowsHookEx(int idHook);
       // 继续下一个钩子
       [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
       public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
       // 取得当前线程编号
       [DllImport("kernel32.dll")]
       static extern int GetCurrentThreadId();
引入上面这几个API,我作为一个从来没弄过HOOK的,这里说一下这几个函数我遇到的几个问题。

1.为什么DllImport后面写的是user32.dll

   这个是调用系统函数,在system32那个文件夹里面,具体为什么,可以查一下MSDN,或
者是去下一个windows API的电子书,上面会告诉你哪个系统函数在哪个dll里面

2.关于安装钩子的函数SetWindowsHookEx的几个参数

  1)int idHook  要添加钩子的类型,在c++里是一个枚举,可以去查一下c++的。
具体的基本上就是下面的这个枚举了,不知道对不对,不过用到的13,14号是肯定对的
     有发现错了的告诉我一下,我只用到了13号键盘事件
/// <summary>
   /// Hook的类型
   /// </summary>
   public enum HookType : int
   {
       WH_JOURNALRECORD = 0,     //对发送到系统消息队列的输入消息进行监视  
       WH_JOURNALPLAYBACK = 1,   //对此前有WH_JORNALRECORD的钩子过错记录的消息
       WH_KEYBOARD = 2,          //对键盘消息进行监视
       WH_GETMESSAGE = 3,        //对发送到消息队列的消息进行监视
       WH_CALLWNDPROC = 4,       //在操作系统将消息发送到目标窗口吃力过程之前,对该消息进行监视
       WH_CBT = 5,               //接受CBT应用程序有用的消息
       WH_SYSMSGFILTER = 6,      //监视由对话框,消息框,菜单条,或滚动条中的输入事件应发的消息。该钩子过程对系统中所有的应用程序的这类消息都进行监视
       WH_MOUSE = 7,             //对鼠标消息进行监视
       WH_MSGFILTER = 8,          //应监视由对话框,消息框,菜单条,或滚动条中输入的时间引发的消息
       WH_DEBUG = 9,             //对其他钩子进行调试
       WH_SHELL = 10,            //接受对外壳应用程序有用的通知
       WH_FOREGROUNDIDLE = 11,   //当用用程序的前台线程即将进入空闲状态时调用,它有助于在空闲时间类执行优先级低的任务
       WH_CALLWNDPROCRET = 12,   //对已被目标窗口过程处理过了的消息进行监视
       WH_KEYBOARD_LL = 13,      //只能安装在NT中,对底层的键盘事件进行监视
       WH_MOUSE_LL = 14          //只能安装在NT中,对底层的鼠标输入事件进行监视
   }
   2)HookProc lpfn 这是一个回调函数,我理解为一个函数指针,在C#里用委托来实现它,实现如下
       public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
       而我们写钩子也是对这三个参数做判断然后作出相应。在实现这个委托的时候在说着三个函数的事。
<="3)后面两参数一块说,这两个参数是用来表示是全局钩子还是局部钩子<">
      IntPtr hInstance, int threadId
      1.全局钩子  hInstance表示所在的线程,threadId为0
比如我这里添加一个全局钩子,对键盘监视,回调函数为KeyboardHookProcedure的全局钩子
SetWindowsHookEx((int)HookType.WH_KEYBOARD_LL, KeyboardHookProcedure, 
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
2 局部钩子 hInstance为null,threadId为当前线程id,你可以用GetCurrentThreadId();得到这个id
4)安装钩子的返回值是钩子的id ,我们需要记录下这个id,以便以后对这个钩子卸载或做其他操作。
5)多个钩子过程形成了一个钩子链,最后安装的钩子总是排在最前面,有点像栈,但是钩子链是先进随便出。
 

3.钩子回调函数

写一个钩子最主要的就是这个回调函数了,我Goole出来的基本都是代码贴,而C++的实现都是用2号钩子实现的。先贴回调函数的代码吧。

       public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
       HookProc KeyboardHookProcedure;
      private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)//回调函数实现
      {
          if (nCode >= 0)
          {
              KeyHookEventArgs e = new KeyHookEventArgs();
              e.nCode = nCode;
              e.wParam = wParam;
              e.lParam = lParam;
              if (OnKeyChange(this, e))
              {
                  return 1;
              }
              
          }
          return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); 
      }

       先不用管这个回调函数里面写了什么,nCode用来确定钩子过程是如何处理当前的消息的,而wParam和lParam用来存消息相关的内容。先说说这几个参数背后的故事,这个故事也是我道听Goole说的。在windows早期的版本的时候,wParam是16位,lParam是32位的,后来wParam变成了32位的,虽然生的时候不同,但是现在他俩其实是一样的。

       这两个32位的值就是在编写Hook的时候要用到的,我写的时候在这两个值的问题上总是想不明白别人给的代码为什么要那样写。

       这两个值用来存消息相关的内容,一般wParam来存固定的一些信息,而lParam用来存不固定的东西,在MSDN上对lParam的解释是一个不固定类型的值,大多数情况下是一个指针,指向一个结构体,而这个结构体里面就包含了我们所需要的信息。

       在哪里能找到这个结构体呢,打开你的MSDN,用C++的帮助,找SetWindowsHookEx这个函数,对于每一种钩子类型(idHook)都会有一个对应的回调函数,比如WH_KEYBOARD_LL的回调函数类型叫LowLevelKeyboardProc,如下图:

写个截图程序之 快捷键

       对于键盘回调函数LowLevelKeyboardProc的lParam的结构体类型

typedef struct {
    DWORD vkCode;
    DWORD scanCode;
    DWORD flags;
    DWORD time;
    ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;

你可以根据你需要的Hook类型去找你需要的wParam和lParam的意思。不过相关的帮助在C++里面,我最开始默认帮助是C#的,google也没找出个四五六来。

     好了上面的基本了解了就开始贴代码,这里我定义了一个事件,然后把这三个参数传出去。

public delegate bool KeyHookEventHandler(object sender,KeyHookEventArgs e);
public event KeyHookEventHandler OnKeyChange;

   其中KeyHookEventArgs 的定义:

   public class KeyHookEventArgs : EventArgs
   {
      public   int nCode;
      public   int wParam;
      public   IntPtr lParam;
    
   }
 和lParam对应的结构体的定义
   public struct KeyMSG
   {
       public int vkCode; 
       public int scanCode;
       public int flags; 
       public int time; 
       public int dwExtraInfo; 
   }
然后在来回头看看那个回调函数
      private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)//回调函数
       {
           if (nCode >= 0)
           {
               KeyHookEventArgs e = new KeyHookEventArgs();
               e.nCode = nCode;
               e.wParam = wParam;
               e.lParam = lParam;
               if (OnKeyChange(this, e))
               {
                   return 1;
               }
               
           }
           return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); 
       }
  
只是在把三个参数传给了事件OnKeyChange里面去执行。 操作成功了就返回1,否则下一位。这里的这个让给下一位这个要注
意写了,不需要处理的消息就放给下一位要不就把所有的消息都处理了,我开始忘了CallNextHookEx,然后键盘全部屏蔽掉
了,什么都不能操作,系统快捷键也不能用,只好重启机器了。
    然后就是截图的时候要处理的消息了:
bool  keyHook_OnKeyChange(object sender, KeyHookEventArgs e)
       {
           KeyMSG keyMsg = (KeyMSG)System.Runtime.InteropServices.Marshal.PtrToStructure(e.lParam, typeof(KeyMSG));

           //进入截图截图 Ctrl+Q
           if ((keyMsg.vkCode == (int)Keys.Q) && (Control.ModifierKeys == Keys.Control)  && e.wParam ==0x101)
           {
               this.FormShow();
               return true;
          }
           if (this.Visible == true && e.wParam ==0x101)
           {
               //取消 ESC
               if (keyMsg.vkCode == (int)Keys.Escape)
               {
                   this.FormHide();
                   return true;
               }
               //快捷键截图 Ctrl+C
               if (keyMsg.vkCode == (int)Keys.C && Control.ModifierKeys == Keys.Control)
               {
                   if (recttangle.Height > 1 && recttangle.Width > 1)
                   {
                       Copy();
                       return true;
                   }
               }
               //快捷键保存
               if (keyMsg.vkCode == (int)Keys.S && Control.ModifierKeys == Keys.Control)
               {
                   if (recttangle.Height > 1 && recttangle.Width > 1)
                   {
                      lock(this )
                      {
                         Save();
                      } 
                       return true;
                   }
               }
           }
           return false; 
       }
对于消息主要是这句
KeyMSG keyMsg = (KeyMSG)System.Runtime.InteropServices.Marshal.PtrToStructure(e.lParam, typeof(KeyMSG)); 
到指针所指的内容。
对于13号Hook也就是键盘Hook,wParam用来判断键盘的按下(0x100)还是弹起(0x101),
这个你可以做了键盘钩子后打印出来看看打印的16进制的值就知道了
得keyMsg.vkCode表示按键的值和Keys的枚举是对应的。
 
好了现在这个截图程序基本上就差不多了。
在程序编译调试的时候, 安装钩子的地方会出错,这个是托管代码的原因
如果需要调试你可以调整项目的属性->调试->启用非托管代码调试:

写个截图程序之 快捷键

最后的话

这一篇基本上意思在写Hook的一些基础的东西,如果有写错的地方,欢迎指正, 程序是好玩一下想到哪里写到哪里,

代码比较乱,但是还是完成最后一步,贴代码,源代码:点我下载