优哉@游哉 的博客

Coding 不仅仅是谋生的手段
随笔 - 15, 文章 - 4, 评论 - 104, 引用 - 8
数据加载中……

C# 实现屏幕键盘 (ScreenKeyboard)

要实现一个屏幕键盘,需要监听所有键盘事件,无论窗体是否被激活。因此需要一个全局的钩子,也就
是系统范围的钩子。

什么是钩子(Hook)

    钩子(Hook)是Windows提供的一种消息处理机制平台,是指在程序正常运行中接受信息之前预先
    启动的函数,用来检查和修改传给该程序的信息,(钩子)实际上是一个处理消息的程序段,通
    过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获
    该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不
    作处理而继续传递该消息,还可以强制结束消息的传递。注意:安装钩子函数将会影响系统的性
    能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的
    钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可
    以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。

钩子的作用范围
    一共有两种范围(类型)的钩子,局部的和远程的。局部钩子仅钩挂自己进程的事件。远程的钩
    子还可以将钩挂其它进程发生的事件。远程的钩子又有两种: 基于线程的钩子将捕获其它进程中
    某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。 系
    统范围的钩子将捕捉系统中所有进程将发生的事件消息。 

Hook 类型
    Windows共有14种Hooks,每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机
    制。下面描述所有可以利用的Hook类型的发生时机。详细内容可以查阅MSDN,这里只介绍我们将要
    用到的两种类型的钩子。
    
    (1)WH_KEYBOARD_LL Hook
        WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。

    (2)WH_MOUSE_LL Hook
        WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。

