.netvb.netpinvoke

Do I need 2 different PInvokes to Get and Set mouse speed?


I want to get the current mouse pointer speed, and I want to set it.

To get it, I use

Public Declare Function SystemParametersInfo Lib "user32.dll" Alias "SystemParametersInfoA" (ByVal uAction As Int32, ByVal uParam As Int32, ByRef lpvParam As Int32, ByVal fuWinIni As Int32) As Int32

    Dim Result As Int32 = 0
    Dim iSuccess As Integer = SystemParametersInfo(SPI_GETMOUSESPEED, 0, Result, 0)

To set it, I use

   Public Declare Function SystemParametersInfo Lib "user32.dll" Alias "SystemParametersInfoA" (ByVal uAction As Int32, ByVal uParam As Int32, ByVal lpvParam As Int32, ByVal fuWinIni As Int32) As Int32

    Dim iVal As Integer = 10
    Dim iSuccess As Integer = SystemParametersInfo(SPI_SETMOUSESPEED, 0, iVal, 0)

Please note the different declarations of the same function.

If I change the ByRef to ByVal or vice versa, one of the functions does not work.

Do I really have to declare the same function differently? Or did I make any mistakes? If I did, can somebody please tell me how to do it correctly?


Solution

  • The core problem is that you cannot declare an overload in VB this way because as the error message says, they cannot overload each other because there differ only by parameters declared ByRef and ByVal (you didnt mention the error). This would not happen in C# because you'd use in and out which would change the signature.

    You have several options, one is to use the Alias:

    ' declaration for all ByVal args:
    Declare Function SetSysParam Lib "user32.dll" Alias "SystemParametersInfoA"...
    
    ' one parm ByRef for getting the speed:
    Declare Function GetSysParam Lib "user32.dll" Alias "SystemParametersInfoA"...
    

    In case it is not obvious, you'd invoke them as SetSysParam and GetSysParam.

    Code Analysis/FxCop will likely complain about the way you are using these, so here is how to implement API calls so it doesnt. Start with a class named NativeMethods:

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SystemParametersInfo(uiAction As SPI,
            uiParam As UInteger,
            pvParam As IntPtr,
            fWinIni As SPIF) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    
    ' alias a ByRef version for GETting the speed, but this is not
    ' needed since NET offers a cleaner way
    <DllImport("user32.dll", EntryPoint:="SystemParametersInfo", SetLastError:=True)>
    Private Shared Function SystemParametersInfoPVByRef(uiAction As SPI,
            uiParam As UInteger,
            ByRef pvParam As IntPtr,
            fWinIni As SPIF) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    
     ' other stuff for the last param
    <Flags>
     Enum SPIF
         None = &H0
         SPIF_UPDATEINIFILE = &H1
         SPIF_SENDCHANGE = &H2
         SPIF_SENDWININICHANGE = &H2
     End Enum
    
    ' there are lots and lots of Sys Params, so use an Enum to group them
    Enum SPI
        SETMOUSESPEED = &H71
        GETMOUSESPEED = &H70
    End Enum
    
    ' exposed "wrapper" for setting the value to allow a more
    ' meaningful name and hiding gory details of Msg values etc
    Friend Shared Function SetMouseSpeed(speed As Integer) As Boolean
         ' somewhat optional error checking 
         If speed < 1 Then speed = 1
         If speed > 20 Then speed = 20
    
         Return SystemParametersInfo(SPI.SETMOUSESPEED, 0, New IntPtr(speed), SPIF.None)
    End Function
    
    Friend Shared Function GetMouseSpeed() As Integer
        Dim speed As New IntPtr(0)
    
        ' the caller will have to evaluate the return to see if this
        ' succeeded
        If SystemParametersInfoPVByRef(SPI.GETMOUSESPEED, 0, speed, SPIF.None) Then
            Return speed.ToInt32
        Else
            Return -1           ' magic number that API call failed
        End If
    
    End Function
    

    One advantage to the NET format is in the return. Nearly all API calls return 0 or 1 to indicate success or failure. The MarshalAs... Boolean converts that to an Net Boolean. You can add to the class over time collecting the correct magic numbers, declarations, Enums and structures for the various Win32 API calls. It is a shared/static procedure, so it would be called as:

    NativeMethods.SetMouseSpeed(myNewSpeed)
    

    The "wrappers" not only hides the untidy details of the API from your code, it can also also work out what to do if the API call fails. In the case of getting the speed, it works out what value to return instead of the current speed if/when the call fails, something not likely be misread as a speed value (like maybe -1). As noted in the code, there is nice clean way to get the mouse speed in .NET making the overload unneeded:

    mySpeed = System.Windows.Forms.SystemInformation.MouseSpeed
    

    Finally, I should mention that it is not a good idea to unilaterally change the user's setting for the user's mouse on their system. They picked that value for a reason. If there is some good reason to do so, the original value should be restored when the app exits.

    Technical note: I ran this thru CA to make sure of the size of everything