VSTO Windows Hook按下事件被调用10次
作者:互联网
因此,我一直在开发一个类来处理VSTO加载项中的Kwyboard输入,到目前为止,我一直在使用Windows挂钩来取得相对成功.
具有以下代码:
//.....
private const int WH_KEYBOARD = 2;
private const int WH_MOUSE = 7;
private enum WM : uint {
KEYDOWN = 0x0100,
KEYFIRST = 0x0100,
KEYLAST = 0x0108,
KEYUP = 0x0101,
MOUSELEFTDBLCLICK = 0x0203,
MOUSELEFTBTNDOWN = 0x0201,
MOUSELEFTBTNUP = 0x0202,
MOUSEMIDDBLCLICK = 0x0209,
MOUSEMIDBTNDOWN = 0x0207,
MOUSEMIDBTNUP = 0x0208,
MOUSERIGHTDBLCLK = 0x0206,
MOUSERIGHTBTNDOWN = 0x0204,
MOUSERIGHTBTNUP = 0x0205
}
private hookProcedure proc;
private static IntPtr hookID = IntPtr.Zero;
//Enganches
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool unHookWindowsHookEx(int hookId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string name);
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetCurrentThreadId();
public CPInputListener() {
proc = keyBoardCallback;
hookID = setHook(proc);
}
private IntPtr setHook(hookProcedure procedure){
ProcessModule module = Process.GetCurrentProcess().MainModule;
uint threadId = (uint)GetCurrentThreadId();
return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId);
}
public void stopListeningAll() {
unHookWindowsHookEx(WH_KEYBOARD);//For now
}
private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) {
if (ncode >= 0) {
//LPARAM pretty useless
Keys key = (Keys)wParam;
KeyEventArgs args = new KeyEventArgs(key);
onKeyDown(args);//for now
}
return CallNextHookEx(hookID, ncode, wParam, lParam);
}
//....
我确实成功接收了键盘输入,但是这是个大麻烦.每次按下一个键,无论它有多快,事件(onKeyDown)都会被准确地调用10次,也不会少于10次.
如果长按该键,则该事件将继续被调用,但10次调用10次,而不是仅调用一次.
到目前为止,我已经尝试过
>使用wParam在Key Up上调用所需的事件:似乎不起作用,在我看到的所有处理Key down和up事件的代码中,都使用IntPtr wParam,但是从该变量中,我只能检索没有帮助.
>使用lParam或nCode:这些var在这10个调用之间给出不一致的值,ncode倾向于检索0和3,lParam似乎是非托管内存地址的某些值…
我期望什么
我确实希望onKeyDown可以在按下键时被调用一次,或者另一方面能够通过on键向上调用该方法,我希望每次释放键都可以调用一次.
如何绕过这个
如果找不到合理的答案,我正在考虑使用定制计时器来丢弃所有这些调用,而仅使用最后一个调用,如果其他所有操作都失败,您会建议这样做吗?
非常感谢!要快乐,要友善! :D
解决方法:
首先,您必须过滤正确的ncode,以仅获取应该处理的击键. (例如,您不应该处理HC_NOREMOVE.)
然后,您必须使用lParam中的标志检查它是KeyDown还是KeyUp事件.
如果长按该键,则Win32已经将多个KeyDown事件组合到一个调用中,因此您无需在此处进行任何特殊操作.但是,如果只想获取最后一个KeyUp事件,则还必须检查lParam中的另一个标志.
因此,这是您需要更改的代码:
private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam)
{
// Feel free to move the const to a private field.
const int HC_ACTION = 0;
if (ncode == HC_ACTION)
{
Keys key = (Keys)wParam;
KeyEventArgs args = new KeyEventArgs(key);
bool isKeyDown = ((ulong)lParam & 0x40000000) == 0;
if (isKeyDown)
onKeyDown(args);
else
{
bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000;
if (isLastKeyUp)
onKeyUp(args);
}
}
return CallNextHookEx(hookID, ncode, wParam, lParam);
}
根据评论中的要求进行编辑:
不幸的是,这些参数的文档很少.
可以找到here的一个“提示”,不处理HC_ACTION以外的任何内容,并指出:
if (nCode < 0) // do not process message
return ...;
// ...
switch (nCode)
{
case HC_ACTION:
// ... do something ...
break;
default:
break;
}
// ...
return CallNextHookEx(...);
在此发表另一项支持声明:
Why does my keyboard hook receive the same key-up and key-down events multiple times?
lParam的内容定义为as follows:
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
}
(仅提醒一下:在x86和x64平台上,DWORD的大小为4 bytes.)
lParam标志的文档位于here和here.
在此链接中描述了
>位30(= 0x40000000)是先前的键状态
(如果该键处于按下状态,则为1;如果该键在导致此调用的新键状态之前为向上,则为0)
>位31(= 0x80000000)是过渡状态
(现在按按键0,释放按键1)
术语“先前的键状态”相当混乱,但实际上,它与当前状态相反(因为只有向上或向下而没有第三种状态).
当激活“键盘的自动重复功能”时,即按下按键足够长的时间时,过渡状态尤其重要.
可以找到另一个样本(使用VC7)here:
if (HIWORD (lParam) & 0xC000)
// Key up without autorepeat
else
// Key down
其中0xC000就是0x4000 || 0x8000并定义密钥已释放并创建了密钥启动事件.
总而言之,这确实很令人困惑,但却是正确的.
也许还有其他链接可以更好地描述这种情况,但是我想在像这样的时代中,“应该”在小型沙箱(如UWP)中进行新的应用程序开发,而VSTO肯定会消亡,为用HTML and JavaScript编写的newer Office add-ins,现在再也没有人关心低级钩子了.
标签:hook,windows,c,ms-word,vsto 来源: https://codeday.me/bug/20191112/2024328.html