下面的 class 把 API 调用封装起来以便调用。

 1// NativeMethods.cs
 2using System;
 3using System.Runtime.InteropServices;
 4using System.Drawing;
 5
 6namespace CnBlogs.Youzai.ScreenKeyboard {
 7    [StructLayout(LayoutKind.Sequential)]
 8    internal struct MOUSEINPUT {
 9        public int dx;
10        public int dy;
11        public int mouseData;
12        public int dwFlags;
13        public int time;
14        public IntPtr dwExtraInfo;
15    }

16
17    [StructLayout(LayoutKind.Sequential)]
18    internal struct KEYBDINPUT {
19        public short wVk;
20        public short wScan;
21        public int dwFlags;
22        public int time;
23        public IntPtr dwExtraInfo;
24    }

25
26    [StructLayout(LayoutKind.Explicit)]
27    internal struct Input {
28        [FieldOffset(0)]
29        public int type;
30        [FieldOffset(4)]
31        public MOUSEINPUT mi;
32        [FieldOffset(4)]
33        public KEYBDINPUT ki;
34        [FieldOffset(4)]
35        public HARDWAREINPUT hi;
36    }

37
38    [StructLayout(LayoutKind.Sequential)]
39    internal struct HARDWAREINPUT {
40        public int uMsg;
41        public short wParamL;
42        public short wParamH;
43    }

44
45    internal class INPUT {
46        public const int MOUSE = 0;
47        public const int KEYBOARD = 1;
48        public const int HARDWARE = 2;
49    }

50
51    internal static class NativeMethods {
52        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
53        internal static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
54
55        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
56        internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
57
58        [DllImport("User32.dll", EntryPoint = "SendInput", CharSet = CharSet.Auto)]
59        internal static extern UInt32 SendInput(UInt32 nInputs, Input[] pInputs, Int32 cbSize);
60
61        [DllImport("Kernel32.dll", EntryPoint = "GetTickCount", CharSet = CharSet.Auto)]
62        internal static extern int GetTickCount();
63
64        [DllImport("User32.dll", EntryPoint = "GetKeyState", CharSet = CharSet.Auto)]
65        internal static extern short GetKeyState(int nVirtKey);
66
67        [DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
68        internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
69    }

70}

安装钩子
    使用SetWindowsHookEx函数(API函数),指定一个Hook类型、自己的Hook过程是全局还是局部Hook,
    同时给出Hook过程的进入点,就可以轻松的安装自己的Hook过程。SetWindowsHookEx总是将你的Hook函
    数放置在Hook链的顶端。你可以使用CallNextHookEx函数将系统消息传递给Hook链中的下一个函数。
    对于某些类型的Hook,系统将向该类的所有Hook函数发送消息,这时,
    Hook函数中的CallNextHookEx语句将被忽略。全局(远程钩子)Hook函数可以拦截系统中所有线程的某
    个特定的消息,为了安装一个全局Hook过程,必须在应用程序外建立一个DLL并将该Hook函数封装到其中,
    应用程序在安装全局Hook过程时必须先得到该DLL模块的句柄。将Dll名传递给LoadLibrary 函数,就会得
    到该DLL模块的句柄;得到该句柄 后,使用GetProcAddress函数可以得到Hook过程的地址。最后,使用
    SetWindowsHookEx将 Hook过程的首址嵌入相应的Hook链中,SetWindowsHookEx传递一个模块句柄,它为
    Hook过程的进入点,线程标识符置为0,该Hook过程同系统中的所有线程关联。如果是安装局部Hook此时
    该Hook函数可以放置在DLL中,也可以放置在应用程序的模块段。在C#中通过平台调用(前文已经介绍过)
    来调用API函数。

 1    public void Start(bool installMouseHook, bool installKeyboardHook) {
 2        if (hMouseHook == IntPtr.Zero && installMouseHook) {
 3            MouseHookProcedure = new HookProc(MouseHookProc);
 4            hMouseHook = SetWindowsHookEx(
 5                WH_MOUSE_LL,
 6                MouseHookProcedure,
 7                Marshal.GetHINSTANCE(
 8                Assembly.GetExecutingAssembly().GetModules()[0]),
 9                0
10           );
11
12            if (hMouseHook == IntPtr.Zero) {
13                int errorCode = Marshal.GetLastWin32Error();
14                Stop(truefalsefalse);
15
16                throw new Win32Exception(errorCode);
17            }

18        }

19
20        if (hKeyboardHook == IntPtr.Zero && installKeyboardHook) {
21            KeyboardHookProcedure = new HookProc(KeyboardHookProc);
22            //install hook
23            hKeyboardHook = SetWindowsHookEx(
24                WH_KEYBOARD_LL,
25                KeyboardHookProcedure,
26                Marshal.GetHINSTANCE(
27                Assembly.GetExecutingAssembly().GetModules()[0]),
28                0);
29            // If SetWindowsHookEx fails.
30            if (hKeyboardHook == IntPtr.Zero) {
31                // Returns the error code returned by the last 
32                // unmanaged function called using platform invoke 
33                // that has the DllImportAttribute.SetLastError flag set. 
34                int errorCode = Marshal.GetLastWin32Error();
35                //do cleanup
36                Stop(falsetruefalse);
37                //Initializes and throws a new instance of the 
38                // Win32Exception class with the specified error. 
39                throw new Win32Exception(errorCode);
40            }

41        }

42    }

使用完钩子后,要进行卸载,这个可以写在析构函数中。

 1
 2    public void Stop() {
 3        this.Stop(truetruetrue);
 4    }

 5    
 6    public void Stop(bool uninstallMouseHook, bool uninstallKeyboardHook, 
 7        bool throwExceptions) {
 8        // if mouse hook set and must be uninstalled
 9        if (hMouseHook != IntPtr.Zero && uninstallMouseHook) {
10            // uninstall hook
11            bool retMouse = UnhookWindowsHookEx(hMouseHook);
12            // reset invalid handle
13            hMouseHook = IntPtr.Zero;
14            // if failed and exception must be thrown
15            if (retMouse == false && throwExceptions) {
16                // Returns the error code returned by the last unmanaged function 
17                // called using platform invoke that has the DllImportAttribute.
18                // SetLastError flag set. 
19                int errorCode = Marshal.GetLastWin32Error();
20                // Initializes and throws a new instance of the Win32Exception class 
21                // with the specified error. 
22                throw new Win32Exception(errorCode);
23            }

24        }

25
26        // if keyboard hook set and must be uninstalled
27        if (hKeyboardHook != IntPtr.Zero && uninstallKeyboardHook) {
28            // uninstall hook
29            bool retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
30            // reset invalid handle
31            hKeyboardHook = IntPtr.Zero;
32            // if failed and exception must be thrown
33            if (retKeyboard == false && throwExceptions) {
34                // Returns the error code returned by the last unmanaged function 
35                // called using platform invoke that has the DllImportAttribute.
36                // SetLastError flag set. 
37                int errorCode = Marshal.GetLastWin32Error();
38                // Initializes and throws a new instance of the Win32Exception class 
39                // with the specified error. 
40                throw new Win32Exception(errorCode);
41            }

42        }

43    }

44

将这个文件编译成一个dll,即可在应用程序中调用。通过它提供的事件,便可监听所有的键盘事件。
但是,这只能监听键盘事件,没有键盘的情况下,怎么会有键盘事件?其实很简单,通过SendInput
API函数提供虚拟键盘代码的调用即可模拟键盘输入。下面的代码模拟一个 KeyDown 和 KeyUp 过程,
把他们连接起来就是一次按键过程。

 1    private void SendKeyDown(short key) {
 2        Input[] input = new Input[1];
 3        input[0].type = INPUT.KEYBOARD;
 4        input[0].ki.wVk = key;
 5        input[0].ki.time = NativeMethods.GetTickCount();
 6
 7        if (NativeMethods.SendInput((uint)input.Length, input, Marshal.SizeOf(input[0])) 
 8            < input.Length) {
 9            throw new Win32Exception(Marshal.GetLastWin32Error());
10        }

11    }

12
13    private void SendKeyUp(short key) {
14        Input[] input = new Input[1];
15        input[0].type = INPUT.KEYBOARD;
16        input[0].ki.wVk = key;
17        input[0].ki.dwFlags = KeyboardConstaint.KEYEVENTF_KEYUP;
18        input[0].ki.time = NativeMethods.GetTickCount();
19
20        if (NativeMethods.SendInput((uint)input.Length, input, Marshal.SizeOf(input[0]))
21            < input.Length) {
22            throw new Win32Exception(Marshal.GetLastWin32Error());
23        }

24    }

自己实现一个 KeyBoardButton 控件用作按钮,用 Visual Studio 或者 SharpDevelop 为屏幕键盘设计 UI,然后
在这些 Button 的 Click 事件里面模拟一个按键过程。

 1
 2    private void ButtonOnClick(object sender, EventArgs e) {
 3        KeyboardButton btnKey = sender as KeyboardButton;
 4        if (btnKey == null{
 5            return;
 6        }

 7
 8        SendKeyCommand(btnKey);
 9    }

10    
11    private void SendKeyCommand(KeyboardButton keyButton) {
12        short key = keyButton.VKCode;
13        if (combinationVKButtonsMap.ContainsKey(key)) {
14            if (keyButton.Checked) {
15                SendKeyUp(key);
16            }
 else {
17                SendKeyDown(key);
18            }

19        }
 else {
20            SendKeyDown(key);
21            SendKeyUp(key);
22        }

23    }

其中 combinationVKButtonsMap 是一个 IDictionary<short, IList<KeyboardButton>>, key 存储的是
VK_SHIFT, VK_CONTROL 等组合键的键盘码。左右两个按钮对应同一个键盘码,因此需要放在一个 List 里。
标准键盘上的每一个键都有虚拟键码( VK_CODE)与之对应。还有一些其他的常量,
把它写在一个静态 class 里吧。

 1    // KeyboardConstaint.cs
 2    internal static class KeyboardConstaint {
 3        internal static readonly short VK_F1 = 0x70;
 4        internal static readonly short VK_F2 = 0x71;
 5        internal static readonly short VK_F3 = 0x72;
 6        internal static readonly short VK_F4 = 0x73;
 7        internal static readonly short VK_F5 = 0x74;
 8        internal static readonly short VK_F6 = 0x75;
 9        internal static readonly short VK_F7 = 0x76;
10        internal static readonly short VK_F8 = 0x77;
11        internal static readonly short VK_F9 = 0x78;
12        internal static readonly short VK_F10 = 0x79;
13        internal static readonly short VK_F11 = 0x7A;
14        internal static readonly short VK_F12 = 0x7B;
15
16        internal static readonly short VK_LEFT = 0x25;
17        internal static readonly short VK_UP = 0x26;
18        internal static readonly short VK_RIGHT = 0x27;
19        internal static readonly short VK_DOWN = 0x28;
20
21        internal static readonly short VK_NONE = 0x00;
22        internal static readonly short VK_ESCAPE = 0x1B;
23        internal static readonly short VK_EXECUTE = 0x2B;
24        internal static readonly short VK_CANCEL = 0x03;
25        internal static readonly short VK_RETURN = 0x0D;
26        internal static readonly short VK_ACCEPT = 0x1E;
27        internal static readonly short VK_BACK = 0x08;
28        internal static readonly short VK_TAB = 0x09;
29        internal static readonly short VK_DELETE = 0x2E;
30        internal static readonly short VK_CAPITAL = 0x14;
31        internal static readonly short VK_NUMLOCK = 0x90;
32        internal static readonly short VK_SPACE = 0x20;
33        internal static readonly short VK_DECIMAL = 0x6E;
34        internal static readonly short VK_SUBTRACT = 0x6D;
35
36        internal static readonly short VK_ADD = 0x6B;
37        internal static readonly short VK_DIVIDE = 0x6F;
38        internal static readonly short VK_MULTIPLY = 0x6A;
39        internal static readonly short VK_INSERT = 0x2D;
40
41        internal static readonly short VK_OEM_1 = 0xBA;  // ';:' for US
42        internal static readonly short VK_OEM_PLUS = 0xBB;  // '+' any country
43
44        internal static readonly short VK_OEM_MINUS = 0xBD;  // '-' any country
45
46        internal static readonly short VK_OEM_2 = 0xBF;  // '/?' for US
47        internal static readonly short VK_OEM_3 = 0xC0;  // '`~' for US
48        internal static readonly short VK_OEM_4 = 0xDB;  //  '[{' for US
49        internal static readonly short VK_OEM_5 = 0xDC;  //  '\|' for US
50        internal static readonly short VK_OEM_6 = 0xDD;  //  ']}' for US
51        internal static readonly short VK_OEM_7 = 0xDE;  //  ''"' for US
52        internal static readonly short VK_OEM_PERIOD = 0xBE;  // '.>' any country
53        internal static readonly short VK_OEM_COMMA = 0xBC;  // ',<' any country
54        internal static readonly short VK_SHIFT = 0x10;
55        internal static readonly short VK_CONTROL = 0x11;
56        internal static readonly short VK_MENU = 0x12;
57        internal static readonly short VK_LWIN = 0x5B;
58        internal static readonly short VK_RWIN = 0x5C;
59        internal static readonly short VK_APPS = 0x5D;
60
61        internal static readonly short VK_LSHIFT = 0xA0;
62        internal static readonly short VK_RSHIFT = 0xA1;
63        internal static readonly short VK_LCONTROL = 0xA2;
64        internal static readonly short VK_RCONTROL = 0xA3;
65        internal static readonly short VK_LMENU = 0xA4;
66        internal static readonly short VK_RMENU = 0xA5;
67
68        internal static readonly short VK_SNAPSHOT = 0x2C;
69        internal static readonly short VK_SCROLL = 0x91;
70        internal static readonly short VK_PAUSE = 0x13;
71        internal static readonly short VK_HOME = 0x24;
72
73        internal static readonly short VK_NEXT = 0x22;
74        internal static readonly short VK_PRIOR = 0x21;
75        internal static readonly short VK_END = 0x23;
76
77        internal static readonly short VK_NUMPAD0 = 0x60;
78        internal static readonly short VK_NUMPAD1 = 0x61;
79        internal static readonly short VK_NUMPAD2 = 0x62;
80        internal static readonly short VK_NUMPAD3 = 0x63;
81        internal static readonly short VK_NUMPAD4 = 0x64;
82        internal static readonly short VK_NUMPAD5 = 0x65;
83        internal static readonly short VK_NUMPAD5NOTHING = 0x0C;
84        internal static readonly short VK_NUMPAD6 = 0x66;
85        internal static readonly short VK_NUMPAD7 = 0x67;
86        internal static readonly short VK_NUMPAD8 = 0x68;
87        internal static readonly short VK_NUMPAD9 = 0x69;
88
89        internal static readonly short KEYEVENTF_EXTENDEDKEY    = 0x0001;
90        internal static readonly short KEYEVENTF_KEYUP          = 0x0002;
91
92        internal static readonly int GWL_EXSTYLE    = -20;
93        internal static readonly int WS_DISABLED    = 0X8000000;
94        internal static readonly int WM_SETFOCUS    = 0X0007;
95    }

屏幕键盘必须是一个不能获得输入焦点的窗体,在这个窗体的构造函数里,可以安装
一个全局鼠标钩子,再通过调用 SetWindowLong API 函数完成。

 1UserActivityHook hook = new UserActivityHook(truetrue);
 2hook.MouseActivity += HookOnMouseActivity;
 3
 4private void HookOnMouseActivity(object sener, HookEx.MouseExEventArgs e) {
 5    Point location = e.Location;
 6
 7    if (e.Button == MouseButtons.Left) {
 8        Rectangle captionRect = new Rectangle(this.Location, new Size(this.Width, 
 9            SystemInformation.CaptionHeight));
10        if (captionRect.Contains(location)) {
11            NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
12                (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE)
13                 & (~KeyboardConstaint.WS_DISABLED));
14            NativeMethods.SendMessage(this.Handle, KeyboardConstaint.WM_SETFOCUS, IntPtr.Zero, IntPtr.Zero);
15        }
 else {
16            NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
17                (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE) | 
18                 KeyboardConstaint.WS_DISABLED);
19        }

20    }

21}

 

鼠标单击标题栏,让屏幕键盘可以接收焦点,并激活,单击其他部分则不激活窗体(如果激活了,其他程序必然取消激活,
输入就无法进行了),这样才可以进行输入,并且保证了可以拖动窗体到其他位置。

至此,一个屏幕键盘程序差不多完成了,能够实现与实际键盘完全同步。至于窗体,按键重绘,以及 Num Lock, Caps Lock,
Scroll Lock 等键盘灯的模拟,这里就不讲了,如果有兴趣,可以下载完整的代码。最后我们的屏幕键盘程序运行的效果如
下图:



点击下载完整源代码

 说明:本程序参考了 Jeffrey Richter 先生的著作 CLR via C#, Second Edition, MSDN 以及一些网络资料。

这是微软技术的一贯特点,使用简单。但是如果要深入的话,还是要投入不少精力的
Tag标签: C#,.Net,自绘,钩子,API

posted on 2008-05-19 17:35 优哉@游哉 阅读(5271) 评论(59)  编辑 收藏 网摘

评论

#1楼   回复  引用  查看    

很有用,可以做按键精灵了。
2008-05-19 17:51 | 天下³      

#2楼   回复  引用  查看    

mark
最近正在看这些内容。
hook 在 OS 里面越来越多了。现在都快要平民化了。
2008-05-19 18:16 | 孤剑      

