Visual Basic

Adding to VarType

Another "byte array" way to extend the type system is to add in new Variant types. In Visual Basic 5, 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 Data access object
vbDecimal 14 Decimal
vbByte 17 Byte
vbArray 8192 Array

In Visual Basic 6, we have a new addition (and a great deal of scope for adding more-a lot of gaps!):

   vbUserDefinedType   36  User-defined type

With some limitations, we can add to this list. For example, we could, with only a small amount of effort, add a new Variant subtype 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 subtype 42; EX_CInt converts a Variant of subtype 42 into 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 , and so I've had to call 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 subtype 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 subtype, 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 subtypes 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 be utilized by a Visual Basic 6 client! In other words, because a Visual Basic 6 executable can pass in a Variant of subtype 14, Visual Basic must be built to expect unknown Variant types, given that the number of Variant subtypes 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 creating a new Variant subtype. This is especially true when, through what we must call a feature of Visual Basic, not all the conversion routines are available for subclassing. Why not use a UDT, or better still, a class to hold your new type instead of extending the Variant system?

Another limitation to creating a new Variant subtype is because of 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 subtype! 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 by reference. I couldn't let Visual Basic evaluate it, even though it's received as a Variant.

Variants also might need destructing 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 7. We would need to bear this in mind whenever we created new Variant subtypes and make sure that we could change their VarType values almost arbitrarily-added complexity that is, again, less than optimal!

All in all, creating new Variant subtypes 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 Variant subtypes 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 subtype's value and storage transparently through the VARIANT structure. To access its internals, it's necessary to change the meaning of Variant access from one of value to one of representation.