2006-11-23

在 .Net 中使用低级键盘钩子屏蔽 Win 键

在微软的文档《HOW TO:在 Visual C# .NET 中设置窗口挂钩 》中,对 .NET 框架中的钩子有如下描述:

在 .NET 框架中不支持全局挂钩
您无法在 Microsoft .NET 框架中实现全局挂钩。若要安装全局挂钩,挂钩必须有一个本机动态链接库 (DLL) 导出以便将其本身插入到另一个需要调入一个有效而且一致的函数的进程中。这需要一个 DLL 导出,而 .NET 框架不支持这一点。托管代码没有让函数指针具有统一的值这一概念,因为这些函数是动态构建的代理。

在 .NET 2.0 中,已经可以开发程序使用全局挂钩了,下面给出使用 C# 开发低级键盘钩子屏蔽 Win 键的代码:
使用钩子需要用到 3 个 Win32 API 函数: SetWindowsHookEx,CallNextHookEx和UnhookWindowsHookEx,在 C# 中需要声明一下:

class Win32API
{
   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern int CallNextHookEx(IntPtr hhk, int nCode, uint wParam, int lParam);

   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern int UnhookWindowsHookEx(IntPtr hhk);
}

低级键盘钩子用到了 KBDLLHOOKSTRUCT 结构,在 C# 中定义对应的结构:

struct KeyBoardHookStruct
{
   public UInt32 vkCode;
   public UInt32 scanCode;
   public UInt32 flags;
   public UInt32 time;
   public UInt32 dwExtraInfo;
}

对于低级键盘钩子,Win32 提供了 LowLevelKeyboardProc 格式的回调函数,对应的,在 C# 中使用代理:

delegate int HookProc(int nCode, uint wParam, int lParam);

最后封装一个类屏蔽左右 Win 键:

class MaskKey
{
   private const int WH_KEYBOARD_LL = 13;
   private const int HC_ACTION = 0;
   private const int VK_LWIN = 0x5B;
   private const int VK_RWIN = 0x5C;
   private IntPtr m_hook;

   public bool StartMaskKey()
   {
     if (m_hook != IntPtr.Zero) return false;

     IntPtr pInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().ManifestModule);
     m_hook = Win32API.SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, pInstance, 0);
     if (m_hook == IntPtr.Zero) return false;

     return true;
   }

   public bool StopMaskKey()
   {
     if (m_hook != IntPtr.Zero)
     {
       int succeed = Win32API.UnhookWindowsHookEx(m_hook);
       if (succeed == 0) return false;

       m_hook = IntPtr.Zero;
     }

     return true;
   }

   private int LowLevelKeyboardProc(int nCode, uint wParam, int lParam)
   {
     if (nCode == HC_ACTION)
     {
       KeyBoardHookStruct khs = (KeyBoardHookStruct)Marshal.PtrToStructure(
          new IntPtr(lParam), typeof(KeyBoardHookStruct));
       if (khs.vkCode == VK_LWIN || khs.vkCode == VK_RWIN) return 1;
     }
     return Win32API.CallNextHookEx(m_hook, nCode, wParam, lParam);
   }
}

上面的代码使用了低级键盘钩子,其中 ManifestModule 为 .Net 2.0 新增的属性,作用是获取包含当前程序集清单的模块。
编译成功后会发现程序仍然无法正确运行,这是因为需要修改 VS2005的编译选项,右键选择项目属性,在 Debug 选项卡中取消 Enable the Visual Studio hosting process 前面的选中标记, 重新编译运行,键盘的左右 Win 键已经不能使用了。

0 Comments: