Summary:
本文所讲的技术:底层键盘Hook拦截Win+R系统快捷键,使用全局互斥变量使程序同一时间只有一个实例启动。
Detail:
捂了那么多天,UltraRun终于决定还是拿出来亮亮相吧。UltraRun是我处于英文狂的时候开发的,所以所有界面都是E文的。不过这里有点技术还是值得说一下的。
UltraRun是作为Windows Run的替代品(其界面如下),是不是发现很想Windows的“运行”,其实本来就是照着那个样子做的,只是功能上拓展了下而已。
拓展的选项,可以用昵称启动自己想要的程序:
这里我要讲的第一个技术是使用底层键盘HOOK来拦截Win+r这个系统快捷键。大家都知道键盘上Windows键和R键一起按就启动了Windows的“运行”功能。这个快捷键是系统全局热键,不仅不让你重新注册,启动后连事件都不转发一个的。所以呢,要想替代这个快捷键,我就想到了用底层键盘HOOK。
首先送上HOOK代码,本段代码摘自CodeProject,并且经过修改,修正了HookProc被回收的问题。
Code
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Runtime.InteropServices;
5using System.Windows.Forms;
6
7namespace KeyBoardHook
8{
9 /**//// <summary>
10 /// A class that manages a global low level keyboard hook
11 /// </summary>
12 public class globalKeyboardHook:IDisposable
13 {
14 Constant, Structure and Delegate Definitions#region Constant, Structure and Delegate Definitions
15 /**//// <summary>
16 /// defines the callback type for the hook
17 /// </summary>
18 public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
19 public keyboardHookProc HProc;
20 public bool WinDown = false;
21 public bool RDown = false;// to solve when r released first the win press will still be recieved by windows
22 public struct keyboardHookStruct
23 {
24 public int vkCode;
25 public int scanCode;
26 public int flags;
27 public int time;
28 public int dwExtraInfo;
29 }
30
31 const int WH_KEYBOARD_LL = 13;
32 const int WM_KEYDOWN = 0x100;
33 const int WM_KEYUP = 0x101;
34 const int WM_SYSKEYDOWN = 0x104;
35 const int WM_SYSKEYUP = 0x105;
36 #endregion
37
38 Instance Variables#region Instance Variables
39 /**//// <summary>
40 /// The collections of keys to watch for
41 /// </summary>
42 public List<Keys> HookedKeys = new List<Keys>();
43 /**//// <summary>
44 /// Handle to the hook, need this to unhook and call the next hook
45 /// </summary>
46 IntPtr hhook = IntPtr.Zero;
47 #endregion
48
49 Events#region Events
50 /**//// <summary>
51 /// Occurs when one of the hooked keys is pressed
52 /// </summary>
53 public event KeyEventHandler KeyDown;
54 /**//// <summary>
55 /// Occurs when one of the hooked keys is released
56 /// </summary>
57 public event KeyEventHandler KeyUp;
58 /**//// <summary>
59 /// Occurs when Win and R pressed together
60 /// </summary>
61 public delegate void WinRunNow();
62 public event WinRunNow WinRunEvent;
63 #endregion
64
65 Constructors and Destructors#region Constructors and Destructors
66 /**//// <summary>
67 /// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
68 /// </summary>
69 public globalKeyboardHook()
70 {
71 // hook();
72 HProc = new keyboardHookProc(hookProc);
73 }
74
75 /**//* /// <summary>
76 /// Releases unmanaged resources and performs other cleanup operations before the
77 /// <see ref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
78 /// </summary>
79 ~globalKeyboardHook()
80 {
81 unhook();
82 }*/
83 #endregion
84
85 Public Methods#region Public Methods
86 /**//// <summary>
87 /// Installs the global hook
88 /// </summary>
89 public void hook()
90 {
91 // GC.KeepAlive(this);
92 IntPtr hInstance = LoadLibrary("User32");
93 hhook = SetWindowsHookEx(WH_KEYBOARD_LL, HProc, hInstance, 0);
94 }
95
96 /**//// <summary>
97 /// Uninstalls the global hook
98 /// </summary>
99 public void unhook()
100 {
101 UnhookWindowsHookEx(hhook);
102 }
103
104 /**//// <summary>
105 /// The callback for the keyboard hook
106 /// </summary>
107 /// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
108 /// <param name="wParam">The event type</param>
109 /// <param name="lParam">The key hook event information</param>
110 /// <returns></returns>
111 public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
112 {
113 if (code >= 0)
114 {
115 Keys key = (Keys)lParam.vkCode;
116
117 KeyEventArgs kea = new KeyEventArgs(key);
118
119 if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
120 {
121 if (key == Keys.LWin || key == Keys.RWin)
122 {
123 WinDown = true; //If win is down set WinDown true
124 if (RDown)
125 KeyDown(this, kea);
126 }
127 else
128 {
129 if (WinDown&&key == Keys.R) // if win & r are pressed together cause event
130 {
131 RDown = true;
132 KeyDown(this, kea); //cause key down event, you can handle it to prevent windows receive.
133 DoWinRunNow(); // send a message that win & r are pressed together
134 }
135 }
136 }
137 else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) )
138 {
139 if (key == Keys.LWin || key == Keys.RWin)
140 {
141 WinDown = false; //Set WinDown false when win key is up
142 if(RDown)
143 KeyUp(this, kea);
144 RDown = false;
145 }
146 }
147 if (kea.Handled)
148 return 1;
149 }
150 return CallNextHookEx(hhook, code, wParam, ref lParam);
151 }
152
153 private void DoWinRunNow()
154 {
155 WinRunEvent();
156 }
157 #endregion
158
159 DLL imports#region DLL imports
160 /**//// <summary>
161 /// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
162 /// </summary>
163 /// <param name="idHook">The id of the event you want to hook</param>
164 /// <param name="callback">The callback.</param>
165 /// <param name="hInstance">The handle you want to attach the event to, can be null</param>
166 /// <param name="threadId">The thread you want to attach the event to, can be null</param>
167 /// <returns>a handle to the desired hook</returns>
168 [DllImport("user32.dll")]
169 static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
170
171 /**//// <summary>
172 /// Unhooks the windows hook.
173 /// </summary>
174 /// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
175 /// <returns>True if successful, false otherwise</returns>
176 [DllImport("user32.dll")]
177 static extern bool UnhookWindowsHookEx(IntPtr hInstance);
178
179 /**//// <summary>
180 /// Calls the next hook.
181 /// </summary>
182 /// <param name="idHook">The hook id</param>
183 /// <param name="nCode">The hook code</param>
184 /// <param name="wParam">The wparam.</param>
185 /// <param name="lParam">The lparam.</param>
186 /// <returns></returns>
187 [DllImport("user32.dll")]
188 static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);
189
190 /**//// <summary>
191 /// Loads the library.
192 /// </summary>
193 /// <param name="lpFileName">Name of the library</param>
194 /// <returns>A handle to the library</returns>
195 [DllImport("kernel32.dll")]
196 static extern IntPtr LoadLibrary(string lpFileName);
197 #endregion
198
199 IDisposable 成员#region IDisposable 成员
200 /**//// <summary>
201 /// use Dispose instead of destruct
202 /// </summary>
203 public void Dispose()
204 {
205 unhook();
206 }
207
208 #endregion
209 }
210}
这段代码本来是没有
public keyboardHookProc HProc;
HProc = new keyboardHookProc(hookProc);
这两句的,因为原作者并不让窗体隐藏,所以不会造成窗体中的一些方法被回收。但是我的程序会在后台运行,所以当垃圾回收器回收内存的时候,我的HOOK因为没有窗体对其引用就被回收掉了。要阻止这种情况发生,就让程序中始终有一个变量对这个方法保留一个引用即可。所以就有了上面两句。另外原作者有对鼠标也HOOK,这里没什么作用就没有贴出啦。
这里当然也对读者们一个建议,HOOK是降低系统稳定性,并且太BT的HOOK会被杀毒软件拦截。大家可以在我的HOOKPROC方法中看到,我只判断了WIN键和R键,如果我把这两句判断去掉,对所有的按键照单全收,那么就是一个键盘记录器,也就是一个键盘记录木马。所以大家在使用的时候也要考虑清楚,是否有必要用HOOK。
另外一个今天要讲的就是利用全局互斥变量是程序只有一个实例运行。应当说网上方法也有很多,这也是我找到的其中一个方法,用这个方法因为我认为这是其中最好的。
这里简单对其他几个方法介绍下,有一个是遍历当前系统中所有进程,如果和当前进程相同,就提示“不能运行两个相同的程序”。乍看之下没什么问题,但事实上,进程是和文件名相同的,我复制一个文件,并改一下名字就可以双开了。或者有一个和你不同的程序,只是程序的可执行文件名字和你的相同,结果也会被误判为双开。
还有一个方法是当程序启动的时候在某处做一个记号,比如注册表某个值设为1,关闭后在设置为0。这方法也不错,不过这里告诉大家一个另外一个方法——利用全局互斥变量。
这个东西本来是用于线程间通讯的,也可以用作进程间通信。大家都知道静态变量(static),在整个程序的任意时刻都能被访问到,它在程序启动时就分配空间,程序退出是才释放。一个类中的静态变量不会被多次分配,也就是说一次只会有一个相同名字的静态变量。而全局互斥变量就有点像进程间的static变量,只是功能更强大(这里只提到一点,其实还有很多特性的,具体请参考 Wrox的《C#高级编程 & .net 3.0》)。
这里给出代码给大家参考:
Code
1 static void Main()
2 {
3 bool RunOne;
4 //Mutex must be released in the end
5 System.Threading.Mutex run = new System.Threading.Mutex(true, "UltraRun", out RunOne);
6 if (RunOne)
7 {
8 try
9 {
10 Application.EnableVisualStyles();
11 Application.SetCompatibleTextRenderingDefault(false);
12 using (Form_Main.global_KeyMonitor = new KeyBoardHook.globalKeyboardHook())
13 {
14 Application.Run(new Form_Main());
15 }
16 }
17 catch (System.Exception ex)
18 {
19 MessageBox.Show(string.Format("Application meet an unconsidered problem and need to be closed.\r\nError Message: {0}", ex.ToString()));
20 }
21 finally
22 {
23 run.ReleaseMutex();
24 }
25 }
26 else
27 MessageBox.Show("UltraRun is already running.", "Hint", MessageBoxButtons.OK, MessageBoxIcon.Information);
28 }