How to store a two's complement number in a long - vb.net

I am converting a VB6 application to VB.net. The application uses an existing C library I can't change.
The Problem: I am expecting a value around -180 or 180. The times that I expect 180, it works. But when I expect -180, I get the value 4294967117. This seems like the C library is returning a 2's complement number, but I don't know how to treat it.
This is the VB6 code that works:
Dim tmp As Long
If GetVal(VAL_A1, tmp) = ERR_VAL_NA Then
lblAngle(0).Caption = "na"
Else
lblAngle(0).Caption = tmp
End If
This is the VB.net code that does not work:
If GetVal(VAL_A1, tmp) = ERR_VAL_NA Then
txtBoxPhaseAngle1.Text = "na"
Else
txtBoxPhaseAngle1.Text = Convert.ToDouble(tmp)
End If
I have also tried:
txtBoxPhaseAngle1.Text = Convert.ToInt32(tmp)
txtBoxPhaseAngle1.Text = tmp
EDIT :
How I declare the C function:
Declare Function GetVal Lib "Z:\Devel\RelayAPI\Debug\RelayAPI.dll" (ByVal what As
Integer, ByRef val As Long) As Byte
Snippets from the GetVal function in the C code:
BYTE __stdcall GetVal(WORD what, long *val){
DWORD relaydate;
BYTE tmpb;
DWORD tmpd;
long tmp;
...
switch(what){
case VAL_A1:
tmpb=RelayAPI_Scaled;
if(tmpd<6){
*val=(short)((WORD)mon[38]+((WORD)mon[39]<<8));
}else{
*val=(short)((WORD)mon[32]+((WORD)mon[33]<<8));
}
break;
break;
}

When upgrading from VB6, you need to be aware that some data types have changed. In VB6, an Integer is 16-bits, and a Long is 32-bits. In VB.NET, though, an Integer is 32-bits and a Long is 64-bits.
In the C function, you have long *val, and a C long is "at least 32-bits" according to spec, so presumably your C-function is returning a 32-bit integer. However, VB.NET now believes that it's a Long (64-bit) value, and interprets it as such -- 4294967117 instead of -180.
If you change your VB.NET declaration of the val parameter to ByRef val As Integer, you will probably see the correct return value.

Related

Assign a string to a byte array in a VBA user-defined type

I work with a relay module that I normaly connect via USB. That all works perfectly. Now I would like to connect it via the network. All manufacturer's VB.NET code works in vba except for accessing this module over the network.
Public Declare PtrSafe Function DapiOpenModuleEx Lib "DELIB64" (ByVal moduleID As Long, ByVal nr As Long, ByRef exbuffer As DAPI_OPENMODULEEX_STRUCT, ByVal open_options As Long) As Long
' Definitions for DapiOpenEx
Public Type DAPI_OPENMODULEEX_STRUCT
address(255) As Byte
timeout As Long
portno As Long
encryption_type As Long
encryption_password(31) As Byte
End Type
'Open ETH-Module with parameter
Dim handle as Ulong
Dim MyModuleID As UInt32
MyModuleID = 42
Dim open_buffer As New DELib64.DAPI_OPENMODULEEX_STRUCT
open_buffer.address = System.String.Copy(192.168.1.1 As String) As String
open_buffer.portno = 0
handle = DELib.DapiOpenModuleEx(MyModuleID, 0, open_buffer)
I am getting an error "open_buffer.address = System.String.Copy(192.168.1.1 As String) As String "
Can someone help me with what i need to change here?
Dim handle as LongLong
Dim MyModuleID As Long
MyModuleID = 42
Dim open_buffer As Delib64.DAPI_OPENMODULEEX_STRUCT
open_buffer.address = system.String.Copy("192.168.1.1" AS String) As String
open_buffer.portno = 0
handle1 = DapiOpenModuleEx(MyModuleID, 0, open_buffer, 0)
According to your comment, the original line of code is
strcpy((char*) open_buffer.address, "192.168.1.10");
So you need to copy the ASCII (single-byte) string "192.168.1.10" into a VBA byte array. This is surprisingly hard, since the obvious approach of open_buffer.address = StrConv("192.168.1.10", vbFromUnicode) won't work (you can't assign to a fixed-size array that's part of a type).
One obvious solution would be to make a Windows API call to CopyMemory, but if we want a VBA-only solution, a simple loop should suffice:
Dim i As Long
Dim b() As Byte
b = StrConv("192.168.1.10", vbFromUnicode)
For i = 0 To UBound(b)
open_buffer.address(i) = b(i)
Next
open_buffer.address(UBound(b) + 1) = 0 ' C-strings need to be 0-terminated
(I do have the feeling that this should be easier, so I'll gladly upvote competing, simpler answers.)

VBA - Is there any alternative to the array() function to create a empty array in vba?

After August 2019 Windows update there is a problem using the array() function in VBA.
Is there any other way to create an empty array in VBA for the purpose "Using multi value combobox on a form"?
The following statement to clear/delete all the selections:
me.cmbMultivalue=Array()
The array returned by Array() is not just an uninitialized array. It's an initialized array with a lower bound of 0 and an upper bound of -1, thus containing 0 elements. This is distinct from normal, uninitialized arrays, which don't have a lower and upper bound.
You can roll your own array function (which I often do for non-variant arrays).
For a variant array, it's really easy. Just take an input ParamArray, and assign that to a variant array:
Public Function altArray(ParamArray args() As Variant) As Variant()
altArray = args
End Function
Then, you can use altArray() to get your special 0-element array.
However, I'm not sure this is also bugged for that specific version of Access. If it is, we can always create a 0-element array using WinAPI (slightly adapted version of this answer):
Public Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
Public Type tagVariant
vt As Integer
wReserved1 As Integer
wReserved2 As Integer
wReserved3 As Integer
pSomething As LongPtr
End Type
Public Declare PtrSafe Function SafeArrayCreate Lib "OleAut32.dll" (ByVal vt As Integer, ByVal cDims As Long, ByRef rgsabound As SAFEARRAYBOUND) As LongPtr
Public Declare PtrSafe Sub VariantCopy Lib "OleAut32.dll" (pvargDest As Any, pvargSrc As Any)
Public Declare PtrSafe Sub SafeArrayDestroy Lib "OleAut32.dll" (ByVal psa As LongPtr)
Public Function CreateZeroLengthArray() As Variant
Dim bounds As SAFEARRAYBOUND 'Defaults to lower bound 0, 0 items
Dim NewArrayPointer As LongPtr 'Pointer to hold unmanaged variant array
NewArrayPointer = SafeArrayCreate(vbVariant, 1, bounds)
Dim tagVar As tagVariant 'Unmanaged variant we can manually manipulate
tagVar.vt = vbArray + vbVariant 'Holds a variant array
tagVar.pSomething = NewArrayPointer 'Make variant point to the new variant array
VariantCopy CreateZeroLengthArray, ByVal tagVar 'Copy unmanaged variant to managed return variable
SafeArrayDestroy NewArrayPointer 'Destroy the unmanaged SafeArray, leaving the managed one
End Function
When you declare a new array, it is still an empty array.
i.e. Dim x() As Variant
(1) As you mention in your question, your goal is to clear combobox values by assigning an empty array to it, it seems like this would work:
Dim EmptyArray() As Variant
Me.cmbMultivalue = EmptyArray
(2) Or if that doesn't work, assuming that Me.cmbMultivalue behaves like a regular array, the following would work:
Erase Me.cmbMultivalue
EDIT:
(3) Another possible workaround similar to (1) would be to create a non-empty array and then erase it as such:
Dim x() As Variant
x = Array(1)
Erase x
You could then use x as an empty array.
If all that fails and, as you mentioned, assigning the value Null or vbEmpty didn't work, it seems like your only options would be to revert the problematic Windows update or hope Microsoft can fix this quickly.
Alternative to clear all values from a multi-value field is with SQL. Example:
CurrentDb.Execute "DELETE Table1.Test.Value FROM Table1 WHERE ID = 1"

VB.net Integer and Short conversion, GetMessagePos()

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

Passing variants/arrays as arguments for a function

Example code;
Sub functiontester()
Dim testdata As Variant
Dim answer As Long
Dim result1 As Long
testdata = Sheets("worksheet1").Range("E1:E2").Value
result1 = testfunction(testdata)
result2 = testfunction2(testdata(2, 1))
End Sub
Function testfunction(stuff As Variant) As Long
testfunction = stuff(2, 1)
End Function
Function testfunction2(num As Long) As Long
testfunction2 = num
End Function
So from my days in python I'd expect result1 & result2 to both run fine, however this is not the case in VBA and if you try to run this you get
"compile error: Byref argument type mismatch" from result2; which I assume has something to do with limits of calculating values inside arguments of functions
So my question is: is there an easy way to make result2 work so that the variant reference just resolves to the specified element?
testdata(2, 1) likely will be of type Double, not Long.
You can use
CLng(testdata(2, 1))
to cast it to a Long.
So:
result2 = testfunction2(CLng(testdata(2, 1)))
should be fine
"compile error: Byref argument type mismatch" Actually refers to the fact it won't implicitly convert the datatype for you, because ByRef arguments (the default) are expected to be writeable. If converted arguments are written to , it gets lost when you return from the function/subroutine because the converted values are only temporary, they're not in any variable outside of the called function.
You can get around this complaint by making the receiving parameter ByValwhich means that it shouldn't be writable anyway:
Function testfunction2(ByVal num As Long) As Long

Visual Basic.Net Converting a Long to a Ulong

I am having issues using VB.Net trying to convert a Long to a ULong.
I have tried many combinations and keep getting overflow errors.
I have a signed value of -2147483648, I know it will have a Ulong of 2151196588 once converted.
However I want to do this in a programmatic fashion because I am parsing values that can be negative and positive, but when the numbers are negative, they need to have the proper ulong value.
Note: Absolute values wont work, it needs to be a ulong for a true value.
Thank you.
If you mean reinterpret_cast<ulong>(long), then use the same technique:
<Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Explicit)> _
Public Structure LongULongUnion
<Runtime.InteropServices.FieldOffset(0)> Public l As Long
<Runtime.InteropServices.FieldOffset(0)> Public ul As ULong
End Structure
Sub Main()
Dim u As LongULongUnion
u.l = -2147483648L
Console.WriteLine(u.ul)
Console.ReadLine()
End Sub
But that gives 18446744071562067968 when converted.
You can use the BitConverter class:
Dim a As Int64 = -2147483648
Dim b = BitConverter.ToUInt64(BitConverter.GetBytes(a), 0)
Console.WriteLine(b.ToString) ' outputs: 18446744071562067968
Are you sure your intended value of 2151196588 is correct?
This is the easiest way to convert any Long to ULong:
Dim x As Long = -2147483648
Dim y As ULong = Not (CType((Not x), ULong)) ' = 18446744071562067968
I'm a C# programmer, so please correct me if I converted it to VB.NET incorrectly. My C# code was:
long x = -2147483648;
ulong y = ~((ulong)~x); // = 18446744071562067968
Basically you take the complement of the negative value, which is always a positive value. You can then safely cast it to ULong. Take the complement again and you have the same bit pattern cast to ULong.
Make sure you are converting your value to positive BEFORE you convert it to ulong, negatives are not in a ulong's value scope.
long: -9223372036854775808 to 9223372036854775807
ulong: 0 to 18446744073709551615