在Windows10 UWP应用程序上使用SendInput发送滚动命令

问题描述:

我的代码与运行在Windows托盘应用程序中的this question非常相似,即使从问题中得到的确切代码我也会得到相同的行为。它在经典的Windows应用程序上运行良好,例如Firefox,Chrome,Windows资源管理器等。但是,当鼠标焦点到达UWP应用程序(例如Edge或Calendar或Mail)时,滚动会变得紧张,在执行几十个滚动后,我的应用程序挂起甚至不能从任务管理器(权限被拒绝)终止,这种行为是非常可重复的。在Windows10 UWP应用程序上使用SendInput发送滚动命令

我会从这里的问题粘贴代码:

using System; 

using System.Diagnostics; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

namespace EnableMacScrolling 
{ 
class InterceptMouse 
{ 
    const int INPUT_MOUSE = 0; 
    const int MOUSEEVENTF_WHEEL = 0x0800; 
    const int WH_MOUSE_LL = 14; 


    private static LowLevelMouseProc _proc = HookCallback; 
    private static IntPtr _hookID = IntPtr.Zero; 

    public static void Main() 
    { 
     _hookID = SetHook(_proc); 

     if (_hookID == null) 
     { 
      MessageBox.Show("SetWindowsHookEx Failed"); 
      return; 
     } 
     Application.Run(); 
     UnhookWindowsHookEx(_hookID); 
    } 

    private static IntPtr SetHook(LowLevelMouseProc proc) 
    { 
     using (Process curProcess = Process.GetCurrentProcess()) 
     using (ProcessModule curModule = curProcess.MainModule) 
     { 
      return SetWindowsHookEx(WH_MOUSE_LL, proc, 
       GetModuleHandle(curModule.ModuleName), 0); 
     } 
    } 

    private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); 

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 
    { 
     if (nCode >= 0 && MouseMessages.WM_MOUSEWHEEL == (MouseMessages)wParam) 
     { 
      MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); 

      Console.WriteLine(hookStruct.mouseData); 
      if (hookStruct.flags != -1) //prevents recursive call to self 
      { 
       INPUT input; 
       input = new INPUT(); 
       input.type = INPUT_MOUSE; 
       input.mi.dx = 0; 
       input.mi.dy = 0; 
       input.mi.dwFlags = MOUSEEVENTF_WHEEL; 
       input.mi.time = 0; 
       input.mi.dwExtraInfo = 0; 
       input.mi.mouseData = -(hookStruct.mouseData >> 16); 
       try 
       { 
        SendInput(1, ref input, Marshal.SizeOf(input)); 
       } 
       catch (Exception e) 
       { 
        System.Diagnostics.Debug.WriteLine(e.Message); 
       } 

       return (IntPtr)1; 
      } 
     } 
     return CallNextHookEx(_hookID, nCode, wParam, lParam); 
    } 


    private enum MouseMessages 
    { 
     WM_MOUSEWHEEL = 0x020A 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct POINT 
    { 
     public int x; 
     public int y; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    private struct MSLLHOOKSTRUCT 
    { 
     public POINT pt; 
     public int mouseData; 
     public int flags; 
     public int time; 
     public IntPtr dwExtraInfo; 
    } 

    public struct INPUT 
    { 
     public int type; 
     public MOUSEINPUT mi; 
    } 

    [StructLayout(LayoutKind.Sequential)] 
    public struct MOUSEINPUT 
    { 
     public int dx; 
     public int dy; 
     public int mouseData; 
     public uint dwFlags; 
     public int time; 
     public int dwExtraInfo; 
    } 



    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr SetWindowsHookEx(int idHook, 
     LowLevelMouseProc 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); 

    [DllImport("User32.dll", SetLastError = true)] 
    public static extern int SendInput(int nInputs, ref INPUT pInputs, int cbSize); 

} } 

难道我处理在此窗口中的错误?任何指向我如何可以找出发生了什么?

更新:

我已用C创建了一个测试Win32应用程序++更容易地再现/演示此问题。这个问题与SendCommand一样,当任何经典应用程序在焦点中执行时,都可以正常工作。但是,当UWP应用程序处于焦点状态时执行时,或者甚至Windows启动/开始菜单导致调用应用程序(我的应用程序)挂起并一直卡住,直到重新启动窗口。

此问题的有效解决方法是在处理钩子回调的线程的另一个线程上执行SendCommand调用。立即启动执行SendCommand并从钩子回调中返回的线程会产生所需的行为并且不会导致任何问题。

+0

@hatchet我意识到这一点。我的应用程序不是UWP,它是一款经典的桌面应用程序。 – Vasil

+0

这可能是与SetWindowsHookEx的32/64位问题有关的问题。您可以通过创建第二个线程并在该线程的上下文中运行SetWindowsHookEx来避开它。请参阅32/64和消息泵的讨论:https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85)。aspx – hatchet

+0

你的C++示例应用程序在哪里? –

if (hookStruct.flags != -1) //prevents recursive call to self 

这很重要,是的。但是,声明如何能够做到这一点是非常不清楚的。该字段的期望值是0,1或3.从不-1。您可能被另一个在您的计算机上处​​于活动状态的鼠标钩误导了。

现在WinRT应用程序是否在前台并不重要。因为此时涉及消息代理并且字段值发生更改,所以LLMHF_LOWER_IL_INJECTED bit已打开。 WinRT应用程序运行在较低完整性级别的沙箱中。所以这个字段不会是-1,你的SendInput()调用再次触发鼠标钩子。在它上面,当它用完时显示结束。

所以第一种可能的解决办法是使用字段如预期,改变声明:

if ((hookStruct.flags & 1) == 0) 

不会工作,如果该推测靠不住的鼠标钩子被破坏的现场,然后再考虑使用static布尔场在你的班级中打破递归。在SendInput()调用之前将其设置为true,之后为false


但我想我知道你为什么这样做,我也一直是一个让它倒退的触控板受害者。有一个更简单的方法来做到这一点,只需修改FlipFlopWheel setting

+0

感谢您的回答。其实它没有进入递归。我已经检查过这个。我的代码与示例不同,用于与完全颠倒滚动不同的目的。但是这个例子导致了同样的问题。无论是我的条件还是你的条件,SendCommand调用都会在执行几次后导致问题,而不是在第一次执行时。 – Vasil

+6

叹了口气,你为什么要浪费我的空闲时间,你实际上没有使用的代码和拒绝文档的原因?这不是免费的,我可以帮助其他人。不,在“少量执行”之后不会造成问题,吹捧操作系统的堆栈比这更多。 –