• 全局鼠标键盘钩子的使用


    前言:

      windows中大部分的应用程序都是基于消息机制的,它们都有一个消息过程函数,根据不同的消息完成不同的功能。而消息钩子是windows提供的一种消息过滤和预处理机制,可以用来截获和监视系统中的消息。按照钩子作用范围不同,又可以分为局部钩子和全局钩子。局部钩子是针对某个线程的,而全局钩子是作用于整个系统的基于消息的应用。全局钩子需要使用DLL文件,在DLL文件中实现相应的钩子函数。

    由于windows操作是基于消息的,那么鼠标和键盘的操作也是通过消息传递到目标窗口的,因此可以按装全局鼠标键盘钩子,使鼠标键盘在传递到目标窗口之前拦截、钩住它,为我们提供一个做操作的机会,可以触发某个事件方法,可以阻止它传递、也可以不做任何处理。

    不管是鼠标钩子还是键盘钩子,都需要先注册windows全局钩子。

    方法:

     首先需要调用user32.dll包

    SetWindowsHookEx:安装全局钩子

    UnhookWindowsHookEx:卸载全局钩子

    CallNextHookEx:调用下一个钩子

    参数:

    private static int hMouseHook = 0;    

    private const int WM_MOUSEMOVE = 0x200; //鼠标移动,本次没有使用这个参数,而是用的MouseEventArgs()方法监听鼠标移动
    private const int WM_LBUTTONDOWN = 0x201;   //左键按下
    private const int WM_RBUTTONDOWN = 0x204;    //右键按下
    private const int WM_MBUTTONDOWN = 0x207;    //中键按下
    private const int WM_LBUTTONUP = 0x202;    //左键抬起
    private const int WM_RBUTTONUP = 0x205;    //右键抬起
    private const int WM_MBUTTONUP = 0x208;    //中键抬起

    private const int WM_LBUTTONDBLCLK = 0x203;  //左键单击,本次未使用
    private const int WM_RBUTTONDBLCLK = 0x206;  //右键单击,本次未使用
    private const int WM_MBUTTONDBLCLK = 0x209;  //中键单击,本次未使用

    完整流程:

    第一步、定义windows全局钩子类:

     1     public class Win32Api
     2     {
     3 
     4         public delegate int HookProc( int nCode, IntPtr wParam, IntPtr lParam);
     5 
     6         /// <summary>
     7         /// 安装钩子。把一个应用程序定义的钩子子程安装到钩子链表中。函数成功则返回钩子子程的句柄,失败返回NULL
     8         /// </summary>
     9         /// <param name="idHook">钩子的类型。它决定了 HOOKPROC 被调用的时机</param>
    10         /// <param name="lpfn">指向钩子回调函数的指针。如果最后一个参数 dwThreadId 为0或者是其它进程创建的线程标识符,则 lpfn 参数必须指向DLL中的钩子回调函数,即 HOOKPROC 函数必须在DLL中实现。否则,lpfn 可以指向与当前进程相关联的代码中的钩子过程</param>
    11         /// <param name="hInstance">包含由 lpfn 参数指向的钩子回调函数的DLL句柄。如果 dwThreadId 参数指定由当前进程创建线程,并且钩子回调函数位于当前进程关联的代码中,则 hmod 参数必须设置为 NULL。</param>
    12         /// <param name="threadId">与钩子程序关联的线程标识符(指定要 HOOK 的线程 ID)。如果此参数为0,则钩子过程与系统中所有线程相关联,即全局消息钩子</param>
    13         /// <returns></returns>
    14         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    15         public static extern int SetWindowsHookEx(int idHook,HookProc lpfn,IntPtr hInstance, int threadId);
    16 
    17         /// <summary>
    18         /// 卸载钩子。函数成功则返回非0,失败返回NULL
    19         /// </summary>
    20         /// <param name="idHook">钩子的类型</param>
    21         /// <returns></returns>
    22         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    23         public static extern bool UnhookWindowsHookEx(int idHook);
    24 
    25         /// <summary>
    26         /// 调用下一个钩子。调用钩子链中的下一个挂钩过程,调用成功返回值是下一个钩子的回调函数,否则为0。当前钩子程序也必须返回此值。
    27         /// </summary>
    28         /// <param name="idHook">钩子的类型</param>
    29         /// <param name="nCode">钩子代码。就是给下一个钩子要交待的内容</param>
    30         /// <param name="wParam">要传递的参数。由钩子类型决定是什么参数</param>
    31         /// <param name="lParam">要传递的参数。由钩子类型决定是什么参数</param>
    32         /// <returns></returns>
    33         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    34         public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
    35 
    36     }
    View Code

    注:鼠标钩子函数和键盘钩子函数都会公用上面这个全局钩子类

    第二步、定义鼠标钩子方法和键盘钩子方法

    鼠标钩子:

      1 class MouseHook
      2     {
      3         private Point point;
      4         private Point Point
      5         {
      6             get { return point; }
      7             set
      8             {
      9                 if (point != value)
     10                 {
     11                     point = value;
     12                     if (MouseMoveEvent != null)
     13                     {
     14                         var e = new MouseEventArgs(MouseButtons.None, 0, point.X, point.Y, 0);
     15                         MouseMoveEvent(this, e);
     16                     }
     17                 }
     18             }
     19         }
     20         private int hHook;
     21         private static int hMouseHook = 0;
     22         private const int WM_MOUSEMOVE = 0x200;
     23         private const int WM_LBUTTONDOWN = 0x201;
     24         private const int WM_RBUTTONDOWN = 0x204;
     25         private const int WM_MBUTTONDOWN = 0x207;
     26         private const int WM_LBUTTONUP = 0x202;
     27         private const int WM_RBUTTONUP = 0x205;
     28         private const int WM_MBUTTONUP = 0x208;
     29         private const int WM_LBUTTONDBLCLK = 0x203;
     30         private const int WM_RBUTTONDBLCLK = 0x206;
     31         private const int WM_MBUTTONDBLCLK = 0x209;
     32 
     33         public const int WH_MOUSE_LL = 14; //idHook值的参数,14为系统级,截获全局鼠标消息。详细SetWindowsHookEx函数的idHook参照https://www.cnblogs.com/ndyxb/p/12883292.html
     34         public Win32Api.HookProc hProc;
     35         public MouseHook()
     36         {
     37             this.Point = new Point();
     38         }
     39 
     40         /// <summary>
     41         /// 安装鼠标钩子
     42         /// </summary>
     43         /// <returns></returns>
     44         public int SetHook()
     45         {
     46             hProc = new Win32Api.HookProc(MouseHookProc);
     47             hHook = Win32Api.SetWindowsHookEx(WH_MOUSE_LL, hProc, IntPtr.Zero, 0);
     48             return hHook;
     49         }
     50 
     51         /// <summary>
     52         /// 卸载鼠标钩子
     53         /// </summary>
     54         public void UnHook()
     55         {
     56             Win32Api.UnhookWindowsHookEx(hHook);
     57         }
     58 
     59         /// <summary>
     60         /// 执行鼠标钩子
     61         /// </summary>
     62         /// <param name="nCode"></param>
     63         /// <param name="wParam"></param>
     64         /// <param name="lParam"></param>
     65         /// <returns></returns>
     66         private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
     67         {
     68             MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
     69             if (nCode < 0)
     70             {
     71                 return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
     72             }
     73             else
     74             {
     75                 MouseButtons button = MouseButtons.None;
     76                 int clickCount = 0;
     77                 switch ((Int32)wParam)
     78                 {
     79                     case WM_LBUTTONDOWN:
     80                         button = MouseButtons.Left;
     81                         clickCount = 1;
     82                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
     83                         break;
     84                     case WM_RBUTTONDOWN:
     85                         button = MouseButtons.Right;
     86                         clickCount = 1;
     87                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
     88                         break;
     89                     case WM_MBUTTONDOWN:
     90                         button = MouseButtons.Middle;
     91                         clickCount = 1;
     92                         MouseDownEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
     93                         break;
     94                     case WM_LBUTTONUP:
     95                         button = MouseButtons.Left;
     96                         clickCount = 1;
     97                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
     98                         break;
     99                     case WM_RBUTTONUP:
    100                         button = MouseButtons.Right;
    101                         clickCount = 1;
    102                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
    103                         break;
    104                     case WM_MBUTTONUP:
    105                         button = MouseButtons.Middle;
    106                         clickCount = 1;
    107                         MouseUpEvent(this, new MouseEventArgs(button, clickCount, point.X, point.Y, 0));
    108                         break;
    109                 }
    110 
    111                 this.Point = new Point(MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y);
    112                 return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);
    113             }
    114         }
    115 
    116 
    117         [StructLayout(LayoutKind.Sequential)]
    118         public class POINT
    119         {
    120             public int x;
    121 
    122             public int y;
    123 
    124         }
    125         [StructLayout(LayoutKind.Sequential)]
    126         public class MouseHookStruct
    127         {
    128 
    129             public POINT pt;
    130 
    131             public int hwnd;
    132 
    133             public int wHitTestCode;
    134 
    135             public int dwExtraInfo;
    136 
    137         }
    138 
    139 
    140 
    141 
    142         public delegate void MouseMoveHandler(object sender, MouseEventArgs e);
    143         public event MouseMoveHandler MouseMoveEvent;
    144         public delegate void MouseDownHandler(object sender, MouseEventArgs e);
    145         public event MouseDownHandler MouseDownEvent;
    146         public delegate void MouseUpHandler(object sender, MouseEventArgs e);
    147         public event MouseUpHandler MouseUpEvent;
    148     }
    View Code

    键盘钩子:

      1 class KeyHook
      2     {
      3         public event KeyEventHandler KeyDownEvent;
      4         public event KeyPressEventHandler KeyPressEvent;
      5         public event KeyEventHandler KeyUpEvent;
      6 
      7         
      8         static int hKeyboardHook = 0; //声明键盘钩子处理的初始值
      9                                       //值在Microsoft SDK的Winuser.h里查询
     10         public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
     11         Win32Api.HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型
     12                                                     //键盘结构
     13 
     14         [StructLayout(LayoutKind.Sequential)]
     15         public class KeyboardHookStruct
     16         {
     17             public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
     18             public int scanCode; // 指定的硬件扫描码的关键
     19             public int flags;  // 键标志
     20             public int time; // 指定的时间戳记的这个讯息
     21             public int dwExtraInfo; // 指定额外信息相关的信息
     22         }
     23         
     24 
     25         // 取得当前线程编号(线程钩子需要用到)
     26         [DllImport("kernel32.dll")]
     27         static extern int GetCurrentThreadId();
     28 
     29         //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
     30         [DllImport("kernel32.dll")]
     31         public static extern IntPtr GetModuleHandle(string name);
     32 
     33         /// <summary>
     34         /// 安装键盘钩子
     35         /// </summary>
     36         public void Start()
     37         {
     38             // 安装键盘钩子
     39             if (hKeyboardHook == 0)
     40             {
     41                 KeyboardHookProcedure = new Win32Api.HookProc(KeyboardHookProc);
     42                 hKeyboardHook = Win32Api.SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
     43                 //hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
     44                 //************************************
     45                 //键盘线程钩子
     46                 Win32Api.SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要监听的线程idGetCurrentThreadId(),
     47                                                                                                //键盘全局钩子,需要引用空间(using System.Reflection;)
     48                                                                                                //如果SetWindowsHookEx失败
     49                 if (hKeyboardHook == 0)
     50                 {
     51                     Stop();
     52                     throw new Exception("安装键盘钩子失败");
     53                 }
     54             }
     55         }
     56 
     57         /// <summary>
     58         /// 卸载键盘钩子
     59         /// </summary>
     60         public void Stop()
     61         {
     62             bool retKeyboard = true;
     63 
     64 
     65             if (hKeyboardHook != 0)
     66             {
     67                 retKeyboard = Win32Api.UnhookWindowsHookEx(hKeyboardHook);
     68                 hKeyboardHook = 0;
     69             }
     70             
     71             if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
     72         }
     73         //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符
     74 
     75 
     76 
     77 
     78         [DllImport("user32")]
     79         public static extern int ToAscii(int uVirtKey, //[in] 指定虚拟关键代码进行翻译。
     80                                          int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
     81                                          byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。
     82                                          byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。
     83                                          int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
     84         //获取按键的状态
     85         [DllImport("user32")]
     86         public static extern int GetKeyboardState(byte[] pbKeyState);
     87 
     88         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
     89         private static extern short GetKeyState(int vKey);
     90 
     91 
     92         /// <summary>
     93         /// 执行键盘钩子
     94         /// </summary>
     95         /// <param name="nCode"></param>
     96         /// <param name="wParam"></param>
     97         /// <param name="lParam"></param>
     98         /// <returns></returns>
     99         private int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    100         {
    101             // 侦听键盘事件
    102             if ((nCode >= 0) && (KeyDownEvent != null || KeyUpEvent != null || KeyPressEvent != null))
    103             {
    104                 KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
    105                 // 键盘按下
    106                 if (KeyDownEvent != null && ((Int32)wParam == (Int32)KeyEvent.WM_KEYDOWN || (Int32)wParam == (Int32)KeyEvent.WM_SYSKEYDOWN))
    107                 {
    108                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
    109                     KeyEventArgs e = new KeyEventArgs(keyData);
    110                     KeyDownEvent(this, e);
    111                 }
    112 
    113                 //键盘点击
    114                 if (KeyPressEvent != null && (Int32)wParam == (Int32)KeyEvent.WM_KEYDOWN)
    115                 {
    116                     byte[] keyState = new byte[256];
    117                     GetKeyboardState(keyState);
    118 
    119                     byte[] inBuffer = new byte[2];
    120                     if (ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) == 1)
    121                     {
    122                         KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);
    123                         KeyPressEvent(this, e);
    124                     }
    125                 }
    126 
    127                 // 键盘抬起
    128                 if (KeyUpEvent != null && ((Int32)wParam == (Int32)KeyEvent.WM_KEYUP || (Int32)wParam == (Int32)KeyEvent.WM_SYSKEYUP))
    129                 {
    130                     Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
    131                     KeyEventArgs e = new KeyEventArgs(keyData);
    132                     KeyUpEvent(this, e);
    133                 }
    134 
    135             }
    136             //如果返回1,则结束消息,这个消息到此为止,不再传递。
    137             //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
    138             return Win32Api.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
    139         }
    140 
    141         ~KeyHook()
    142         {
    143             Stop();
    144         }
    145 
    146       
    147     }
    View Code

    第三步、点击调用鼠标键盘钩子:

    点击调用鼠标钩子

     1 //鼠标事件监听
     2 public partial class MouseEventHook
     3 {
     4     MouseHook mh;     //定义全局的鼠标监听方法
     5     Point p1 = new Point(0, 0);   //定义全局鼠标坐标
     6     Point p2 = new Point(0, 0);   //多定义个坐标可以显示鼠标按下与抬起之间移动的距离,这个也可不要
     7 
     8     //点击按钮开始鼠标监听
     9     private void button1_Click(object sender, EventArgs e)
    10     {
    11            mh = new MouseHook();
    12            mh.SetHook();     //安装鼠标钩子
    13 
    14            mh.MouseMoveEvent += my_MouseMoveEvent;     //绑定鼠标移动时触发的事件
    15            mh.MouseDownEvent += my_MouseDownEvent;    //绑定鼠标按下时触发的事件
    16            mh.MouseUpEvent += my_MouseUpEvent;            //绑定鼠标抬起时触发的事件
    17     }
    18 
    19     
    20     //点击按钮停止鼠标监听
    21     private void button2_Click(object sender, EventArgs e)
    22     {
    23         if (mh != null) mh.UnHook();
    24         MessageBox.Show("鼠标监听停止!");
    25     }
    26 
    27 
    28 
    29         // 鼠标移动触发的事件
    30         private void my_MouseMoveEvent(object 
    31  sender,MouseEventArgs e)
    32         {
    33             Point p = e.Location;   //获取坐标
    34             string movexy = p.ToString();
    35             richTextBox1.AppendText(movexy + "," + stopWatch.ElapsedMilliseconds + "
    ");   //将坐标记录到RichTextBox控件中
    36         }
    37 
    38 
    39          //按下鼠标键触发的事件
    40         private void my_MouseDownEvent(object sender, MouseEventArgs e)
    41         {
    42             if (e.Button == MouseButtons.Left)
    43             {
    44                 LeftTag = true;
    45                 richTextBox1.AppendText("按下了左键
    ");    //将按键操作记录到RichTextBox控件中
    46             }
    47             if (e.Button == MouseButtons.Right)
    48             {
    49                 RightTag = true;
    50                 richTextBox1.AppendText("按下了右键
    ");
    51             }
    52             p1 = e.Location;
    53             
    54         }
    55 
    56 
    57         //松开鼠标键触发的事件
    58         private void my_MouseUpEvent(object sender, MouseEventArgs e)
    59         {
    60             p2 = e.Location;
    61             double value = Math.Sqrt(Math.Abs(p1.X - p2.X) * Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y) * Math.Abs(p1.Y - p2.Y));
    62             if (e.Button == MouseButtons.Left)
    63             {
    64                 richTextBox1.AppendText("松开了左键  " + LineNum + "
    ");
    65 
    66             }
    67             if (e.Button == MouseButtons.Right)
    68             {
    69                 richTextBox1.AppendText("松开了右键  " + LineNum + "
    ");
    70             }
    71             richTextBox1.AppendText("移动了" + value + "距离
    ");
    72             RightTag = false;
    73             LeftTag = false;
    74             p1 = new Point(0, 0);
    75             p2 = new Point(0, 0);
    76         }
    77 
    78 
    79 
    80 }    
    View Code

     点击调用键盘钩子

     1 public partial class KeyEventHook
     2 {
     3         KeyHook k_hook;
     4         KeyEventHandler myKeyDownHandeler;
     5         KeyEventHandler myKeyUpHandeler;
     6         
     7         /// <summary>
     8         /// 开始键盘监听
     9         /// </summary>
    10         public void startKeyListen()
    11         {
    12             k_hook = new KeyHook();
    13             myKeyDownHandeler = new KeyEventHandler(hook_KeyDown);
    14             k_hook.KeyDownEvent += myKeyDownHandeler;//钩住键盘按下
    15             myKeyUpHandeler = new KeyEventHandler(hook_KeyUp);
    16             k_hook.KeyUpEvent += myKeyUpHandeler;//钩住键盘抬起
    17             k_hook.Start();//安装键盘钩子
    18         }
    19 
    20         /// <summary>
    21         /// 结束键盘监听
    22         /// </summary>
    23         public void stopKeyListen()
    24         {
    25             if (myKeyDownHandeler != null)
    26             {
    27                 k_hook.KeyDownEvent -= myKeyDownHandeler;//取消按键事件
    28                 myKeyDownHandeler = null;
    29                 k_hook.Stop();//关闭键盘钩子
    30             }
    31         }
    32 
    33 
    34 
    35         /// <summary>
    36         /// 键盘按下时就调用这个
    37         /// </summary>
    38         /// <param name="sender"></param>
    39         /// <param name="e"></param>
    40         public void hook_KeyDown(object sender, KeyEventArgs e)
    41         {
    42             if (e.KeyCode.Equals(Keys.Escape))         //如果按下Esc键可执行if里面的操作
    43             {
    44                        //按下特定键后执行的代码
    45             }
    46             Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘按下" );      //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志,
    47             
    48         }
    49 
    50         /// <summary>
    51         /// 有键盘抬起时就调用这个
    52         /// </summary>
    53         /// <param name="sender"></param>
    54         /// <param name="e"></param>
    55         public void hook_KeyUp(object sender, KeyEventArgs e)
    56         {
    57              Log.LogMouseEvent(e.KeyCode.ToString() + ",键盘抬起");      //这个Log.LogMouseEvent是我用来记录键盘按下抬起的日志
    58                 
    59         }
    60 }
    View Code

    总结:

    监听流程为:安装钩子——》触发钩子后调用相应的绑定事件——》调用下一个钩子——》监听完成,卸载钩子

    钩子在程序退出之后会自动卸载,不过那样容易出错而且不好控制,最好还是手动卸载

    END

  • 相关阅读:
    CARLA——The External Sensor Interface (ESI)
    Eventbased Vision meets Deep Learning on Steering Prediction for Selfdriving Cars
    DDD20 EndtoEnd Event Camera Driving Dataset: Fusing Frames and Events with Deep Learning for Improved Steering Prediction
    CARLA——Core implementations: synchrony, snapshots and landmarks
    Generative Adversarial Imitation Learning
    EventBased Vision Enhanced: A Joint Detection Framework in Autonomous Driving
    CARLA——Sensors in CARLA
    Imitation Learning via OffPolicy Distribution Matching
    CARLA入门
    颜色
  • 原文地址:https://www.cnblogs.com/kk138/p/14505659.html
Copyright © 2020-2023  润新知