#3楼   回复  引用  查看    

http://www.cnblogs.com/Emoticons/yoyocici/224023586.gif" alt="" />
2008-05-19 18:24 | CoderZh      

#4楼   回复  引用    

很好 很强大
2008-05-19 18:29 | 风[未注册用户]

#5楼   回复  引用  查看    

支持,学习了
2008-05-19 19:22 | Justin      

#6楼   回复  引用  查看    

支持,学习.
2008-05-19 19:44 | 侯垒      

#7楼   回复  引用  查看    

唔,.Net是不支持Global Hook的,不过对于键盘和鼠标hook可以有专门的函数专门支持
2008-05-19 19:52 | lbq1221119      

#8楼   回复  引用  查看    

@Justin
回复两次了 呵呵 到处都可以看到熟人啊
2008-05-19 19:52 | lbq1221119      

#9楼   回复  引用    

这篇文章写的真好,目前正在搞一个学校应用系统的 "学生考试系统" .
BS结构的.
在考虑如何屏蔽某些键,让学生不能作弊,非常好啊...解燃眉之急.
感谢了.
2008-05-19 20:16 | cyberguest[未注册用户]

#10楼   回复  引用  查看    

支持,不错的东西,收藏了
2008-05-19 20:39 | 高_超      

#11楼   回复  引用  查看    

