为啥我的低级 Windows 键挂钩停止工作?

Posted

技术标签:

【中文标题】为啥我的低级 Windows 键挂钩停止工作?【英文标题】:Why does my low level windows key hook stop working?为什么我的低级 Windows 键挂钩停止工作? 【发布时间】:2012-01-26 21:52:30 【问题描述】:

这是我最初问题的延续 Why is D3D10SDKLayers.dll loaded during my DX11 game? 我正在创建一个 DX11 游戏,并且正在使用低级 Windows 键挂钩来捕获 Alt+Enter,这样我就可以使用自己的方法切换全屏,而不是让 Windows 自动切换,这不可避免地会导致问题。链接问题中提供了此过程和详细信息的描述。我的问题是,由于某种原因,在第 6 次 Alt+Enter 之后,键挂钩一直停止工作。我自己并没有取消注册。

这里是关键的钩子代码:


LRESULT _stdcall MyClass::WindowsKeyHook( s32 nCode, WPARAM wParam, LPARAM lParam ) 
    printf("Key hook called, nCode: %d. ", nCode);
    if( nCode < 0 || nCode != HC_ACTION )   // do not process message 
        return CallNextHookEx( MyClassVar.GetWindowsKeyHook(), nCode, wParam, lParam );
    
    printf(" Key hook status ok.\n");

    BOOL bEatKeystroke = FALSE;
    KBDLLHOOKSTRUCT* p = ( KBDLLHOOKSTRUCT* )lParam;
    switch( wParam ) 
        //NOTE: Alt seems to be a system key when it is PRESSED, but a regular key when it is released...
        case WM_SYSKEYDOWN:
            if(p->vkCode == VK_MENU || p->vkCode == VK_LMENU || p->vkCode == VK_RMENU) 
                MyClassVar.SetAltPressed(TRUE);
            
            if(MyClassVar.IsAltPressed() && p->vkCode == VK_RETURN) 
                bEatKeystroke = TRUE;
                MyClassVar.SetAltEnterUsed(TRUE);
                printf("Alt+Enter used.\n");
            
            break;
        case WM_SYSKEYUP:
            //NOTE: releasing alt+enter causes a SYSKEYUP message with code 0x13: PAUSE key...
            break;
        case WM_KEYDOWN:
            break;
        case WM_KEYUP: 
            if(p->vkCode == VK_MENU || p->vkCode == VK_LMENU || p->vkCode == VK_RMENU) 
                MyClassVar.SetAltPressed(FALSE);
            
            bEatKeystroke = ( !MyClassVar.IsShortcutKeysAllowed() &&
                                ( p->vkCode == VK_LWIN || p->vkCode == VK_RWIN ) );
            break;
        
    

    if( bEatKeystroke ) 
        return 1;
    
    else 
        return CallNextHookEx( MyClassVar.GetWindowsKeyHook(), nCode, wParam, lParam );
    

如果您需要更多信息,请告诉我需要什么。我不知道为什么会这样,所以我不确定我需要提供什么样的信息。据我所知,除了显式取消注册之外,摆脱密钥挂钩的唯一方法是如果 Windows 超时。所有 MyClassVar 方法都是内联的,以尽可能快,并且 Alt+Enter 是从单独的线程处理的。

【问题讨论】:

“Windows 自动执行,这不可避免地会导致问题”我在这里感到困惑:Windows 不会自动使 Windows 全屏您的解决方案有问题。那么你通过编写自己的键盘钩子避免了什么? @todda.speot 在 DX11 游戏中按 Alt+Enter 时,Windows 会自动为您切换全屏模式。它将前端缓冲区分辨率设置为桌面分辨率,如果我希望游戏以 640x480 运行,这是不正确的。为避免这种情况,我设置了一个键挂钩来捕获 Alt+Enter 并运行我自己的 ToggleFullscreen() 方法,该方法运行良好。 我的回答有些啰嗦,看看有没有帮助。 【参考方案1】:

据我所知,除了显式取消注册之外,摆脱密钥挂钩的唯一方法是如果 Windows 超时。

那是正确的,您也许可以暂时确认它正在超时by increasing the LowLevelHooksTimeout registry key。

这也是可能的,但极不可能有另一个键盘钩子首先到达那里并在调用你的钩子之前吞下所有输入(这正是你的钩子试图对某些组合键执行的操作)。

从您的评论看来,当 DXGI 为您切换到全屏时,您似乎想选择一个全屏分辨率。

