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

3
我有一段与这个问题非常相似的代码,在Windows托盘应用程序中运行。即使使用来自该问题的完全相同代码,我也会得到相同的行为。它在经典的Windows应用程序(如Firefox、Chrome、Windows Explorer等)上都能正常工作。然而,当鼠标焦点到达UWP应用程序(如Edge、Calendar或Mail)时,滚动变得抖动,并且在执行几十次滚动后,我的应用程序会挂起,甚至无法从任务管理器中终止(权限被拒绝),这种行为非常容易重现。
我将在此处粘贴来自该问题的代码:
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);

} }

有没有可能这是windows中的一个bug?有什么指针可以帮我找出问题所在?
更新:
我已经用C++创建了一个测试Win32应用程序,以更轻松地重现/演示这个问题。问题在于SendCommand,当它被执行时,只要任何经典应用程序处于焦点状态,就能正常工作。但是,当它被执行时,如果一个UWP应用程序或者甚至是Windows的启动/开始菜单处于焦点状态,调用应用程序(我的应用程序)将会挂起并一直卡住,直到Windows被重新启动。
对于这个问题的一个有效的解决方法是在处理钩子回调的线程中从另一个线程执行SendCommand调用。立即启动执行SendCommand的线程,并从钩子回调返回能够产生期望的行为并且不会引起任何问题。

@hatchet 我知道这个。我的应用程序不是UWP,而是经典桌面应用程序。 - Vasil
这可能与SetWindowsHookEx的32/64位问题有关。您可以通过创建第二个线程并在该线程的上下文中运行SetWindowsHookEx来解决此问题。请参阅32/64和消息泵的讨论:https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx - hatchet - done with SOverflow
你的C++示例应用程序在哪里? - Simon Mourier
1个回答

4
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 bool 字段来打破递归。在调用 SendInput() 之前将其设置为 true,之后将其设置为 false
但我认为我知道你这样做的原因,我也曾经遭受过触摸板反向的困扰。有一种更简单的方法,只需修改FlipFlopWheel setting即可。

谢谢你的回答。实际上它并没有进入递归。我已经检查过了。我的代码与示例不同,目的也不是完全颠倒滚动。但是这个示例会导致相同的问题。无论是我的条件还是你的条件,SendCommand调用在几次执行后会出现问题,而不是在第一次执行时。 - Vasil
8
叹气,你为什么要浪费我的空闲时间去写一些你不会用到并且你拒绝记录原因的代码?这不是免费的,我可以帮助别人。而且,它在“几次执行”后并不会导致问题,耗尽操作系统的堆栈需要更多次执行才行。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接