确实不错,收藏
2008-05-19 20:45 | 生鱼片      

#12楼   回复  引用  查看    

2008-05-19 20:51 | Klesh Wong      

#13楼   回复  引用  查看    

很好 很强大!!
好人一生平安
2008-05-19 21:16 | zhuds      

#14楼   回复  引用  查看    

很好很不错
2008-05-19 21:17 | luciamobile      

#15楼   回复  引用  查看    

.net不支持全局钩子?真奇怪。
2008-05-19 21:45 | Phinecos(洞庭散人)      

#16楼   回复  引用  查看    

哇.很好很强大.收藏一下下...支持
2008-05-19 21:55 | airwolf2026      

#17楼   回复  引用  查看    

谢谢!!!
2008-05-19 22:46 | <流泳>      

#18楼   回复  引用  查看    

支持
2008-05-20 07:12 | 杜伟光      

#19楼   回复  引用  查看    

支持一下。。。
2008-05-20 09:01 | winzheng      

#20楼   回复  引用    

我 也 来顶一下 。。挺好的

#21楼   回复  引用  查看    

看了这篇文章,感到略感震撼,因为我也大概研究学习过HOOK,因此感到这篇文章的工作并不是一个简单容易的工作,。因此抱着一种学习和求证的态度下载了楼主的代码,令我很惊奇,这个例子写的非常好,里面的挖掘深度也很深,使我非常敬佩。
-----------------------------------------------------------------
但是从另一方面来讲,鉴于国内的研究氛围和习惯较差,我第一反应甚至是不相信这是心态比较浮躁的中国人会有心思去研究的(请原谅我的第一反应竟然如此)。而且我也在源码中发现了基本上是全英文化(包括菜单的文字),大量的较典型的外国人的注释风格。为此我搜索了网络,搜索到国内的一些类似技术帖子声称是借鉴了codepoject上的例子。于是找到这样一片文章:
http://www.codeproject.com/KB/cs/globalhook.aspx" target="_new">http://www.codeproject.com/KB/cs/globalhook.aspx
《Processing Global Mouse and Keyboard Hooks in C#》(by George Mamaladze),显示该文最初发表与2004年6月3日,作者是在德国工作的一个程序员。从内容上看,该文主要是解决在C#中实现安装全局HOOK。因为在MSDN文档上明确说明.NET Framework不支持全局HOOK。而作者也指出另一个codeproject上的相似文章使用了Native的C++的DLL来实现全局hook。在该文中代码中有一个UserActivityHook类与楼主代码中基本雷同,一些注释也一模一样。在这件事上我宁愿相信老外,因为老外的研究态度通常都比较严谨求是。
----------------------------------------------------------------
在楼主的代码中还有一些实现比较不错的地方,比如按钮的自绘制,等等。我希望楼主不要认为这是我对楼主工作的否定,但是我认为我们必须对自己也对他人负责。
-----------------------------------------------------------------
2008-05-20 10:55 | hoodlum1980      

