Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USER32.SendInput with MOUSEEVENTF_WHEEL does not work in .NET 9 #111249

Closed
matthew-a-thomas opened this issue Jan 9, 2025 · 5 comments
Closed

Comments

@matthew-a-thomas
Copy link

matthew-a-thomas commented Jan 9, 2025

Description

SendInput can be P/Invoked with the MOUSEEVENTF_WHEEL flag in order to simulate using the scroll wheel on a mouse.

This works in .NET versions < 9. However, this does not work in .NET 9.

Reproduction Steps

The following code is available here.

Here is a video demonstrating the following.

  1. Execute the following code
  2. Observe that "Scroll" is printed to the console, but long documents do not scroll when the mouse is over them
  3. Change <TargetFramework>net9.0-windows</TargetFramework> in the .csproj to net8.0-windows
  4. Observe that long documents will now scroll when the mouse is over them and when "Scroll" is printed to the console

Program.cs

namespace ScrollTest;

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.UI.Input.KeyboardAndMouse;

class Program
{
    static void Main()
    {
        while (true)
        {
            Console.WriteLine("Scroll");
            Scroll(-1);

            Thread.Sleep(1000);
        }
    }

    static void Scroll(double lines)
    {
        var amount = (uint)(PInvoke.WHEEL_DELTA * lines);
        var mouseInput = new MOUSEINPUT{
            dx = 0,
            dy = 0,
            mouseData = amount,
            dwExtraInfo = 0,
            time = 0,
            dwFlags = MOUSE_EVENT_FLAGS.MOUSEEVENTF_WHEEL
        };

        var input = new INPUT{ type = INPUT_TYPE.INPUT_MOUSE, Anonymous = { mi = mouseInput }};
        Span<INPUT> inputs = stackalloc INPUT[1];
        inputs[0] = input;
        if (PInvoke.SendInput(inputs, Marshal.SizeOf<INPUT>()) == 0)
        {
            throw new Win32Exception();
        }
    }
}

.csproj

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net9.0-windows</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.Windows.CsWin32">
        <Version>0.3.106</Version>
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
    </ItemGroup>

</Project>

The cswin32 project is used here to reduce the amount of code in this reproduction. It should be noted that I first observed this issue in the FlaUI project which does not use cswin32 but instead has hand-crafted all the relevant structs and function signatures. In other words, this isn't caused by the cswin32 project.

Expected behavior

Long documents ought to scroll when the mouse is over them and when "Scroll" is printed to the console, no matter the TargetFramework.

Actual behavior

Long documents only scroll when the mouse is over them and when "Scroll" is printed to the console, and when TargetFramework is net8.0-windows or older.

Regression?

Works in net8.0-windows.

Known Workarounds

Avoid .NET 9.

Configuration

cmd> dotnet --list-sdks
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.401 [C:\Program Files\dotnet\sdk]
6.0.427 [C:\Program Files\dotnet\sdk]
7.0.317 [C:\Program Files\dotnet\sdk]
7.0.410 [C:\Program Files\dotnet\sdk]
8.0.110 [C:\Program Files\dotnet\sdk]
8.0.404 [C:\Program Files\dotnet\sdk]
9.0.101 [C:\Program Files\dotnet\sdk]
cmd> systeminfo
OS Name:                   Microsoft Windows 11 Enterprise
OS Version:                10.0.22631 N/A Build 22631
System Type:               x64-based PC
cmd> ver
Microsoft Windows [Version 10.0.22631.4602]

I also observe this behavior in Hyper-V VMs running relatively fresh installations of Windows 11 Pro (23H2) with the .NET 9.0.101 SDK installed.

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jan 9, 2025
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Jan 9, 2025
@matthew-a-thomas matthew-a-thomas changed the title USER32.SendInput with MOUSE_EVENT_FLAGS.MOUSEEVENTF_WHEEL does not work in .NET 9 USER32.SendInput with MOUSEEVENTF_WHEEL does not work in .NET 9 Jan 9, 2025
@matthew-a-thomas
Copy link
Author

I noted in the bug report in FlaUI that it doesn't appear to be an issue with UIPI blocking. See my comment there for additional details about that.

@matthew-a-thomas
Copy link
Author

matthew-a-thomas commented Jan 9, 2025

I tossed the binaries produced by both TargetFramework versions into ildasm and the IL appears to be identical from the ScrollTest.Program::Scroll method through and including the Windows.Win32.PInvoke::SendInput methods.

@KeterSCP
Copy link

Problem is not with marshalling or WinApi. It is related to overflow calculation (in your case, its happening when calling Scroll(-1), and then casting negative result to uint),

On .NET 8 following code prints 4294967176, while on .NET 9 it prints 0:

Console.WriteLine(Test(-1));

uint Test(double d) => (uint)(120 * d);

This breaking change is known and documented here:

https://learn.microsoft.com/en-us/dotnet/core/compatibility/jit/9.0/fp-to-integer

@jkotas jkotas added area-System.Numerics and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jan 10, 2025
@jkotas jkotas closed this as completed Jan 10, 2025
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Jan 10, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

@tannergooding
Copy link
Member

Worth explicitly noting (even though it's covered in the breaking change doc) that the previous floating-point to integer conversion behavior depended on what the underlying hardware supported.

What this means is that while you discovered the break on .NET 9 where the behavior was explicitly normalized across all hardware, it was actually already broken in .NET 8 (and older targets). In particular you would definitely see the same failure on Arm64 and you might see the same failure on an x64 machine with AVX512 support.

As such, this may be a behavior you want to explicitly handle on all targets, not just .NET 9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants