C# SetWindowsHookEx不会对媒体键作出反应
我想捕获用户键盘输入,以便在键盘媒体键上做出良好反应:播放/暂停,尤其是下一个和上一个。我曾尝试将SetWindowsHookEx与低级WH_-KEYBOARD_-LL参数一起使用,以确保我能从这个WINAPI函数中获得最大的责任,但我什么都没有做。若我在钩子回调中设置了断点,当我按常规键(如字母或F修饰符)时,调试器会在任何键盘事件上停止,但当我按其中一个媒体键(如Play/Payse)时,调试器不会停止。我试着下载演示SetWindowsHookEx用法的演示应用程序,但没有一个能与MM键配合使用。我也读过,但里面没有答案 我使用这段代码设置了hook:C# SetWindowsHookEx不会对媒体键作出反应,c#,winapi,keyboard-hook,C#,Winapi,Keyboard Hook,我想捕获用户键盘输入,以便在键盘媒体键上做出良好反应:播放/暂停,尤其是下一个和上一个。我曾尝试将SetWindowsHookEx与低级WH_-KEYBOARD_-LL参数一起使用,以确保我能从这个WINAPI函数中获得最大的责任,但我什么都没有做。若我在钩子回调中设置了断点,当我按常规键(如字母或F修饰符)时,调试器会在任何键盘事件上停止,但当我按其中一个媒体键(如Play/Payse)时,调试器不会停止。我试着下载演示SetWindowsHookEx用法的演示应用程序,但没有一个能与MM键配
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYUP = 0x0101;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static IntPtr SetHook()
{
using (var curProcess = Process.GetCurrentProcess())
{
using (var curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
}
public static void UnsetHook()
{
UnhookWindowsHookEx(_hookID);
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
Debug.WriteLine((Keys)vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
我发现,
RegisterHotKey
的信息应该对我的任务有所帮助,但因为我也希望对常规键做出反应,所以对于所有常规键和MM键,使用一个API成员将非常舒服。这是死路一条还是我遗漏了什么?问题在于多媒体(mm)键没有普通键那么标准化。不管windows有什么特殊的代码来捕捉mm按键,使用与否取决于键盘供应商。有时您还需要安装驱动程序以使其正常工作
首先,检查你的键盘是否触发了这些事件:使用一些实用工具,比如键盘映射器。同时检查它是否在一些流行的mm播放器中工作
尝试在钩子中捕获所有的WM\u键控
WM\u键控
WM\u系统键控
WM\u系统键控
使用Spy++,您可能会发现一些异常事件,帮助您解决任务
快乐编码!=) 这是因为这些键不会生成键盘消息。相反,它们会生成新的。例如,按下播放按钮生成APPCOMMAND\u MEDIA\u Play 该消息被发送到拥有焦点的任何窗口。这样的窗口几乎从不对消息进行操作,而是将其传递给默认的窗口过程。它把它捡起来,并把它传递给外壳。挂钩技术上是可行的,你需要一个WHU外壳挂钩。获取HSHELL\u APPCOMMAND通知
但这通常是好消息的结尾,您不能用托管语言编写全局钩子。因为它们需要一个DLL,可以在创建窗口的每个进程中注入。这样的DLL不能是托管DLL,进程将不会加载CLR。它实现了这样一个DLL,并展示了一个如何钩住shell通知的示例,但不知道它的工作情况如何。这些项目往往无法满足现代Windows版本的要求,这些版本需要32位和64位挂钩,并且需要为UAC提升的进程做一些合理的事情。在询问之前,我仔细搜索了一下,得到的只是其他令人沮丧的家伙在一些论坛上未回复的帖子。所以,当我最终得到它的工作,我想张贴完整的答案,为未来的游客 序言 汉斯·帕桑(Hans Passant)在回答中提出了基于Lib的最终解决方案,但在此之前,我试图创造出自己的东西,但失败了。不知道我是否缺少C++体验(我写了3年前的CPP代码的最后一行)或者我选择了死亡方法,但是我的LIB调用了一些shell消息的回调,但是从来没有在MM键上调用。所以我读了codeproject的文章并下载了代码。如果您尝试演示,您将看到它对MM键也没有反应。幸运的是,它需要微小的修改才能实现 解决方案 <> Pr++的核心是C++项目。您需要在stdafx.h中进行更改:
#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
版本为0x0400,因此无法使用winuser.h中的某些常量,尤其是HSHELL\u APPCOMMAND
。但正如汉斯·帕桑特(Hans Passant)所提到的,这个信息正是我们想要的!所以,在修改标头之后,您可以在GlobalCbtHook.cpp(方法ShellHookCallback,我的第136行)中注册新的自定义消息:
这就是我们在非托管库中需要更改的全部内容。重新编译dll。请注意,与.NET代码的调试和发布配置不同,C++代码在编译代码大小和性能方面有很大差异。
您还需要在C#应用程序中处理新的自定义WILSON_HOOK_HSHELL_APPCOMMAND消息。您可以从演示应用程序中使用helper类GlobalHooks.cs,您需要添加消息id成员、APPCOMMAND的适当事件,并在自定义消息出现时启动它。在项目页面上可以找到有关使用帮助器的所有信息。然而,由于我只需要MM keys hook,并且我的应用程序是WPF,所以我不能使用来自helper的ProcessMessage,所以我决定不使用helper并编写小包装,如下所示:
#region MM keyboard
[DllImport("GlobalCbtHook.dll")]
public static extern bool InitializeShellHook(int threadID, IntPtr DestWindow);
[DllImport("GlobalCbtHook.dll")]
public static extern void UninitializeShellHook();
[DllImport("user32.dll")]
public static extern int RegisterWindowMessage(string lpString);
private const int FAPPCOMMAND_MASK = 0xF000;
private const int APPCOMMAND_MEDIA_NEXTTRACK = 11;
private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
private const int VK_MEDIA_STOP = 13;
private const int VK_MEDIA_PLAY_PAUSE = 14;
private int AppCommandMsgId;
private HwndSource SenderHwndSource;
private void _SetMMHook()
{
AppCommandMsgId = RegisterWindowMessage("WILSON_HOOK_HSHELL_APPCOMMAND");
var hwnd = new WindowInteropHelper(Sender).Handle;
InitializeShellHook(0, hwnd);
SenderHwndSource = HwndSource.FromHwnd(hwnd);
SenderHwndSource.AddHook(WndProc);
}
private static int HIWORD(int p) { return (p >> 16) & 0xffff; }
public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == AppCommandMsgId)
{
int keycode = HIWORD(lParam.ToInt32()) & ~FAPPCOMMAND_MASK; //#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
// Do work
Debug.WriteLine(keycode);
if (OnKeyMessage != null)
{
OnKeyMessage(null, EventArgs.Empty);
}
}
return IntPtr.Zero;
}
#endregion
#region Managed
private readonly Window Sender;
private KeyboardHook() { }
public KeyboardHook(Window sender)
{
Sender = sender;
}
public void SetMMHook()
{
_SetMMHook();
}
public event EventHandler OnKeyMessage;
private bool Released = false;
public void ReleaseAll()
{
if (Released) return;
UninitializeShellHook();
SenderHwndSource.RemoveHook(WndProc);
Released = true;
}
~KeyboardHook()
{
ReleaseAll();
}
#endregion
实际上,它除了从消息的LPRAM中解码密钥代码并激发空事件外,什么都不做,但这就是您所需要的;您可以为事件定义自己的委托,您可以创建任意键enum等。对于我来说,我可以为Play_Pause、Prev、Next键获得正确的键码-这正是我所寻找的。键盘触发事件,因为它可以在媒体播放器中工作,例如Foobar 2K;医生说钥匙密码的标准化很好。当然,我不确定我的键盘供应商是否添加了他自己的东西,但再次强调,所需的prev next play按钮有效。在程序甚至调用提供的回调时,检查事件类型(WM_KEYDOWN/WM_keydup等)是有意义的。但不幸的是,事实并非如此。不管怎样,谢谢你的回答,明白了。在回答之前,我尝试使用RegisterHotKey,但它只在窗口聚焦时接收WM_APPCOMMAND消息,所以我猜您所指的项目是达到我想要的目标的唯一途径。感谢您的详细解释。请发布一个工作wpf工作示例,我所有的尝试都是一次又一次的失败+$exception{“值不能为空。\r\n参数名称:window}System.ArgumentNullException,现在{“在DLL'GlobalCbtHook.DLL'中找不到名为'InitializeShellHook'的入口点”。:“}确定,朋友,这是一个非常古老的(6岁以上)年份
#region MM keyboard
[DllImport("GlobalCbtHook.dll")]
public static extern bool InitializeShellHook(int threadID, IntPtr DestWindow);
[DllImport("GlobalCbtHook.dll")]
public static extern void UninitializeShellHook();
[DllImport("user32.dll")]
public static extern int RegisterWindowMessage(string lpString);
private const int FAPPCOMMAND_MASK = 0xF000;
private const int APPCOMMAND_MEDIA_NEXTTRACK = 11;
private const int APPCOMMAND_MEDIA_PREVIOUSTRACK = 12;
private const int VK_MEDIA_STOP = 13;
private const int VK_MEDIA_PLAY_PAUSE = 14;
private int AppCommandMsgId;
private HwndSource SenderHwndSource;
private void _SetMMHook()
{
AppCommandMsgId = RegisterWindowMessage("WILSON_HOOK_HSHELL_APPCOMMAND");
var hwnd = new WindowInteropHelper(Sender).Handle;
InitializeShellHook(0, hwnd);
SenderHwndSource = HwndSource.FromHwnd(hwnd);
SenderHwndSource.AddHook(WndProc);
}
private static int HIWORD(int p) { return (p >> 16) & 0xffff; }
public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == AppCommandMsgId)
{
int keycode = HIWORD(lParam.ToInt32()) & ~FAPPCOMMAND_MASK; //#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
// Do work
Debug.WriteLine(keycode);
if (OnKeyMessage != null)
{
OnKeyMessage(null, EventArgs.Empty);
}
}
return IntPtr.Zero;
}
#endregion
#region Managed
private readonly Window Sender;
private KeyboardHook() { }
public KeyboardHook(Window sender)
{
Sender = sender;
}
public void SetMMHook()
{
_SetMMHook();
}
public event EventHandler OnKeyMessage;
private bool Released = false;
public void ReleaseAll()
{
if (Released) return;
UninitializeShellHook();
SenderHwndSource.RemoveHook(WndProc);
Released = true;
}
~KeyboardHook()
{
ReleaseAll();
}
#endregion