#22楼   回复  引用  查看    

略微看了一下代码 great!
再仔细瞧瞧...
2008-05-20 10:56 | xrainfir      

#23楼[楼主]   回复  引用  查看    

@hoodlum1980
这个程序是我个人毕业设计时用到的一个模块, 后来经过了一些修正。
没错, 当时有关Hook的内容当然在网上搜索了不少资料,这些内容网上
网上到处转载,具体是谁写的也记不清楚了,但可以保证的是,我不只
参考了一个人写的东西,还包括MSDN platform SDK。
可能由于我的疏忽,未在文章中对这些贡献者表示感谢,在此表示谦
意,并在此对这些人表示感谢,同时也感谢@hoodlum1980指出了这一
问题。
其中EventSet的实现参考 Jeffrey Richter 先生的 CLR via C#,
Second Edition,他实现的是提供一种线程安全的一个类,如果你看
了完全代码的话,可以知道它是用 Dictionary 实现的,代替
BCL 中的EventHandlerList,同时提高了算法效率。(EventHandlerList
是微软 BCL 类库中广泛用于控件事件注册用到的一个类,本身是用 链表
方式实现的。)
Hook 的实现参考了网上的资料,在此无法找到是哪些了,控件的自绘纯粹
是我个人写的,ImageProcessHelper.cs 实现参考了 BCL 的实现 (通过反
射),SendInput, SendMessage 等 API 函数,设置窗口WS_DISABLED
风格,键盘虚拟码,等是从 MSDN 上查到的。
在此对这些原创者再次表示感谢
2008-05-20 11:35 | 优哉@游哉      

#24楼   回复  引用    

.net不支持全局钩子?真奇怪。

通常情况下好像只能用键盘和鼠标的全局钩子,这是.net 的特性所决定的
2008-05-20 17:36 | jijl2001[未注册用户]

#25楼   回复  引用  查看    

好东东,支持一下
2008-05-20 19:39 | reaper      

#26楼   回复  引用  查看    

楼主,想问你几个问题
像在消息结构中使用[StructLayout(LayoutKind.Sequential)] [FieldOffset(4)]
这些属性,和键盘输入的数据结构,是MSDN上提到的吗
2008-05-21 10:54 | jdxyw      

#27楼[楼主]   回复  引用  查看    

@jdxyw
你可以看看 MSDN platform sdk 中关于 SendInput 函数的说明:
Syntax

UINT SendInput( UINT nInputs,
LPINPUT pInputs,
int cbSize
);
Parameters

nInputs
[in] Number of structures in the pInputs array.
pInputs
[in] Pointer to an array of INPUT structures. Each structure represents an event to be inserted into the keyboard or mouse input stream.
cbSize
[in] Specifies the size, in bytes, of an INPUT structure. If cbSize is not the size of an INPUT structure, the function fails.

说的很明白, 参数 pInputs是指向 INPUT 结构体的指针, 再看看 INPUT 结构体的定义:

typedef struct tagINPUT {
DWORD type;
union {MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
};
}INPUT, *PINPUT;

