# Visual Basic > Visual Basic 6 and Earlier >  [RESOLVED] VB6 Variant VarType Question

## rdee

Elroy's recent LongLong thread got me to wondering about something:

Suppose you had a procedure that had a ByRef Variant as a parameter . . .




> MySub(Vnt as Variant)
> 
>     If VarType(Var) = vbSingle Then
> 
>        ' change vartype in variant to a Long (VT_14) - a cheap form of casting
> 
>        ' now do bit operation or something else on Vnt as if it were a Long 
> End Sub


Would this work? Would it work if the parameter was ByVal?

I was thinking of experimenting, but I thought maybe some else has already tried.

Thank you in advance.
rdee

----------


## Elroy

If I understand what you're asking, yes, it'll work ByRef, but it's not going to work ByVal.

Except for Objects, Arrays, and Strings (and UDTs in special cases), all the data for a Variant is contained within the actual 16 bytes of the Variant.  In other words, when a Variant is passed ByVal, all of its data is pushed-and-popped from the stack just like passing any other variable ByVal.  And, to carry this farther, if the Variant is somehow changed in a procedure that received it ByVal, those changes won't be seen upon return to the caller.

Regards,
Elroy

----------


## Elroy

> ' now do bit operation or something else on Vnt as if it were a Long


Also, regarding that quote.  If you use VariantChangeTypeEx or VariantChangeType to change the type of a variant (to, say, a Long).  It's not going to be "as if" it were a long.  It "will" be a long, although sitting in a Variant.  And you can do anything with it that you can do to any other Long.  Are you thinking it'll still be a Single?  Because it won't.

This page (scrolled down to the Variants discussion) has a pretty good explanation of how Variants work. 

Best Regards,
Elroy

----------


## Elroy

Hi Again rdee,

I keep trying to figure out what you may have been thinking.  I'm now wondering if you thought a call to VariantChangeTypeEx would just change the Variant's type-indicator, and not change the actual Single's bits.  But that's not true.  Using VariantChangeTypeEx to re-typecast a Variant from a Single (4-bytes) to a Long (also 4-bytes) will literally re-arrange those 4-bytes from an IEEE Single to a Twos-compliment Long.  In other words, the actual bit-pattern of the data within the Variant _will_ be changed.

If you want to do bitwise operations (And, Or, etc) on a Single, you'd have to use the CopyMemory (RtlMoveMemory) API (or possibly GetMem4), to copy the bits into a long, do your bitwise, and then copy the bits back.  

However, doing bitwise operations on Single would be a _very_ strange thing to do.  Interpreting the bits of an IEEE Single is _not_ a trivial thing, and manipulating individual bits could easily corrupt the Single (although I seriously doubt it'd crash VB).  Wikipedia has a nice page on IEEE Singles.

Regards,
Elroy

EDIT1: By "crash", I was thinking of a memory fault type crash.  A corrupted Single could very well cause an error to raise.

----------


## rdee

Elroy, thank you for your responses and the link (and for the LongLong threads - most interesting!).

My interest in the question was in connection with some bit manipulation code I have posted on PSC (here). The class already uses GetMemN to read ByRef parameters (and optionally TypeCasting - adapted from a Bonnie West class) so as to do bit manipulation on the IEEE types. They get copied to a 2 Long UDT, then 'manipulated, then copied back. In the case of the optional casting, a Long array is pointed to the calling variable and the bit handling is done in place on the caller as if it were a Long array, with no need to copy the changes afterward.

The class uses ByRef Variants in all the procedures so that various VarTypes can be manipulated like this. So after reading your thread it got me to wondering if it would be possible to simply change the vartype of the variant parameter and then I could work directly on the calling variable similar to how I do with the typecasting described above. 

Reading your comments above, it appears to me that the VarType might need to be changed with GetMem2 or CopyMemory (or CopyBytes) rather than using the COM API. If I read your comments correctly, using the COM API to change the VarType might be causing other changes in the data, whereas, if I changed the VarType using GetMem or CopyMemory all I would change is the VarType, not the actual variable that is calling.

Anyways, that is what I am thinking. It could be I'm missing something. But I really appreciate your thoughts and the link which I'll pour over.

Take Care,
rdee

----------


## Elroy

> using the COM API to change the VarType might be causing other changes in the data, whereas, if I changed the VarType using GetMem or CopyMemory all I would change is the VarType, not the actual variable that is calling


That is _exactly_ correct.   :Smilie:

----------


## rdee

Elroy (or anyone else for that matter),

When a Variant is used as a ByRef parameter in a procedure, is there anything else Or-ed with the VarType to indicate that the data in the variant is a pointer (rather than the actual data)? I seem to remember something about this - but not quite sure.

The reason for asking is that if I used GetMem2 to get a copy of the VarType from a variant then I would have to mask out any such info added to the vartype value and add it back in when I replace the vartype with a new value, so that the ByRef variant still thinks it is ByRef. I'm guessing that if I failed to do this the variant parameter will now think that the pointer it holds is the actual data.

Just to clarify what I had in mind . . . 




> MySub(Vnt as Variant)  ' Vnt is ByRef
> 
>     Vnt = Vnt Or 2 ^ 5
> 
> End Sub


In this example, if a Long was the caller of MySub it will result in this: 
     00000000 00000000 00000000 00100000

But if the caller is a Single it will result in this:
     01000010 00000000 00000000 00000000

So my idea is, when a Single is the caller, I change the vartype in Vnt to a ByRef Long and then do the bit operation and (in theory) the Single caller will have the same bit pattern as the Long in the example above.

Or would VB throw a fit?

Thanks in advance,
rdee

----------


## rdee

Just got a chance to test out what I was theorizing in my previous post:




> Private Declare Sub(GetMem2 Src As Any, Dest As Any)
> 
> Public Sub MyTest(Vnt as Variant)
> 
> Dim n as Integer
> 
>      n = 16387  ' this is 3 (or &H3 to denote a Long) + 16384 ( = &H4000 to denote ByRef) = 16387 (or &H4003)
> 
>      GetMem2 n, ByVal VarPtr(Vnt)
> ...


When I called it with a Single (with a value of 0) I got this result:

      00000000 00000000 00000000 00100000

which is the same as I would get if I called it with a Long. (See my previous post for the value you would get if "Vnt = Vnt Or (2 ^ 5) is done on a Single.

No hiccups so far. Now I gotta do a little more thinking & speed comparison testing . . .

rdee

----------


## rdee

A little more testing:

After running my little VarType change trick on a Single, Vb is still treating the calling Single as a Single, not a Long. So far it appears that changing the VarType on a ByRef Variant parameter called by a Single does work as a sort of (temporary) casting.

After setting bit 5 in the Single to True (with no other bits set), I did this:

"MySingle=MySingle + 5"

Bit 5 in the Single was zeroed and the Single reverted to its natural state for a value of 5. No errors were raised.

This is interesting, if not a bit academic. Besides using a Single as a bit field, I'm not sure what other use this could have. It would be interesting to do this on a 64 bit IEEE type and temporarily cast it into a LongLong.

rdee

----------


## passel

It seems like a more valid test would be to send something other than 0.
A Long zero is four bytes of 0.
A Single zero is four bytes of 0.

Try setting the Single value to some other number like 8, then set the bit and see what the result is.
Then set it to 16, set the bit and see what the result is.

I didn't test it, but I would expect setting the 6th bit in a single with different values in the exponent, would have twice the effect on the Single that was 16 vs the Single that was 8.
Without actually trying it out, my guess would be that 8 would become something like 8.000030  and 16 would be 16.000060, or something along those lines.

p.s. Setting a lower bit (any or even all of the lower 23 bits) with the exponent portion (the bits 24 and above) zero, will be a very small number close to 0 (along the lines of 10^-39 to 10^-44).
That is why as soon as you add a relatively big number (like 5 in your example) to it, the lower bit was cleared.
It wasn't actually cleared, it was logically pushed 128+ bits to the right so fell well outside the range of resolution of a Single of that magnitude (i.e. the value 5 would only have around 21 bits of fractional resolution available in the single) can hold.

----------


## Elroy

rdee,

I'm a bit busy for a couple of weeks, but let me say a couple of things.  With respect to a Variant, you can always start by just thinking of it as a 16 byte variable.  In the cases of a non-array Byte, Integer, Long, Single, Double, or Decimal, you can be assured that the actual data resides in those 16 bytes.  As, I think you know, the first two bytes of the Variant are always reserved to denote the "type" of variable that the Variant is.  Except for Decimal, the next 6 bytes are ignored, with the last 8 bytes (or a portion of them) storing the actual data.  Therefore, if it's one of these, you can just "spoof" (change type without changing anything else) the type with no harm done.

Spoofing the type would certain be a strange thing to do, but it wouldn't hurt anything.  The reason I say it would be strange is because of things like the following.  When thinking of a Byte, Integer, or Long, all possible bit patterns result in a valid number.  However, when thinking about Single or Double, there are many bit patterns that result in NaN values (NaN = Not-a-Number).  Read about IEEE Singles and IEEE Doubles to understand more about this.

Now, there are also cases where a Variant may have a pointer as its data.  This is true in the case of the Variant containing a String, an Object, or an Array (and also UDTs with GUIDs).  In these cases, if you "spoof" (change with GetMem2 or CopyMemory) the Variant's type, you are probably going to corrupt memory, resulting in a hard crash.

It's not a matter of whether the data will fit into the 16 bytes (or 14 bytes following the "type" 2 bytes).  It is all about what the Variant is storing.  And I've briefly outlined most of it above.  There are also cases like Null, Empty, Error and a few others that actually are stored/indicated wholly within the Variant.  If it's wholly stored within the Variant, spoofing isn't going to corrupt memory, although it may result in some strange interpretations of the Variant's data (that could potentially raise overflow errors (or the like)).

Best of Luck,
Elroy

----------


## DEXWERX

> rdee,
> 
>  In the cases of a non-array Byte, Integer, Long, Single, Double, or Decimal, you can be assured that the actual data resides in those 16 bytes.


I think his point was VT_BYREF ? where the data is not stored in the variant, a pointer is.

anyway, you can experiment with these if you want.


```
Private Declare Function VariantChangeTypeEx Lib "oleaut32" (ByRef pvargDest As Variant, ByRef pvarSrc As Variant, ByVal lcid As Long, ByVal wFlags As Integer, ByVal VT As Integer) As Long
Private Declare Function GetMem4 Lib "msvbvm60" (Src As Any, Dst As Any) As Long
Private Declare Function GetMem2 Lib "msvbvm60" (Src As Any, Dst As Any) As Long

Public Const vbLongLong As Integer = &H14
Private Const vbTypeMask As Integer = &HFFF
Private Const vbByRef As Integer = &H4000

Public Property Get VarType(VarName) As VbVarType
    GetMem2 VarName, VarType
    VarType = VarType And vbTypeMask
End Property

Public Property Let VarType(VarName, ByVal Value As VbVarType)
    Dim VT As Integer
    GetMem2 VarName, VT
    VT = (VT And (Not vbTypeMask)) Or (Value And vbTypeMask)
    GetMem2 VT, VarName
End Property

Public Function CLngLng(Expression)
    Const LOCALE_INVARIANT As Long = &H7F&
    Dim hr&: hr& = VariantChangeTypeEx(CLngLng, Expression, LOCALE_INVARIANT, 0, vbLongLong)
    If hr < 0 Then Err.Raise hr
End Function
```



```
    Dim v: v = CLngLng("&H7FFFFFFFFF1FFFFF")
    MsgBox v
    VarType(v) = vbCurrency
    MsgBox v
```

----------


## rdee

Thank you passel, Elroy and DEXWERX for both your insights and the (DEX) the code to play with.

As DEX pointed out, my main interest is in ByRef Variant parameters, although, I more than glad to broaden out my (limited) knowledge in other areas related to this.

The Single-to-Long aspect that I was testing out above was mainly as a proof of concept type thing. That is, could I temporarily convert a Single to a Long (since they share the same size). And could it be done efficiently by simply changing the VarType of the Variant it was passed thru. Using a Single as a bitfield was also my main interest. Of course, I'll grant that using a Single where a Long is better suited (as a bit field) would be 'very odd.'

My real interest is in the idea of turning a 64 bit type (Currency, Double, Date) into a LongLong and be able to persist it. And to see if  doing it via playing around with the vartype of the variant it was passed thru might be more efficient than using GetMem8 to copy it back and forth.

But work beckons right now. I post more of my experiments later. And thank you all again for the responses above.
rdee

----------


## rdee

Okay, so I did a bit of testing:




> Public Sub MyTestSub(Vnt As Variant)
> Dim L As Long, V As Long, LL As Long
> 
>    L = &H4003  ' this is the marker for ByRef and Long
>    V = &H400C  ' this is the marker for ByRef and Variant
>    LL = &H4014  ' this is the marker for ByRef and LongLong
> 
>    Select Case VarType(Vnt)
>       Case vbInteger
> ...


Here are my results from testing:

*Integers:* When I send an integer to the sub, the sub puts the ByRef/Long indicator (&H4003) into the 1st 2 bytes of Vnt and then Vnt is Or'ed with 2 ^ 15. Normally this would cause an overflow when done to an integer. But instead, the sign bit was set on the calling integer. I also repeated this with the calling integer having various values. The result was the same - the sign bit was turned on w/o any overflow problems. This is a clear indication that the calling integer was being treated like a Long. Obviously, one would have to be careful not to do anything past the two bytes where the integer resides.

*Longs:* I called the procedure with a Long and the procedure then changes the VarType to a ByRef/LongLong. Then the procedure attempts to Or Vnt with 2 ^ 31. Normally this would cause an overflow in a Long. But it would not be a problem for a 64 bit integer. Instead, what I got was an error: "Variable uses an Automation Type not supported by Visual Basic." So it appears that any use of the LongLong type must remain in the Variant. VB won't allow an outside variable to be cast to a LongLong.

*Singles:* I did as passel suggested. I sent a Single to the procedure with various values. The sub puts &H4003 (for a ByRef/Long) in the vartype section of Vnt and then Or's it with 2 ^ 5. Here are the binary string results:

Single = 8     : 01000001 00000000 00000000 00000000
Or'ed w/ 2^5 : 01000001 00000000 00000000 00100000

Single = 16    : 01000001 10000000 00000000 00000000
Or'ed w/ 2^5 : 01000001 10000000 00000000 00100000

Again, it is apparent that changing the vartype from a ByRef/Single to a ByRef/Long effectively casts the calling Single into a Long, at least for the duration of the procedure.

*Variants:* This one was a surprise. I sent an empty variant (dimmed only) to the procedure. What I was expecting in Vnt was a ByRef/Vartype of &H400C (&H4000 = ByRef and &H000C = Variant). I was also expecting there to be a Long pointer at the +8 byte offset. Instead, what I got was . . . nothing, or rather, an empty variant. No vartype, no ByRef indicator, and no pointer back to the calling Variant. I'm a bit perplexed about that.

Any thoughts or insights would be most welcome.
rdee

Edit1: Incidentally, I can do VarPtr(MyVar) when MyVar is empty and I will get a memory pointer. But when I sent it to the sub (above) there is no pointer sent. ??

----------


## Elroy

Hi rdee,

I followed you (with interest) to the very end, and then I got lost.

Okay, VarPtr(MyVar) is always going to return a pointer to the 16 byte memory structure for the Variant (and that 16 byte structure is always going to exist).  If it's uninitialized it's going to be 16 bytes of zeros.  Now, if you pass that ByRef, it's going to do something a bit differently than passing a Long, Integer, etc to a procedure with a Variant as an argument.

For instance, if you pass a Long to a procedure expecting a Variant (as I'm assuming you did above), it'll create a temporary Variant, and then put a pointer in the Variant that points to the Long.  So, you've only got one Long, but you've also got a temporary Variant that's used by your called procedure that gets destroyed when it returns.

Now, when you pass a Variant (ByRef) to a procedure expecting a Variant, there is no temporary Variant created.  This time, an address (same one reported by VarPtr(MyVar)) is passed on the stack to your procedure.  If it's uninitialized, it's just a pointer to those 16 bytes of zeros.  And, if you go digging around in the uninitialized Variant, all you'll find is those zeros.

Regards,
Elroy

p.s.  Nice Work!

EDIT1:  It's also interesting, but scary, to me that you're "spoofing" the type of a variable and then doing math on it.  I suppose it's not a problem if the original variable and the spoofed variable have the same byte-length (such as Single and Longs, or Doubles and Currency).  However, if you spoof a Long to a Currency, it sure seems like it'd be easy to start writing into memory you shouldn't be messing with (even with math that doesn't, on its face, seem like it would).  It'll probably be difficult to cause a crash, but you may be changing another variable's value and not even know it.

EDIT2:  Also, I think your conclusions about LongLongs is on-the-nose, but also interesting.  I also did some testing and you seem to be spot-on.  If a LongLong is sitting in the variant, all the math works fine.  However, if a Variant references it, math (or even assignment) causes automation error.



```

Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef source As Any, ByVal bytes As Long)

Private Type TwoLongs
    l1 As Long
    l2 As Long
End Type


Private Sub Form_Load()
    Dim udt As TwoLongs
    udt.l1 = 123
    test udt.l1
End Sub


Private Sub test(v As Variant)


    CopyMemory v, &H4014, 4


    MsgBox Hex$(VarType(v))     ' <--- Reports 14 as we'd expect.

    'v = v + 1                   ' <--- Crashes with automation error

    Dim v2 As Variant
    'v2 = v                      ' <--- ALSO, crashes with automation error

    ' So we get it out the hard way.
    CopyMemory v2, &H14, 4      ' <--- Force it to be a LongLong for our test.
    Dim t1 As Long
    Dim t2 As Long

    CopyMemory t1, ByVal VarPtr(v) + 8, 4           ' Get the address to the long's data.

    CopyMemory ByVal VarPtr(v2) + 8, ByVal t1, 4    ' Move Long's data directly into V2.

    MsgBox v2                   ' <--- Reports 123.
    MsgBox Hex$(VarType(v2))    ' <--- Reports 14 as we want.

    v2 = v2 + 1                 ' <--- THIS time, no error, yayyy!

    MsgBox v2                   ' <--- Reports 124.

End Sub


```

----------


## Tanner_H

@rdee: thank you for sharing your findings as you discover them.  For every forum user who comments on an "exploration" post like this, I think there are quite a few others (like me!) who silently follow along out of curiosity.

In case others are curious, I thought I'd mention that in other languages, there are many performance tricks that originate from logical operations performed on floating-point values.  Here's one example of fast int/float conversions by messing with a Single's mantissa bits:

http://lolengine.net/blog/2011/3/20/...er-conversions

So yes, there are absolutely times that you might want to treat floating-point values as "bitfields", although doing so in VB is... not straightforward (to say the least).

----------


## rdee

Wow, thank you Elroy!




> For instance, if you pass a Long to a procedure expecting a Variant . . . it'll create a temporary Variant, and then put a pointer in the Variant that points to the Long. So, you've only got one Long, but you've also got a temporary Variant that's used by your called procedure that gets destroyed when it returns.


I had no idea how that worked. That makes a lot of sense now.




> Now, when you pass a Variant (ByRef) to a procedure expecting a Variant, there is no temporary Variant created. This time, an address (same one reported by VarPtr(MyVar)) is passed on the stack to your procedure.


Would there be any way to get access to that address (i.e. the empty variant that calls the procedure) from within the procedure? (Because the pointer is not in the variant parameter - its empty like the variant caller.)

Thanks a bunch for your insights, Elroy.
rdee

----------


## Elroy

> Would there be any way to get access to that address (i.e. the empty variant that calls the procedure) from within the procedure? (Because the pointer is not in the variant parameter - its empty like the variant caller.)


I'm not exactly sure what you mean, but here's what I think you mean...




```

Option Explicit

Private Sub Form_Load()

    Dim v As Variant
    MsgBox VarPtr(v)    ' <--- reports starting memory address of 16 bytes for Variant
    test v


    Dim l As Long
    MsgBox VarPtr(l)    ' <--- reports starting memory address of 4 bytes for the Long
    testtt l



End Sub


Sub test(vv As Variant)

    MsgBox VarPtr(vv)   ' <--- because we passed ByRef, we get the same memory address as before the call.

End Sub

Sub testtt(vv As Variant)

    MsgBox VarPtr(vv)   ' <--- this time, we get a different address, the one to the temporary variant.

End Sub



```

----------


## Elroy

And just as an FYI, you can get the memory address of just about anything you want in VB6.  The only exceptions I've found to this are Constants and Fixed-Length-Strings.

Regarding Constants, you put them into Varptr(SomeConst), and it creates a temporary variable and returns the memory address of that temporary variable, and _not_ the constant.

Something similar happens with fixed-length-strings.  Just about anyway you touch those things (other than assigning data to them), it turns them into a temporary-variable-length-string, does its work, and then destroys the temporary-variable-length-string.  And that's also true when doing VarPtr(SomeFixedLenStr).  All you'll get is the memory address of the temporary-variable-length-string.

The one regarding fixed-length-strings has been particularly frustrating to me at times.  There are just certain times when I'd like to tinker directly with their memory, but I've never found a way to do it.  You can "cheat" by putting them into a UDT and then get the VarPtr(UDT), but that's about the only way.

Best Regards,
Elroy

----------


## rdee

*Tanner_H:*

Thank you for your thoughts. The link you provided is very interesting, but way over my head. My knowledge of C/C++ is less than miniscule (although, I did actually convert a C++ routine to VB in the class I referenced and linked to on PSC in one of my posts above - see the "CountBits" routine). The class uses memory copying and/or casting with a long array to do bit manipulation on singles, doubles, currencies and dates. I did not know that there was other reasons for bit manipulating other than for making bit fields. 

My interest in altering vartypes was to look at another possible form of casting. At the moment I am thinking that memory copying is more efficient that changing vartypes. But I did want to explore the possibilities (and hopefully come away with more knowledge.)

*Elroy:*

I'm having a problem with using VarPtr on a variant. Using the example code I had above to explain what I'm getting:

Dim MyVariant

MsgBox VarPtr(MyVariant)   ' this gives me an address - e.g. 1631736

Now I call MySub(Vnt as Variant) using MyVariant and within the sub I do: VarPtr(Vnt) and I always get an address that is 32 bytes higher than when I called VarPtr(MyVariant). 

I wonder where the extra 32 bytes is coming from?


But besides that, I want thank you for your experienced help.
rdee

----------


## Elroy

Hmmm rdee,

I'm not sure.  I just did this test...



```

Option Explicit

Private Sub Form_Load()
    Dim MyVariant
    MsgBox VarPtr(MyVariant) ' <--- I get 1636440 (but you'll probably get something different).
    Call MySub(MyVariant)

End Sub


Sub MySub(Vnt As Variant)
    MsgBox VarPtr(Vnt)  ' <--- I STILL get 1636440 (and you should get the same number you originally got).
End Sub


```

Can I see your test code (including the code where you initially check VarPtr and also the code where you call MySub)?

----------


## rdee

Sorry Elroy,

I just checked my code again and there was a mistake. I'm getting the same results as you are now. I've been up too long.

I'm going to give it a rest for the night. Thank you for your patient efforts.

rdee

----------


## Schmidt

> Would there be any way to get access to that address (i.e. the empty variant that calls the procedure) from within the procedure? (Because the pointer is not in the variant parameter - its empty like the variant caller.)


You can determine whether VB "passed a Variant directly", by checking for VT_BYREF.
That value is masked out by VBs VarType-Function though, so you will have to copy the first two bytes (the VT-Field) of the Variant over yourself.

In the example below, I simply adjust the "pData-Pointer" to the Value accordingly.
In case of a passed VT_ByRef, there is usually a pointer **at** the Offset 8 - 
and in case VT_ByRef was not set, the pointer **is** the Offset 8.



```
Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef source As Any, ByVal bytes As Long)
 
Private Sub Form_Load()
  Dim V As Variant, L As Long, C As Currency, S As Single, D As Double
      V = CLng(1): Test V
      V = CCur(1): Test V
      V = CSng(1): Test V
      V = CDbl(1): Test V
    
      L = 1:       Test L
      C = 1:       Test C
      S = 1:       Test S
      D = 1:       Test D
End Sub
 
Sub Test(V As Variant)
Const vbByRef As Integer = &H4000

   Dim VT As VbVarType, pData As Long, LL(0 To 1) As Long
   CopyMemory VT, ByVal VarPtr(V), 2

   If VT And vbByRef Then
     CopyMemory pData, ByVal VarPtr(V) + 8, 4
     If pData = 0 Then MsgBox "a Zero-Pointer was passed": Exit Sub
   Else
     pData = VarPtr(V) + 8 'in case of a real Variant, most data starts at Offs 8
   End If
 
   Select Case VT And Not vbByRef
     Case vbLong, vbSingle
       CopyMemory LL(0), ByVal pData, 4
       Debug.Print "32Bit-Value: ", LL(0)
     Case vbCurrency, vbDouble, vbDate
       CopyMemory LL(0), ByVal pData, 8
       Debug.Print "64Bit-Value: ", LL(0), LL(1)
   End Select
End Sub
```

Olaf

----------


## rdee

Olaf, thanks for your addition to the thread.

One of the things that mystified me was the fact that if an empty variant was sent to a sub with a ByRef variant procedure, the parameter stays empty, just like the empty calling variable. I was expecting the parameter to have &H400C (for ByRef and Variant) in the 1st 2 bytes and for the address of the caller to be at offset 8. As Elroy pointed out, you can still get the address with VarPtr. It just seemed strange that the ByRef variant follows a different method than for the more standard variables (like int, long, single, etc).

I had the idea of playing around with a variant and to use it as a 128 bit bitfield. That is why I was testing an empty variant. If one were going to experiment with that he would have to strictly avoid anything that might otherwise set bits within the variant.

rdee

----------


## Elroy

Hi rdee,

Just another point on this.  VB6 is quite resistant to putting one Variant into another Variant.  First, it'd always have to be ByRef because one Variant wouldn't wholly fit into another Variant in the way Longs, Singles, and other types will.  Secondly, if they allowed this, you could built a recursive loop of Variants, which wouldn't be good.

I don't know all the details off the top of my head, but I do believe there are ways one Variant can reference another so long as it's never more than one nested level deep.  But I typically stay away from doing that altogether.  

Regards,
Elroy

----------


## georgekar

I found a cInt64() but works bad, so I change it with this.
We can get a variant with type 20 (Int64). We can also get the variant from the hex value, cint64("&HFFFFFFFFFFFFFFFF") return -1. The problem was for hex values: "&H00000000FFFFFFFF" this convert to -1 from vb6, because the first leading zero removed. But if we have 1 bit somewhere to high long we get the right value. So we have to get the lower long (if the higher is zero) and compute it as unsigned long. The first cInt64 = CDec(v), may produce a sign, which isn't part of the value part of decimal. The decimal has an unsigned integer plus a scale and sign.


```
Public Function cInt64(v As Variant) As Variant
    Dim DecHdr As Dec_Hdr, m As Long
    On Error GoTo er111
    cInt64 = CDec(v)
     If VarType(v) = vbString Then
        If InStr(1, v, "&h", vbTextCompare) = 1 Then
            Do
                m = Len(v)
                v = Replace(v, "&H0", "&H", , , vbTextCompare)
            Loop Until Len(v) = m Or m < 5
            If m = 10 Then
                If cInt64 < 0 Then
                    cInt64 = CDec("&H100000000") + cInt64
                End If
            End If
            
        End If
    End If
    CopyMemory DecHdr, ByVal VarPtr(cInt64), LenB(DecHdr)
    If DecHdr.DecScale Then
    cInt64 = Fix(cInt64)
    End If
    GetMem4 VarPtr(cInt64) + 12, m
    If VarType(v) = vbString Then If m < 0 And Len(v) <= 10 Then GoTo er111
    PutMem4 VarPtr(cInt64), VT_I8
    If (DecHdr.DecSign <> 0) And (cInt64 > 0) Then cInt64 = -cInt64
    If (VarType(cInt64) <> VT_I8) Then Err.Raise 6
    Exit Function
er111:
     Err.Raise 6
End Function
```

I have examine the code through M2000 Interpreter 



> // && for Long Long (64bit)
> // & for Long % for Integer
> // A &HFFFFFFFF is Currency type (represent unsigned Long)
> // A &HFFFFFFFFFFFFFFFF is Decimal (represent unsigned long long)
> clipboard str$(0xFFFFFFFF,"")
> ? 0xFFFFFFFF=4294967295  // max unsigned long (as currency value)
> // @ using for decimal literals. Without this, we get a rounded double (the default type)
> ? 0xFFFFFFFFFFFFFFFF=18446744073709551615@  // max unsigned long long (as decimal value)
> // we can use 0x or &H for hex numbers
> ...

----------


## georgekar

And these types/Enums


```
Private Enum VARENUM
   VT_I8 = &H14
End Enum ' } VARENUM;


Private Type Dec_Hdr
    DecType     As Integer
    DecScale    As Byte
    DecSign     As Byte
End Type
```

----------


## Elroy

Georgekar, what is it you're trying to accomplish, in the broadest sense?

If you're just trying to get 64-bit integer arithmetic, it's probably easiest to just use a Currency.  Doing that, you can directly add and subtract.  And you can use APIs to do multiplication and division.

If you're just looking for large numbers, you're probably best off just using the Decimal type.

If you just really want to use the LongLong type, it's always going to be tricky because the native LongLong type isn't truly supported in VB6.  Yeah, you can (sort of) coerce it into a Variant, but getting LongLong values into that Variant is always going to be tricky.

----------


## Elroy

Personally, if I was insistent upon going that route, I'd probably cast to a Decimal (in a Variant), and then copy the important 8-bytes from that Variant into a LongLong (in a Variant).  That way, you'd circumvent overflow issues when going to the Decimal.  However, you'd still have to manually check for overflows of the Decimal into a LongLong, because the Decimal can have larger numbers than a LongLong can deal with.  You'd also want to round the Decimal to an integer value before moving to the LongLong so that you wouldn't have to deal with any exponent of the Decimal.

But, supplying an input value for you're cInt64 function is always going to be tricky, because, as stated, the LongLong isn't natively supported in VB6.  To guarantee success, you'll probably always need to specify a String.  A String will always cast correctly into a Decimal (Variant), even when a very large number is in the String.

----------


## Elroy

You know what?  Even using a String for input doesn't keep you out of trouble.

If you try the following:



```

Debug.Print CDec("&h8000000000000000")

```

You get:




> -9223372036854775808


I've got no idea why that's coming back as a negative number.  To my way of thinking, it should be positive.  Apparently, VB6 is making some internal use of the LongLong type.

----------


## georgekar

@Elroy, 
I had an evaluator for any types in M2000, written in vb6, which works with variants (may return object from expression, if some functions return objects,  and also may use a operators defined for a special object named Group). So I try to figure how VB6 trait the int64 values. So I said for variants. I have a declare function for calling external functions and I found that I can send int64 through the variants in parameters, by value and by reference, without using Currency, (not the Vb6 declare, but another maid with Vb6 code). 
Here is the inspection on various results of types when we mix types.
As we see a long long (int64) value added with a value of 1 of type double return double, and for decimal and currency, we get decimal and currency. This is one "big difference". 

The \ operator  (div in M2000) for long/integer division works for long long too if we have long long, long and integer as divisor.
So a Long Long (stored in a variant) works fine, with long and integer. The / operator return double.




```
def typ$(x)=type$(x)
// test long long (int64)
? typ$(10&&*1)="Double"  ' && for long long (int64), no special char is double
? typ$(10&& div 1&&)="Long Long"  ' using \ inside vb6 code
? typ$(10&& div 1)="Double"  ' using \ inside vb6 code
? typ$(10&& div 1&)="Long Long"  ' using \ inside vb6 code
? typ$(10&& div 1%)="Long Long"  ' using \ inside vb6 code
? typ$(10&& div 1#)="Currency"  ' using \ inside vb6 code # for currency
? typ$(10&& div 1@)="Decimal"  ' using \ inside vb6 code @ for decimal
? typ$(10&& / 1&&)="Double"
? typ$(10% / 1&&)="Double"
? typ$(10@/ 1&&)="Decimal"
? typ$(10#/ 1&&)="Double"
? typ$(10&& +1)="Double"
? typ$(10&& + 1%)="Long Long"  ' % for integer
? typ$(10&& + 1&)="Long Long" ' & for long
? typ$(10&& + 1&&)="Long Long" ' && for long long
? typ$(10&& + 1@)="Decimal" ' @ for  Decimal
? typ$(10&& + 1#)="Currency" ' # for Currency
? typ$(10&& + 1)="Double"
? typ$(10&& + 1~)="Double" ' ~ for Single
// Decimal have higher priority
? typ$(10@ + 1)="Decimal" ' Decimal + Double
? typ$(10@ * 1)="Decimal"
? typ$(10@ ** 2)="Double"  ' except for power
? typ$(10@ * 1e34)="Double" ' if is out of range we get a Double.
? typ$(10@ + 1e34)="Double" ' if is out of range we get a Double.
// Currency has some limitations
// see currency multiply by a double value return double always
? typ$(10# + 1)="Currency" ' Currency + Double
? typ$(10# * 1)="Double"
? typ$(10# ** 2)="Double"  ' except for power
? typ$(10# * 1e34)="Double" ' if is out of range we get a Double.
? typ$(10# + 1e34)="Double" ' if is out of range we get a Double.
// using Currency amd integer we get Currency
? typ$(10# + 1%)="Currency" ' Currency + Integer
? typ$(10# * 1%)="Currency"
```

----------


## Elroy

Here's a quick mock-up as to how I might do it:



```

Option Explicit
'
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef source As Any, ByVal bytes As Long)
Private Declare Function VariantChangeTypeEx Lib "oleaut32" (ByRef pvargDest As Variant, ByRef pvarSrc As Variant, ByVal lcid As Long, ByVal wFlags As Integer, ByVal vt As Integer) As Long
'


Public Function cInt64(s As String) As Variant
    ' As a note, if "&H..." is passed in, there might still be problems,
    ' as these are possibly going to be seen as negative in certain circumstances.
    ' All base-10 numbers in strings should work just fine.
    '
    Static bInit    As Boolean
    Static vLo      As Variant
    Static vHi      As Variant
    Static v2sComp  As Variant
    If Not bInit Then
        bInit = True
        vHi = CDec("+9223372036854775807")          ' Largest LongLong.
        vLo = CDec("-9223372036854775808")          ' Smallest LongLong.
        v2sComp = CDec("9223372036854775808") + CDec("9223372036854775808")
    End If
    '
    ' Cast input to a Decimal, as locale invariant.
    Const LOCALE_INVARIANT As Long = &H7F&
    Dim vDec As Variant
    VariantChangeTypeEx vDec, s, LOCALE_INVARIANT, 0&, vbDecimal
    '
    ' Make sure we don't overflow the LongLong.
    If vDec > vHi Then Error 6
    If vDec < vLo Then Error 6
    '
    ' Make sure an integer number was supplied.
    Dim Exp As Byte
    CopyMemory Exp, ByVal (VarPtr(vDec) + 2&), 1&   ' Get the exponent.
    If Exp <> CByte(0) Then Error 13                ' Don't allow fractions.
    '
    ' Since sign bit is stored separately in Decimal, we just force Decimal to 2s complement.
    If Sgn(vDec) < 0 Then vDec = vDec + v2sComp
    '
    ' The Decimal should nicely snuggle into a LongLong after the above checks.
    Const VT_I8 As Integer = &H14
    CopyMemory cInt64, VT_I8, 2&                    ' Force Variant's type.
    CopyMemory ByVal (VarPtr(cInt64) + 8&), _
               ByVal (VarPtr(vDec) + 8&), 8&        ' Copy data from Decimal to LongLong.
    '
    ' Should return our Variant as a LongLong.
End Function


```

----------


## georgekar

> You know what?  Even using a String for input doesn't keep you out of trouble.
> 
> If you try the following:
> 
> 
> 
> ```
> 
> Debug.Print CDec("&h8000000000000000")
> ...


It is a negative type, because all the signed types, like integer, long, int64 (or long long)  use last bit as sign, so you just pass the sign, the 4th bit of last nibble (which is at the left side).
This is M2000 code, so we see that -32678 as integer has a Hex representation &h8004, where the last bit is the sign and the others is the number. How we can calculate the number?
reverse all the bits and subtract one. (it is known as 2s complement arithmetic, where 1st complement is the reversing of bits).



```
? &h8004%=-32764
? -(&h7FFD%-1)=-32764
? &hFFFF8004&=-32764
? -(&h00007FFD&-1)=-32764
? &hFFFFFFFFFFFF8004&&=-32764
? -(&h0000000000007FFD&&-1)=-32764
```

----------


## Elroy

> It is a negative type, because all the signed types, like integer, long, int64 (or long long)  use last bit as sign, so you just pass the sign, the 4th bit of last nibble (which is at the left side).
> This is M2000 code, so we see that -32678 as integer has a Hex representation &h8004, where the last bit is the sign and the others is the number. How we can calculate the number?
> reverse all the bits and subtract one. (it is known as 2s complement arithmetic, where 1st complement is the reversing of bits).


George, I "get" all of that.  However, again, natively, VB6 shouldn't understand the LongLong type.  So, why it's interpreting CDec("&h8000000000000000") as negative, is quite interesting to me.  The only time that's a negative number is if it's seen as 2s complement LongLong.

----------


## georgekar

About the overflow. In vb6 if we add two long long (int64) which get out of limits we get double



```
? typ$(&H7FFFFFFFFFFFFFFF&&+1&&)="Double"
```

The real problem is with the sign. Because, for positive numbers (when we have last bit 0) we have one less from negative numbers. Because with last bit zero (the sign) we have the 0 and all positives, and for last bit 1, we have all negative numbers (so always negative numbers are one more for positive, for this type of storage of numbers).

So we have 32767% max integer (16bit) and -32768% minimum integer. So when we get the negative from any integer, there is one integer which cause overflow: the minimum integer. This is the same for long and long long.

Decimal number has unsigned value, and the sign exist as a flag only.

----------


## georgekar

> George, I "get" all of that.  However, again, natively, VB6 shouldn't understand the LongLong type.  So, why it's interpreting CDec("&h8000000000000000") as negative, is quite interesting to me.  The only time that's a negative number is if it's seen as 2s complement LongLong.


I think VB6 use a standard function from a library which work for long long. But as I found if the higher long is zero works like it is long value, so "&H0000000080000000" return the minimum long, not the long long value.

So using variants we can use Long Long. The VarType() works, return 20, but the Typename() raise error (the type isn't of use). I think the problem with the use of long long was about OLE types, which not include the Int64, so I think for that reason this type not included in VB6.

----------


## wqweto

> About the overflow. In vb6 if we add two long long (int64) which get out of limits we get double.


Only when working with VT_I8 (20) wrapped in Variants. If you work with LongLongs in say VBA7 (x86 or x64) you get regular overflow.

You have to force Variants with CVar(A) + CVar(B) to not overflow with exception and to get a Double result.

Btw, in TwinBASIC there is a nifty function/module attribute *IntegerOverflowChecks (False)* which allows treating signed integeres as unsigned without overflows.

Btw, "OLE types" (i.e. automation types) include LongLong so the reason it's missing in VB6 is simpler -- just a legacy language!

cheers,
</wqw>

----------

