I have a function that returns unsigned 64 bit integers, which VBA does not support. I have seen workarounds using Currency but I want to do it using Type instead. This is what I came up with:
Public Type LARGE_INTEGER
LoPart As Long
HiPart As Long
End Type
Private Const SCALE_UP As Double = 2^32 - 1
Public Function IntegerDivide(ByRef numerator As LARGE_INTEGER, ByRef denominator As LARGE_INTEGER) As Double
IntegerDivide = Normalify(numerator) / Normalify(denominator)
End Function
Public Function Largify(ByVal normal As Double) As LARGE_INTEGER
Largify.HiPart = normal / SCALE_UP
Largify.LoPart = normal - Largify.HiPart * SCALE_UP
End Function
Public Function Normalify(ByRef large As LARGE_INTEGER) As Double
Normalify = large.HiPart * SCALE_UP + large.LoPart
End Function
I have a feeling it could be buggy because of off-by-one errors or two's complement. But I don't know this stuff well enough. Is this correct?
e.g. here's an example function that returns a 64 bit unsigned integer
Private Declare Function QueryPerformanceCounter Lib "KERNEL32" (ByRef outTickCount As LARGE_INTEGER) As Long 'BOOL
You have the Decimal type readily available that can perform operations on integers up to 12 bytes.
You can't declare variables as "Decimal" but you can declare them as "Variant" and use the "Cdec" function to convert them to decimal and then all integer operations will work correctly on these BIG integers.
Related
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)))
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).
Is there any way in which I can generate a unique number in code ?
I had an idea of using system time for that, but eventually could not implement it.
You can use the Now() then format the output to a number:
Sub unique()
Dim t As Date
t = Now()
Range("A1").NumberFormat = "#"
Range("A1") = CStr(Format(t, "yyyymmddhhMMss"))
End Sub
This would be unique.
As #Vasily pointed out, without formatting the cell as string and placing the number as a sting the value gets truncated to scientific notation.
especially for such cases the GUID (Global Unique IDentifyer) was invented. It may be a little bit oversized ... but just that you have seen it:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
whenever you call newGUID() you will become a value that should be really unique in world. You can try and call it as often as you want ... you will never get the same value a second time.
it's also possible to convert such GUID's to string:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Declare Function StringFromGUID2 Lib "OLE32.dll" _
(guid As TYP_GUID, _
ByVal lpszString As String, _
ByVal iMax As Long) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
Public Function newGUID_String() As String
Dim sBuffer As String
Dim lResult As Long
sBuffer = VBA.Space(78)
lResult = StringFromGUID2(newGUID, sBuffer, Len(sBuffer))
newGUID_String = Left$(StrConv(sBuffer, vbFromUnicode), lResult - 1)
End Function
I have been using Excel 2013 here. For example,
Public Function RoundTest(ByVal flNumber As Double) As Double
RoundTest = Round(flNumber)
End Function
Public Function Test( _
ByVal flNumber As Double, _
ByVal flDivisor As Double) _
As Double
Test = flNumber - (RoundTest(flNumber / flDivisor) * flDivisor)
End Function
Let flDivisor is passed as 10 by caller. Calling Test() with flNumber <= 10^22 yields a correct result, that is 0, but with flNumber > 10^22, Test() returns a wrong result, that is a negative number. However, if an intermediary variable is used to temporarily hold the partial result of the calculation, Test() returns a correct result, that is 0.
Public Function Test( _
ByVal flNumber As Double, _
ByVal flDivisor As Double) _
As Double
Dim flTemp As Double
flTemp = RoundTest(flNumber / flDivisor) * flDivisor
Test = flNumber - flTemp
End Function
Why does this happens? How can I avoid this peculiarity?
Here is more straightforward example:
Public Function RoundTest(ByVal flNumber As Double) As Double
RoundTest = Round(flNumber)
End Function
Sub TestCDbl()
Dim a As Double
Dim b As Double
a = 10 ^ 23
b = 10
w1 = a - RoundTest(a / b) * b ' -8388608
w2 = a - Round(a / b) * b ' 0
w3 = a - CDbl(RoundTest(a / b) * b) ' 0
End Sub
IMO intrinsic implementation of calculations works so that results of native Round() function and returned from RoundTest() function are processed in different ways. Also turns out it may process any part of expression and particularly subtrahend not as double type, and having explicit conversion to double with CDbl() might help in your case, instead of coercion by assigning to temp variable of double type.You have to bear in mind that this effort and suchlike do not guarantee to fix the issue. Each calculation can introduce floating point errors, as #Comintern commented.
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.