VB.net Integer and Short conversion, GetMessagePos() - vb.net

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).

Related

BC36913: Cannot infer a common type

Here's my code:
Private Const CFE_LINK As UInt32 = &H20
Public Sub SetSelectionLink(ByVal link As Boolean)
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0))
End Sub
Public Function GetSelectionLink() As Integer
Return GetSelectionStyle(CFM_LINK, CFE_LINK)
End Function
Private Sub SetSelectionStyle(ByVal mask As UInt32, ByVal effect As UInt32)
Dim cf As CHARFORMAT2_STRUCT = New CHARFORMAT2_STRUCT()
cf.cbSize = CUInt(Marshal.SizeOf(cf))
cf.dwMask = mask
cf.dwEffects = effect
Dim wpar As IntPtr = New IntPtr(SCF_SELECTION)
Dim lpar As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf))
Marshal.StructureToPtr(cf, lpar, False)
Dim res As IntPtr = SendMessage(Handle, EM_SETCHARFORMAT, wpar, lpar)
Marshal.FreeCoTaskMem(lpar)
End Sub
I got an error (Cannot infer a common type for the first and second operands of the binary 'If' operator) on that line:
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0))
The type for the constant 0 is Integer. I think the error is because the compiler can't tell whether it should use Integer or UInt32 as the result type; they're both integer types with the same bit width, the only difference is the upper and lower bounds.
As you've noted, you can use an explicit conversion to make both operands to If have the same type.
You can also use the appropriate type suffix to make the constant 0 have the right type. In this case, the following should work:
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0UI))
The UI suffix tells the compiler to treat the 0 as a UInteger (which is the same type as UInt32) instead of an Integer.
Ok...Following the MSDN Documentation, I tried this and the compilator seems ok.
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, CUint(0)))

CH341DLL.DLL + I2C not works properly with VB.NET