以下内容摘自DirectX Graphics Infrastructure (DXGI): Best Practices。这些信息可能有帮助,也可能没有帮助,我在此节选之前省略的内容也可能有帮助,其中谈到了WM_SIZE

上述解释的方法遵循一条非常特殊的路径。 DXGI 默认将全屏分辨率设置为桌面分辨率。但是,许多应用程序会切换到首选的全屏分辨率。在这种情况下,DXGI 提供IDXGISwapChain::ResizeTarget。这应该在调用SetFullscreenState 之前调用。尽管可以以相反的顺序调用这些方法(首先是SetFullscreenState,然后是ResizeTarget),但这样做会导致向应用程序发送额外的WM_SIZE 消息。 (这样做也会导致闪烁,因为可能会强制 DXGI 执行两次模式更改。)在调用 SetFullscreenState 后,建议再次调用 ResizeTarget 并将 RefreshRate 成员清零。这相当于 DXGI 中的无操作指令,但它可以避免刷新率的问题,这将在后面讨论。

DXGI Overview 有更多关于此事的信息,ResizeTarget 可能没有人们希望的那么有用:

默认情况下,DXGI 选择包含窗口大部分客户区域的输出。这是 DXGI 在全屏响应 alt-enter 时唯一可用的选项。

但是,它确实提到大小由窗口的客户区决定。或许你想limit the client area,而不是安装键盘挂钩?

【讨论】:

事实上,我已经阅读了 DXGI 最佳实践,并且一直在遵循它们。再次,我想强调一下,没有钥匙钩我可以完美地工作。例如,如果我使用键 T 使用我的方法切换全屏,我可以无限期地执行此操作并且效果很好(即使键挂钩也可以继续工作)。问题是当我使用 Alt+Enter 时,键挂钩在第 6 次后停止工作。 我刚刚看到你的答案编辑。感谢您提供更多信息,我实际上并不了解 DXGI 概述中的内容。这可能会提供使用密钥挂钩的解决方法。 @Darkhydro 祝你好运。我认为应该有一个合理的距离来做到这一点,一个低级的键盘钩子尖叫过度。 我将保留这个问题,因为我仍然想知道为什么 key hook 会失败。至少我确定这是关键钩子,而不是我的 ToggleFullscreen() 代码。【参考方案2】:

你试过禁用 DXGI 的钩子吗?

IDXGISwapChain::MakeWindowAssociation( DXGI_MWA_NO_WINDOW_CHANGES )

此外,使用DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 创建交换链将导致 DXGI 找到与您的窗口大小最匹配的显示模式。如果您已经将 ResizeTarget() 设置为 640x480,并且至少有一些 640x480 模式可用,那么您应该得到它。然后,您将获得一个 640x480 的 WM_SIZE,这是您调用 ResizeBuffers 的机会。如果这样调用ResizeBuffers,模式切换后会开启翻页(新的后台缓冲区必须与显示器关联,否则系统不能直接翻页)。如果没有像这样启用页面翻转,那么所有 Present 调用都会调用 blt 操作,这会占用您不需要花费的带宽。

请注意,您可能会或可能不会获得 640x480。例如,如果显示器旋转,您可能会获得下一个最大的模式,其中包含 640x480,可能是 768x1024。如果您想在旋转的显示器上正常工作,您可以注意这一点,并将自己的信箱设置为您想要的 4x3 纵横比。

但通常最好的做法是让 DXGI 选择显示模式,因为桌面模式可能是最佳模式的原因有很多。用户可能已将计算机连接到只能执行该一种模式的投影仪。一些 LCD 将以原始分辨率呈现 640x480,即在显示器中间有一个很小的邮票。理想情况下,您应该对应用进行编码,使其能够在基本上任何分辨率下看起来都很漂亮,但至少在 4x3、3x4、6x9 和 9x6 纵横比下。

快乐编码 -杰夫

【讨论】:

以上是关于为啥我的低级 Windows 键挂钩停止工作?的主要内容,如果未能解决你的问题,请参考以下文章

为啥每次开机都提示windows安全中心已停止服务

我的WINDOWS7出现资源管理器停止工作 代码是BEX 求大神

为啥 Qt 中的拖放停止工作?

为啥我的 postgres 工作停止运行?

为啥我的应用安装后 Google Pay 停止工作?

为啥我的 Facades 在更新到 Laravel 7.3 后停止工作?