I am trying to use DWORD WINAPI GetMessagePos(void) function in VB.net.
The function returns a DWORD (32 bit) which can be stored in a VB.net integer variable. A quote from the MSDN doc:
The x-coordinate is in the low-order short of the return value; the y-coordinate is in the high-order short (both represent signed values because they can take negative values on systems with multiple monitors)
How can I retrieve x and y coordinates using vb.net?
I am currently trying
<System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling:=True)>
Private Shared Function GetMessagePos() As Integer
End Function
Sub test()
Dim pos As Integer = GetMessagePos()
Try
Dim x As Short = CShort(pos And &HFFFF)
Dim y As Short = CShort(pos >> 16)
MessageBox.Show(x & ", " & y)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
but I am not sure if it is the right way to do it. I am trying to do some tests like
Try
Dim x As Short = -1
Dim y As Short = 1
Dim i As Int32 = (y << 16) Or x
Dim x2 As Short = CShort(i And &HFFFF)
Dim y2 As Short = CShort(i >> 16)
MessageBox.Show(x & ", " & y)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
Basically I code x and y coordinates in Short (Int16) variables, put them together in a Int32 and then try to decode.
But it doesn't seem to work since it leads to an overflow.
Any ideas of how to decode the x-y coordinates from the GetMessagePos()
WINAPI?
You have to be careful when you extract these values in order to ensure that they work properly on a multiple monitor system. The "standard" way of doing it, in terms of what you would normally use with the GetMessagePos
function are the GET_X_LPARAM
and GET_Y_LPARAM
macros, defined in the Windows SDK headers. These are mentioned specifically in the GetMessagePos
documentation, and there should be similar references in all documentation for functions that return packed coordinates. There is also a warning not to use the classic LOWORD
or HIWORD
macros because they treat the values as unsigned quantities, as you alluded to in the question.
So, the task is essentially to translate the GET_X_LPARAM
and GET_Y_LPARAM
macros from C to VB.NET. Translating them to C# is relatively simple because you can take advantage of the unchecked
keyword:
int GetXValue(UInt32 lParam)
{
return unchecked((short)(long)lParam);
}
int GetYValue(UInt32 lParam)
{
return unchecked((short)((long)lParam >> 16));
}
But VB.NET doesn't have an equivalent for C#'s unchecked
keyword, so you have to find some other way to avoid overflow. Personally, I write this kind of interop code in C# and stick it in a library so I can do what I find the most readable.
If you prefer to stick with VB.NET, there are a couple of ways to do it. The simplest conceptually is to manipulate the value as a UInt32
to avoid the overflow. For the x-coordinate in the lower bits, you will need to explicitly mask off the upper bits to avoid an overflow. For the y-coordinate, you'll need to shift and mask. Then you can convert back to a Short
:
Public Function GetXValue(lParam As UInt32) As Short
Return CShort(lParam And &HFFFF)
End Function
Public Function GetYValue(lParam As UInt32) As Short
Return CShort((lParam >> 16) And &HFFFF)
End Function
Another alternative is a bit more clever, perhaps too clever, but probably more efficient. It involves declaring the equivalent of a C-style union, which in VB.NET terms is just a Structure whose fields overlap:
<StructLayout(LayoutKind.Explicit)> _
Public Structure CoordUnion
<FieldOffset(0)> Public LParam As UInt32
<FieldOffset(0)> Public XCoord As Short
<FieldOffset(2)> Public YCoord As Short
Public Sub New(lParam As UInt32)
LParam = lParam
End Sub
End Structure
Then you can use it like this:
Dim temp As CoordUnion = New CoordUnion(GetMessagePos())
Dim pt As Point = New Point(temp.XCoord, temp.YCoord)
' ...
Note, also, that I've implicitly changed the P/Invoke signature for GetMessagePos
so that it returns a UInt32
:
<System.Runtime.InteropServices.DllImport("user32.dll", ExactSpelling:=True)>
Private Shared Function GetMessagePos() As UInt32
End Function
You could have just as easily used the IntPtr
type throughout all of these helper/conversion functions as the UInt32
type. In fact, that's how you would normally write it, since you usually get these pointer coordinates packed into an lParam
value as part of a window message (e.g., when overriding the WndProc
method of a control).