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.