Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/104.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# SetWindowsHookEx不会对媒体键作出反应_C#_Winapi_Keyboard Hook - Fatal编程技术网

C# SetWindowsHookEx不会对媒体键作出反应

C# SetWindowsHookEx不会对媒体键作出反应,c#,winapi,keyboard-hook,C#,Winapi,Keyboard Hook,我想捕获用户键盘输入,以便在键盘媒体键上做出良好反应:播放/暂停,尤其是下一个和上一个。我曾尝试将SetWindowsHookEx与低级WH_-KEYBOARD_-LL参数一起使用,以确保我能从这个WINAPI函数中获得最大的责任,但我什么都没有做。若我在钩子回调中设置了断点,当我按常规键(如字母或F修饰符)时,调试器会在任何键盘事件上停止,但当我按其中一个媒体键(如Play/Payse)时,调试器不会停止。我试着下载演示SetWindowsHookEx用法的演示应用程序,但没有一个能与MM键配

我想捕获用户键盘输入,以便在键盘媒体键上做出良好反应:播放/暂停,尤其是下一个和上一个。我曾尝试将SetWindowsHookEx与低级WH_-KEYBOARD_-LL参数一起使用,以确保我能从这个WINAPI函数中获得最大的责任,但我什么都没有做。若我在钩子回调中设置了断点,当我按常规键(如字母或F修饰符)时,调试器会在任何键盘事件上停止,但当我按其中一个媒体键(如Play/Payse)时,调试器不会停止。我试着下载演示SetWindowsHookEx用法的演示应用程序,但没有一个能与MM键配合使用。我也读过,但里面没有答案

我使用这段代码设置了hook:

    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