I write VB.NET class for implement CH341DLL.DLL functionality. The method CH341StreamI2C() is used for stream write and read into device. This way I've imported the method CH341StreamI2C() from DLL:
<DllImport("CH341DLL.DLL", SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
Private Shared Function CH341StreamI2C(ByVal iIndex As Integer, ByVal iWriteLength As Integer, ByRef iWriteBuffer As IntPtr, ByVal iReadLength As Integer, ByRef oReadBuffer As IntPtr) As Boolean
End Function
For check how this method works, I use I2C humidity and temperature sensor HTU21D. It's IIC address is 40h, and register where temperature is getting is E3h. So I invoke method CH341StreamI2C() like this:
Dim writeBuffer as Byte() = {&H40, &hE3} 'Address+Command
Dim s As String = Encoding.Unicode.GetString(writeBuffer)
Dim writeBufPtr As IntPtr = Marshal.StringToHGlobalAuto(s) 'Get pointer for write buffer
Dim wLen As Integer = writeBuffer.Length
Dim readBufPtr As IntPtr = IntPtr.Zero 'Init read pointer
Dim rLen as Integer = 3 'Sensor must return 3 bytes
Dim res As Boolean = CH341StreamI2C(0, wLen, writeBufPtr, rLen, readBufPtr)
I use logic analyzer to see what is on the SDA and SCL lines. And result is unpredictable. For example, if call previous code 4 times, that's the result:
It's seen, that physically CH341 device writes unpredictable values in the line. This is not DLL error, because other applications use this method and the result is correct. For note, other methods, e.g. CH341ReadI2C() and CH341WriteI2C(), that reads/writes only one byte per time, acts correct in my code.
What is the probably reason of the such behavior? May be, I've marshalled write buffer incorrect? How is the right way to do this?
If this is what you're using, the original declaration is:
BOOL WINAPI CH341StreamI2C(ULONG iIndex, ULONG iWriteLength, PVOID iWriteBuffer, ULONG iReadLength, PVOID oReadBuffer);
Since the buffer parameters are PVOIDs, you should be able to just marshal them directly to byte arrays:
<DllImport("CH341DLL.DLL", SetLastError:=True, CallingConvention:=CallingConvention.StdCall)>
Private Shared Function CH341StreamI2C(ByVal iIndex As Integer, ByVal iWriteLength As Integer, ByVal iWriteBuffer As Byte(), ByVal iReadLength As Integer, ByVal oReadBuffer As Byte()) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Arrays are reference types (classes) which means you always refer to them via their memory pointer. Thus when you pass them to a function (P/Invoked or not) you're actually passing the array's pointer, rather than the array itself. This is really helpful when P/Invoking since it often lets you pass the arrays as-is.

Comparing strings not working properly vb.net

I'm trying to compare 2 strings (KeyCode and SerialN)
I've tried a bunch of diferente aproaches... but as you can see in my print screen neither = or Equals work.
Can you tell me what is wrong?
Private Function VerificaKeyCode(SerialN As String) As Boolean
Dim snEsq As Integer
Dim snDir As Integer
Dim KeyCode As String
Dim buffer As String
Dim ind As Short
VerificaKeyCode = False
buffer = Space(255)
For ind = 1 To 5
'UPGRADE_WARNING: App property App.EXEName has a new behavior. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="6BA9B8D2-2A32-4B6E-8D36-44949974A5B4"'
GetPrivateProfileString(My.Application.Info.AssemblyName, "HD" & ind, "", buffer, Len(buffer), My.Application.Info.DirectoryPath & "\ServerHD.ini")
KeyCode = Trim(buffer)
If KeyCode <> "" Then
VerificaKeyCode = KeyCode.Equals(SerialN)
VerificaKeyCode = CBool(KeyCode = SerialN)
If VerificaKeyCode Then Exit Function
End If
Next ind
End Function
Edit
Aparently there is an empty char in the string, I need to remove it somehow
The expressions in your watch window are stale as indicated by the disabled color and the presence of the Refresh/Recycle icon. Click the icon and the expression will be re-evaluated and the result updated:
Mimicking what you have, notice the first expression is stale and wrong. The second has been refresh/reevaluated and reports the correct result. As soon as you execute a line of code, the watch expressions will be marked as stale/not updated.
A better way to return a function with NET is to use a local variable and Return it. The local var allow you to easily view the result by hovering the mouse:
A variable can go out of scope, but it can't be stale.
Edit 4 or 5 reveals that while the watch expressions are stale, so what we see in the pic neither proves nor disproves that the strings do not match, this is apparently a VB6 -> .NET refurb (notice the 102 warnings).
We cant see the declaration for GetPrivateProfileString but the usage is at least suboptimal.
<DllImport("kernel32.dll", SetLastError:=True)>
Shared Function GetPrivateProfileString(appname As String,
keyname As String, def As String,
sbuffer As StringBuilder,
nSize As Int32,
strFile As String) As Integer
End Function
Note that it is a Function; it returns the number of chars read into the buffer. Rather than Trim which will not remove NUL, the return should be used (if not a stringbuilder) to get the number of characters specified. If you use a string buffer, trim to the number of characters read:
Dim n As Int32 = GetPrivateProfileString(...)
KeyCode = buffer.Substring(0, n)
StringBuilder should do that for you:
Dim buffer As New StringBuilder(255)
...
Dim n As Int32 = GetPrivateProfileString(..., buffer, buffer.Capacity)
KeyCode = buffer.ToString()
You can still check the function return against the size of the string.
You could skip GetPrivateProfileString entirely and read the file yourself with a stream reader. It should be faster than PInvoke, but at the least, less convoluted to use.

VB Nullables and Nothings

I researched C#'s default keyword equivalence in VB.NET and came across this question.
Then I got curious. Some background - I'm working with parsing an excel spreadsheet, where many columns can be null, and there is certainly a difference for me between an integer column being 0 and being null.
I wrote a little parse method:
Function Test(ByVal i As String) As Nullable(Of Integer)
Dim j As Integer
If Integer.TryParse(i, j) Then
Return j
Else
Return Nothing
End If
End Function
this seems to work correctly. But here, I can return an Integer if i want:
Function Test(ByVal i As String) As Nullable(Of Integer)
Return 2 'or Return Nothing
End Function
which I can in C# as well:
static void Main(string[] args)
{
int? j = Test("1");
}
public static int? Test(string i)
{
return 2; //or return null
}
In the code above, if I change j to an int, I'll get a compile-time conversion error, which makes total sense.
Now my question - in VB, if i attempt a similar approach:
Sub Main()
Dim j As Integer = Test("hello")
End Sub
Function Test(ByVal i As String) As Nullable(Of Integer)
Dim j As Integer
Return If(Integer.TryParse(i, j), j, Nothing)
End Function
or, in my test case where i is not an Integer it can be rewritten as:
Function Test(ByVal i As String) As Nullable(Of Integer)
Return DirectCast(Nothing, Integer)
End Function
because of the way Nothing works in VB.NET, this code compiles and runs without error -- j is set to Integer's default 0.
This feels so dirty to me. In this scenario you can somewhat alter the method's intentions without any warnings or errors. I'm just curious I suppose, is this an unintentional by-product of the way Nothing works in VB, or is this the intended purpose?
Your VB.Net code compiles because you're using late binding, which allows changing the type of a variable at runtime.
If you compile your code with OPTION STRICT ON, you'll get a compiler error like:
Option Strict On disallows implicit conversions from 'Integer?' to 'Integer'.
You can't assign NULL to a value type in VB.Net in which it instantiates that type with its default value. In your case you are not creating a NULL Integer, but an integer that holds the default value of 0.
Another good note: turn Option Strict On

How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?

I have written my own function, which in C would be declared like this, using standard Win32 calling conventions:
int Thing( char * command, char * buffer, int * BufSize);
I have the following amount of Visual Basic code figured out, which should import the DLL file and call this function, wrapping it up to make it easy to call Thing("CommandHere",GetDataBackHere).
UPDATE: This code is now a working solution, as shown here:
Imports Microsoft.VisualBasic
Imports System.Runtime.InteropServices
Imports System
Imports System.Text
Namespace dllInvocationSpace
Public Class dllInvoker
' I tried attributes, but I could not make it build:
' <DllImport("thing1.dll", False, CallingConvention.Cdecl, CharSet.Ansi, "Thing", True, True, False, True)>
Declare Ansi Function Thing Lib "thing1.dll" (ByVal Command As String, ByRef Buffer As StringBuilder, ByRef BufferLength As Integer) As Integer
' This part contributed by helpful user:
Shared Function dllCall(ByVal Command As String, ByRef Results As String) As Integer
Dim Buffer As StringBuilder = New StringBuilder(65536)
Dim Length As Integer = Buffer.Capacity
Dim retCode As Integer = Thing(Command, Buffer, Length)
Results = Buffer.ToString()
'Debug.Assert(Results.Length = Length) ' This assertion is not true for me
Return retCode
End Function
End Class
End Namespace
I got the code to build by following the help received here, and then I had forgot the As Return Type (which got me a MarshalDirectiveException PInvokeRestriction). Then I had an assertion failure inside my DLL, which lead to an SEHException. Once fixed, this works BEAUTIFULLY. Thank you folks. There are newsgroups where people are saying this can not be done, that Visual Basic only loads managed DLL assemblies (which I guess is the normal thing most Visual Basic users are used to).
It depends on how you use the buffer argument in your C code. If you only pass a string from your VB.NET code to your C code then declaring it ByVal String is good enough. If however you let the C code return a string in the buffer then you have to declare it ByVal StringBuilder and initialize it properly before the call. For example:
Public Class dllInvoker
Declare Ansi Function Thing Lib "Thing1.dll" (ByVal Command As String, ByVal Buffer As StringBuilder, ByRef BufferLength As Integer) As Integer
Shared Function dllCall(ByVal Command As String, ByRef Results As String) As Integer
Dim Buffer As StringBuilder = New StringBuilder(65536)
Dim Length As Integer = Buffer.Capacity
Dim retCode As Integer = Thing(Command, Buffer, Length)
Results = Buffer.ToString()
Debug.Assert(Results.Length = Length)
Return retCode
End Function
End Class
Note the ambiguity in the returned Length value.
You cannot convert a StringBuilder instance to a string instance, instead, use the 'ToString' method to convert it back to the string type...here's the portion of the code in the dllCall function...
retCode = Thing(Command, Buffer, bufsz)
Results = Buffer.ToString();