.netvb.netwinformswindows-cecompact-framework2.0

WM_MOUSEMOVE LParam low-order Overflow


When i receive a WM_MOUSEMOVE message in WndProc the lParam contains the cursor coordinates. I translate this parameter into a Point structure:

Friend Shared Function LParamToPoint(ByVal lParam As IntPtr) As Point
    Return New Point(LOWORD(lParam.ToInt32()), HIWORD(lParam.ToInt32()))
End Function

Friend Shared Function HIWORD(ByVal value As Integer) As Short
    Return CShort((value >> 16))
End Function

Friend Shared Function LOWORD(ByVal value As Integer) As Short
    Return CShort((value And UShort.MaxValue))
End Function

My problem is that when the x-coordinate of the cursor becomes negative the LOWORD function fails with an overflow exception. According to MSDN you should use the GET_X_LPARAM and GET_Y_LPARAM macros not the HI/LO WORD macros. But no such functions are available in vb.net.

So what to do?


Solution

  • Overflow checking really gets in the way here. The best way to tackle this problem is by declaring a union. A union is a structure that has overlapping values. Which elegantly allows you to overlay an IntPtr on top of structure members that have type Short. The conversion is extremely fast and cannot throw an overflow exception.

    Add a new Module to your project, name it NativeMethods and make it look like this:

    Imports System.Runtime.InteropServices
    Imports System.Drawing
    
    Module NativeMethods
        <StructLayout(LayoutKind.Explicit)> _
        Public Structure LParamMap
            Public Sub New(value As IntPtr)
                lparam = value
            End Sub
    
            Public Shared Widening Operator CType(value As LParamMap) As Point
                Return New Point(value.loword, value.hiword)
            End Operator
    
            <FieldOffset(0)> Public lparam As IntPtr
            <FieldOffset(0)> Public loword As Short
            <FieldOffset(2)> Public hiword As Short
        End Structure
    
    End Module
    

    I threw in a conversion operator for Point since that's the one you really want. Some test code that exercises this:

    Imports System.Drawing
    Imports System.Diagnostics
    
    Module Module1
        Sub Main()
            Debug.Assert(BitConverter.IsLittleEndian)
            Dim test As New LParamMap(New IntPtr(-1))
            Debug.Assert(test.loword = -1)
            Debug.Assert(test.hiword = -1)
            Dim pos As Point = test
            Debug.Assert(pos = New Point(-1, -1))
        End Sub
     End Module
    

    It now becomes a very simple one-liner in, say, an override of a form's WndProc() method:

    Protected Overrides Sub WndProc(ByRef m As Windows.Forms.Message)
        If m.Msg = &H200 Then
            Dim pos As Point = New LParamMap(m.LParam)
            '' etc...
        End If
        MyBase.WndProc(m)
    End Sub