很明显, mi, ki, hi 是在一个共用体里面的, 对于 P/Invoke 的调用, 一般应该声明结构上加特性 [StructLayout(LayoutKind.Sequential)], 这是由托管代码
与非托管代码的内存布局差别来决定的(具体你可以查阅网络。) Marshal 的时候才不会出错,而共用体是一个特殊的结构,C# 里面没有一种表示方法与之对应,因此必须声明为 [StructLayout(LayoutKind.Explicit)], 意思是自己告诉
类型系统怎么布局内存,[FieldOffset(int)]可以完成这样的工作。

不知道能否回答你的问题
2008-05-21 11:06 | 优哉@游哉      

#28楼[楼主]   回复  引用  查看    

关于源代码的一点修改:
打开这个程序, 滚动鼠标滚轮, 程序可能会崩溃,原因是取得一个 DWORD
的高位字节的时候,(mouseHookStruct.mouseData >> 16) & 0xffff
可能会超出 short 所能表示的范围,直接转换为 short 类型会抛出异常,
修改方法是添加 unchecked 关键字。
UserActivityHook.cs 源文件中 MouseHookProc 方法中的
mouseDelta = (short)((mouseHookStruct.mouseData >> 16)
& 0xffff) 改为 mouseDelta = unchecked((short)
((mouseHookStruct.mouseData >> 16) & 0xffff)) 即可。
2008-05-23 01:12 | 优哉@游哉      

#29楼   回复  引用    


GOOD!

#30楼   回复  引用    

看样不错,功能很强大,收藏了,强力支持。
2008-05-26 13:29 | 漫游者[未注册用户]

#31楼   回复  引用  查看    

学习.谢谢楼主.我对HOOK这个东西,还没什么概念呀.?
不知道有什么入门书籍吗?
2008-05-31 21:57 | hackenliu      

#32楼[楼主]   回复  引用  查看    

@hackenliu
HOOK 是Windows 中深入一点儿的东西, 一般使用 windows 本地 C/C++
比较容易实现, 你可以去网上搜索一些资料, 至于书籍, 我也接触的不多,
Jeffrey Richter 先生的著作 Windows 核心编程 里面有些介绍.

MS确实说过.Net 不支持Global Hook(但是对于应用程序级的HOOK
却是很容易实现的), 鼠标和键盘是目前成功应用的特例, 所以你要深入
研究的话, 建议你使用 C/C++ 来实现.
2008-06-01 11:31 | 优哉@游哉      

#33楼   回复  引用  查看    

Mark
2008-06-14 17:53 | -=FengLiN=-      

#34楼   回复  引用    

不得不感谢楼主提供那么好用的代码。
2008-06-20 16:05 | birdlong[未注册用户]

#35楼   回复  引用    

不 错
2008-07-03 22:47 | 幸[未注册用户]

#36楼   回复  引用    

我把楼主的代码改成了VS2005的了,我有一个奇怪的问题是,程序在调试状态下运行结果不对,在运行状态下执行就对了,是不是因为某些原因程序不能处于调试状态?小弟比较菜,请楼主指示.
2008-07-08 19:23 | 未注册[未注册用户]

#37楼[楼主]   回复  引用  查看    

@未注册
这里模拟的是一个标准键盘, 目前已知的问题是, 我使用使用笔记本电脑 Numlock 不能同步, 调试结果不对的问题我没有碰到过, 可否描述详细些?
2008-07-08 20:10 | 优哉@游哉      

#38楼   回复  引用    

@优哉@游哉
我描述的有问题不是调试不对是会抛出一个异常
在这句代码处: throw new Win32Exception(errorCode);
2008-07-08 20:18 | 未注册[未注册用户]

#39楼   回复  引用    

太感谢你的好东东了

#40楼   回复  引用    

原来在delphi下编过一个模拟键盘,但在.net因为焦点问题一直没办法做
所以非常感谢。

#41楼   回复  引用    

谢谢!我也毕业设计也要用到这个东西
可是还有一个问题没有解决
焦点的获取和都是最顶层窗体怎么让他们互存
2008-09-22 15:35 | 金!刚[未注册用户]

#42楼[楼主]   回复  引用  查看    

@金!刚
输入焦点如果在同一个窗体上, 实际上就不需要 Hook(当然用了也不会有问题), 去掉 OnLoad 里面的
NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE, (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE) | KeyboardConstaint.WS_DISABLED);
语句。
同时在各个键的 KeyUp 事件中让输入控件 Focus 就可以了
2008-09-24 08:34 | 优哉@游哉      

#43楼   回复  引用  查看    

最近我想做一个比较安全的屏幕键盘,请问博主有什么方法能够让发出去的按键不被hook到么?
我试过.net自带的sendkeys还用过sendInput和keybd_event都会被键盘钩子拦截到
请问博主有什么好方法阻止这种情况么?
2008-09-29 12:17 | Samson小天      

#44楼   回复  引用    

@优哉@游哉
谢谢你!
可是我原来的窗体也是顶层窗体且全屏
我让我窗体获得焦点时你的这个键盘就不是顶层窗体啦,消失在后面,无法使用
这个问题怎么解决捏!
谢谢指教!
2008-10-07 10:52 | 金 刚[未注册用户]

#45楼[楼主]   回复  引用  查看    

@金 刚
这种情况你不能直接使用这个程序, 你可以把键盘按键放到你的那个窗体上, 拷贝相应的代码就可以了。要注意的是点击按键时切换输入焦点,如果你无法得到你的输入控件的句柄,那么可以改造你的按键,不要使用CanFous为true的控件就可以了,比如直接继承Control。
2008-10-09 12:30 | 优哉@游哉      

#46楼[楼主]   回复  引用  查看    

@Samson小天
我还没有这方面的资料,可以上网找下,一些第三方的产品,比如 QQ带有密码保护的键盘,银行的输入控件等都是不能被HOOK到的,不过好像这些保护程序是要收费的。
2008-10-09 12:36 | 优哉@游哉      

#47楼   回复  引用  查看    

@优哉@游哉
这些技术因为是收费的,所以找不太到资料
不过还是谢谢楼主~
2008-10-09 18:27 | Samson小天      

#48楼   回复  引用  查看    

C#中keysend的编写思路是什么?
我想用两个按钮
一个shift一个$
当我同时按SHIFT和$时就是$只按$时是一个4怎么办呢?
2008-10-15 11:05 | 飞花浇叶      

#49楼[楼主]   回复  引用  查看    

@飞花浇叶
你可以看看源代码。
调用 Windows API SendInput 函数实现的, 也可以使用 keybd_event 函数

按下Shift->按下$对应键->弹起$对应键->弹起Shift 如果是英文输入状态就是一个 $, 只按 $ 应该是4.
实际上你不可能同时按下两个键的,总是会有先后顺序的。
2008-10-15 12:51 | 优哉@游哉      

#50楼   回复  引用    

看了您的代码,特别感兴趣。
您把Hook的实现做到了dll中,并封装到类里面。
请问 我想从外部应用程序使用您dll中的方法,改如何调用呢?
2008-12-05 15:01 | yatany[未注册用户]

#51楼[楼主]   回复  引用  查看    

@yatany
从托管代码调用非常简单, 引用 HookEx.dll, 声明一个 filed:

private HookEx.UserActivityHook hook = new HookEx.UserActivityHook(false, false);

注册事件:

this.hook.KeyDown += HookOnGlobalKeyDown;
this.hook.KeyUp += HookOnGlobalKeyUp;
this.hook.MouseActivity += HookOnMouseActivity;

实现相应的处理.
void HookOnGlobalKeyDown(object sender, HookEx.KeyExEventArgs e) {
// TODO
}
......
2008-12-06 10:43 | 优哉@游哉      

#52楼   回复  引用    

另一个疑问
我的一个工程是用VC++ 开发的,想部分使用您的屏幕键盘的功能,比较好的方法是什么呢?
谢谢!
2008-12-08 09:57 | yatany[未注册用户]

#53楼   回复  引用  查看    

好东西,收藏.
2008-12-19 15:31 | stg609      

#54楼   回复  引用  查看    

So good
2009-01-05 14:31 | yjh4866      

#55楼   回复  引用    

屏幕键盘最关键的东西就是2个:
一个是无焦点窗体:
NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
12 (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE)
13 & (~KeyboardConstaint.WS_DISABLED));

另外一个就是按键发送
NativeMethods.SendInput((uint)input.Length, input, Marshal.SizeOf(input[0]))
2009-03-11 10:38 | iwome[未注册用户]

#56楼   回复  引用  查看    

好了,这下可以做病毒了,至少可以做个键盘监听器,偷个帐号什么的。
呵呵...
2009-03-11 11:13 | yjh4866      

#57楼   回复  引用    

好热的帖子,纯顶
2009-05-11 10:14 | 11111111111[未注册用户]



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1202732




相关文章:

相关链接: