Adding to VarType

Another “byte array” way to extend the type system is to add new Variant types. In Visual Basic 4, the following subtypes were available via the Variant:

Visual Basic Name VarType Description
vbEmpty 0 Uninitialized (default)
vbNull 1 Contains no valid data
vbInteger 2 Integer
vbLong 3 Long integer
vbSingle 4 Single-precision floating-point number
vbDouble 5 Double-precision floating-point number
vbCurrency 6 Currency
vbDate 7 Date
vbString 8 String
vbObject 9 Automation object
vbError 10 Error
vbBoolean 11 Boolean
vbVariant 12 Variant (used only for arrays of Variants)
vbDataObject 13 Non-Automation object
vbByte 17 Byte
vbArray 8192 Array

In Visual Basic 5, we have one addition (and a great deal of scope for adding more—a lot of gaps!):

vbDecimal 14 Decimal value

With some limitations, we can add to this list. For example, we could, with only a small amount of effort, add a new Variant type of 42 to represent some new entity by compiling this C code to a DLL named NEWTYPE.DLL:

#include “windows.h"
#include “ole2.h"
#include “oleauto.h"

#include <time.h>

typedef VARIANT * PVARIANT;

VARIANT  __stdcall CVNewType(PVARIANT v)
{
    // If the passed variant is not set yet...
    if(0 == v -> vt)
    {
        // Create new type.
        v->vt = 42;

        // Set other variant members to be meaningful for this  
        // new type...
        // You do this here!
    }

    // Return the variant, initialized/used variants unaffected by 
    // this routine.
    return *v;
}


int  __stdcall EX_CInt(PVARIANT v)
{
    // Sanity check - convert only new variant types!
    if(42 != v->vt)
    {
        return 0;
    }
    else
    {
        // Integer conversion - get our data and convert it as 
        // necessary.
        // Return just a random value in this example.
        srand((unsigned)time(NULL));

        return rand();
    }
}

This code provides us with two routines: CVNewType creates, given an already created but empty Variant (it was easier), a Variant of VarType 42; EX_CInt converts a Variant of type 42 to an integer value (but doesn’t convert the Variant to a new Variant type). “Converts” here means “evaluates” or “yields.” Obviously, the implementation above is minimal. We’re not putting any real value into this new Variant type, and when we convert one all we’re doing is returning a random integer. Nevertheless, it is possible! Here’s some code to test the theory:

Dim v As Variant

v = CVNewType(v)

Me.Print VarType(v)
Me.Print EX_CInt(v)

This code will output 42 and then some random number when executed against the DLL. The necessary DLL declarations are as follows:

Private Declare Function CVNewType Lib “NEWTYPE.DLL” _
(ByRef v As Variant) As Variant
Private Declare Function EX_CInt   Lib “NEWTYPE.DLL” _
(ByRef v As Variant) As Integer

Again, we cannot override Visual Basic’s CInt (see page 347), and so I’ve had to name my routine something other than what I wanted to—in this case, EX_CInt for “external” CInt. I could, of course, have overloaded Val:

Public Function Val(ByRef exp As Variant) As Variant

    Select Case VarType(exp)
        Case 42:   Val = EX_CInt(exp)
        Case Else: Val = VBA.Conversion.Val(exp)
    End Select

End Function

Here, if the passed Variant is of type 42, I know that the “real” Val won’t be able to convert it—it doesn’t know what it holds after all—so I convert it myself using EX_CInt. If, however, it contains an old Variant type, I simply pass it on to VBA to convert using the real Val routine.

Visual Basic has also been built, starting with version 4, to expect the sudden arrival of Variant types about which nothing is known. This assertion must be true because Visual Basic 4 can be used to build ActiveX servers that have methods. In turn, these can be passed Variants as parameters. A Visual Basic 5 client (or server) can utilize these servers. A Visual Basic 5 client, then, might surprise a Visual Basic 4 server! In other words, because a Visual Basic 5 executable can pass in a Variant of type 14, Visual Basic must be built to expect unknown variant types, given that the Variant type is likely to grow at every release. You might want to consider testing for this in your Visual Basic 4 code.

Having said all this and having explained how it could work, I’m not sure of the real value currently of adding a value to VarType. This is especially true when, through what we must call a feature of Visual Basic, not all the conversion routines are available for subclassing. In other words, why not use a user-defined type or, better still, a class to hold your new type instead of extending the Variant system?

Another limitation to adding to VarType is due to the way we cannot override operators or define them for our new types. We have to be careful that, unlike an old Variant, our new Variant is not used in certain expressions. For example, consider what might happen if we executed Me.Print 10 + v. Because v is a Variant, it needs to be converted to a numeric type to be added to the integer constant 10. When this happens, Visual Basic must logically apply VarType to v to see what internal routine it should call to convert it to a numeric value. Obviously, it’s not going to like our new Variant type! To write expressions such as this, we’d need to do something like Me.Print 10 + Val(v). This is also the reason why, in the Val substitute earlier, I had to pass exp ByRef. I couldn’t let Visual Basic evaluate it (which it would have to do using ByVal), even though it’s received as a Variant.

Variants also might need to be destroyed correctly. When they go out of scope and are destroyed, you might have to tidy up any memory they might have previously allocated. If what they represent is, say, a more complex type, we might have to allocate memory to hold the representation.

Microsoft does not encourage extending the Variant type scheme. For example, 42 might be free today, but who knows what it might represent in Visual Basic 6. We would need to bear this in mind whenever we created new Variant types and make sure that we could change their VarType values almost arbitrarily—added complexity that is, again, less than optimal!

All in all, adding to VarType is not really a solution at the moment. If we get operator overloading and proper access to VBA’s conversion routines, however, all of this is a little more attractive.

Note The code to create Variants needs to be written in a language such as C. The main reason is that Visual Basic is too type safe and simply won’t allow us to treat a Variant like we’re doing in the DLL. In other words, accessing a Variant in Visual Basic accesses the type’s value and storage transparently through the VARIANT structure or user-defined type. To access its internals, it’s necessary to change the meaning of variant access from one of value to one of representation.