Variant Type
Purpose
The Variant data type is the data type for all variables that are not explicitly declared as some other type (using statements such as Dim, Local, Global, Public, or Static). The Variant data type has no type-declaration character.
Syntax
Dim v [As Variant]
v:variable name
Description
A Variant is a special data type that can contain one of a range of data types from Integers, Floating Point numbers, strings, objects and arrays (all the supported types are discussed in more detail later).
Variants are 16-byte variables: the first two bytes hold the code describing the variable type it is mimicing, the next six are reserved for system use and the last eight hold either the data value or a reference to it; described as a Type, it looks like this:
Type VariantType
wVarType As Word
wReserved1 As Word
wReserved2 As Word
wReserved3 As Word
pData As VariantData
EndType
Type VariantData Union
ByteData As Byte
ShortData As Word
LongData As Long
SingleData As Single
CurrencyData As Currency
DoubleData As Double
Date As Date
StringObject As Handle
EndType
Although Variants are more flexible than standard variable types, their complexity makes them slower in operation so they should be used mainly where their flexibility proves useful but less so where speed is of the essence. A speed comparison can be made by running the following code:
Local a, b, m%, n%, t1#, t2#
t1# = Timer
For a = 1 To 100000 : b = b + 50 : Next a // Loop using Variants
t1# = Timer - t1#
t2# = Timer
For n% = 1 To 100000 : m% = m% + 50 : Next n% // Loop using Int32 variables
t2# = Timer - t2#
Debug "Variant time: ";t1# : Debug "Integer time: ";t2#
Debug "Int32 variables were" & Round(((t1# / t2#) * 100), 2) & "% faster than Variants"
Initialisation and Conversion
To initialise a Variant use Dim var as Variant; you can omit the As Variant as any undefined variables are assumed to be Variants.
Once a Variant is initialised it is considered to be Empty until a value or object is assigned to it.
Generally, you do not have to concern yourself with the internal representation of data inside a Variant, nor with variable initialisation when a value is added, as GFABASIC and Windows handle this very competently. There are occasions, however, when you may wish to either specify or change the variable type for some purpose or other and, in most cases (except for Bytes and Shorts) this is possible using the conversion functions CInt, CSng, etc (see here). More information on how to do this is given below in the sections on Variable Types.
Similarly, comparisons and conversions between string and numeric data types are usually seamless, but errors will be thrown if you try to convert a string which is not numerical to a numerical data type, or compare such a string with a numeric literal or variable. The IsNumeric query function can be used to try and intercept any possible errors of this type.
When GFA-BASIC 32 converts a representation that is not numeric (such as a string containing a number) to a numeric value, it uses the Regional settings (specified in the Windows Control Panel) to interpret the thousands separator, decimal separator, and currency symbol.
Thus, if the country setting in the Windows Control Panel is set to United States, Canada, or Australia, these two statements would return true:
Print IsNumeric("$100")
Print IsNumeric("1,560.50")
While these two statements would return false:
Print IsNumeric("€100")
Print IsNumeric("1.560,50")
However, the reverse would be the case - the first two would return false and the second two true - if the country setting in the Windows Control Panel was set to Germany.
If you assign a Variant containing a number to a string variable or property, GFA-BASIC 32 converts the representation of the number to a string automatically. If you want to explicitly convert a number to a string, use the CStr function. You can also use the Format function to convert a number to a string that includes formatting such as currency, thousands separator, and decimal separator symbols. The Format function automatically uses the appropriate symbols according to the Regional Settings Properties dialog box in the Windows Control Panel.
It should also be noted that automatic conversion of data to a Variant does have its limits. What should be done when two Variants are added (or concatenated) when one contains a string and the other a numeric value? For the concatenation below, the possible courses of action are:
vntC = CVar("123") + CVar(456)
- Add them as if they were both numeric values, so convert the string to a numeric value.
- Concatenate them as strings, resulting in the string "123456".
- None of the above, but generate an error.
GFA-BASIC 32 copies VB and takes option 1; see Strings in Variants for more detail on this.
Accepted Variable Types for Variants
GFA-BASIC32 supports a large number of variable types that it is possible to enclose in a Variant, but by no means all. The supported types, which largely mirror those supported as standalone variable types (with the notable exception of Large Integers and Fixed Strings), are listed in detail below.
To discover which variable type a Variant contains, you can use TypeName to return a string or VarType to return a numerical representation of the type. There are also a range of type specific query functions, which are: IsArray, IsDate, IsEmpty, IsError, IsMissing, IsNothing, IsNull and IsObject; as well as the more general IsNumeric function.
Integers in Variants Show
Of the Integer Variable types supported by GFABASIC, the following are also supported within Variants:
TypeName | VarType | Value | Description |
"Boolean" | basBoolean | 11 | Boolean value (?)
|
"Byte" | basByte | 17 | Byte (|) Support for Byte varaibles is limited to arrays - see below. |
"Integer" | basLong basInt | 3 | Integer, Long, 32 Bit-Integer (%) If an integer is assigned to a Variant without conversion, the Variant will automatically be converted to this type of variable. |
"Short" | basShort | 2 | Short, Word, 16 Bit Integer (&) Support for Short variables is limited to arrays - see below. |
Of the above, Byte and Short variables are only supported by GFABASIC in Variant Safe-arrays (see Variant Arrays); all non-Array instances of this integer variable type passed to Variants are automatically converted to 32-bit Integers (even if CByte and CShort are used). If you specifically want a Byte or Short within a variant, create it as a one dimension array as shown below:
Dim a = Array(16) As Short // Create single-dimension array...
Debug TypeName(a(0)) // ...and show that is actually Short
a(0) = a(0) + 65532 // Add 65532 to test overflow...
Debug.Print a(0) // ...and show that it works (prints 12)
Debug.Show
To create a Boolean in a Variant, either dimension it with True or False or use the CBool function as shown below:
Local b = True, c = -1 // Set both variants to True
Debug TypeName(b), TypeName(c) // Show types: note c is an Integer
c = CBool(c) // Convert c to a Boolean...
Debug TypeName(b), TypeName(c) // ...and show this is done.
Debug.Show
There is no support for Large variables, which are converted into Doubles, and Card variables which are converted into 32-bit Integers; in addition, neither of these variable types can be used in a Variant Array (see below for more on them).
Similarly, a Handle is stored as a Long (VT_I4).
Floating Point Numbers in Variants Show
The default variable type for floating-point numbers is Double. To define a Variant as Single or Currency you need to use the CSng or CCur functions as shown below:
Local c = 12.58, d = 103.56788, s = -8.766
Debug TypeName(c), TypeName(d), TypeName(s)
c = CCur(c) : s = CSng(s)
Debug TypeName(c), TypeName(d), TypeName(s)
Debug.Show
Date and Time in Variants Show
TypeName | VarType | Value | Description |
"Date" | basDate | 7 | Date & Time |
Date and Time is handled in Variants in the same way as it is with a standard Date/Time variable type with just one exception:
the range for dates which can be displayed using GFABASIC's auto-conversion to string in Variant variables is January 1, 0100, to December 31, 9999. (For Date variables, this function only works from January 1, 1601.)
As with normal Date variables, you can use the IsDate function to determine if the Variant holds a valid date value; in addition, all Date/Time commands and functions work with Dates in Variants, as shown by this example below which works out how long it is until Christmas:
Dim xmas, rightnow, daysleft, hoursleft, minutesleft // As Variant by default
rightnow = Now // Now returns the current date/time.
xmas = DateSerial((Year(rightnow) + Iif(Month(rightnow) = 12 And Day(rightnow) > 24, 1, 0), 12, 25, ))
daysleft = Int(xmas - rightnow)
hoursleft = 24 - Hour(rightnow)
minutesleft = 60 - Minute(rightnow)
Print daysleft & " days, ";
Print hoursleft & " hours and ";
Print minutesleft & " minutes left until Christmas Day."
Strings in Variants Show
TypeName |
VarType |
Value |
Description |
"String" |
basVString |
8 |
String in Variant |
Strings are stored in the BSTR format within a variant (actually the BSTR is stored separately and referenced from the Variant) which is a version of 16-bit Unicode format, while GFA-BASIC 32 stores strings as 8-bit characters (GFA-BASIC 32 is based on 8-bit strings because Windows 95 didn't support Unicode (16 bit) functions). Therefore, each time a string is assigned to a Variant or vice versa, a conversion is performed automatically by GFA-BASIC32, as shown in the example below:
Local a$, vnt As Variant
a$ = "String" // The word 'String' is stored in 8-bit format in the GFA string data type
vnt = a$ // GFA converts the 8-bit string to BSTR 16-bit format and assigns it to a Variant
Print vnt // GFA then converts the value in the BSTR back to 8-bit format before 'Print'-ing to screen
Due to this automatic conversion performed by GFA-BASIC32, strings in a Variant can use most of the commands and functions designed for the 8-bit GFA-BASIC32 string data type, as shown below:
Local a$ = "String" , vnt As Variant = a$, vary(0 To 1) As Variant : vary(0) = "Hello"
Print Len(a$), Len(vnt), Len(vary(0)) // Only works if Variant is not in an array
Print Mid(a$, 2, 2), Mid(vnt, 2, 2), Mid(vary(0), 2, 2)
Print Mirror$(a$), Mirror$(vnt), Mirror$(vary(0))
Print Upper(a$), Upper(vnt), Upper(vary(0))
An example of a keyword that does not work with strings in a Variant is the Mid$ command (not to be confused with the Mid function shown above).
The GfaWinx.lg32 library contains three other functions that work with strings in variants: VstrLen, VStrLenB and VStrPtr.
Generally, storing and using strings in Variant variables poses few problems. However, sometimes the result of the + operator can be ambiguous when used with two Variant values. If both of the Variants contain numbers, the + operator performs addition. If both of the Variants contain strings, then the + operator performs string concatenation. But if one of the values is represented as a number and the other is represented as a string, the situation becomes more complicated. GFA-BASIC 32 first attempts to convert the string into a number. If the conversion is successful, the + operator adds the two values; if unsuccessful, it generates a Type mismatch error.
To make sure that concatenation occurs, regardless of the representation of the value in the variables, use the & operator. For example:
Form_Click
Sub Form_Click ()
Dim X, Y
X = "6"
Y = "7"
Print X + Y, X & Y // 67 67
X = 6
Print X + Y, X & Y // 13 67
End Sub
Empty, Null, Missing and Error Show
TypeName |
VarType |
Value |
Description |
"Empty" |
basEmpty |
0 |
Uninitialized
When a Variant is created, unlike other GFABASIC variables, no value is assigned and to show that a Variant is in this state, the Empty value was created. For more information, see Empty. This should not be confused with Null (see below) or Missing value (see Error below). |
"Null" |
basNull |
1 |
Null (no valid data)
The Null value must be deliberately set and signifies that the Variant contains no valid data, whether as a standard variable, a handle or an object. This is a useful programming tool and is explained further here. This should not be confused with either Empty (above) or Missing (see Error below). |
"Error" |
basError |
10 |
Error
The Error type can be used to indicate an error has occured or can be used to indicate that the Variant is of a custom or non-standard type, such as Missing. For more information, see here. |
The Missing value does not have its own variable type but is stored as an Error with value $80020004 as shown by the code below:
Local vnt As Variant = Missing
Print VarType(vnt), TypeName(vnt)
Print "Error Code for Missing: "; Hex(LPeek(V:vnt + 8))
Objects (and Variants) in Variants Show
TypeName |
VarType |
Value |
Description |
Object Name |
basObject |
9 |
OLE Automation object
This includes all Objects created by CreateObject and all GFABASIC32 Ocx controls and forms. |
Object Name |
basDataObject |
13 |
Non-OLE Automation object
This refers to the IUnknown interface of COM objects. |
"Variant" |
basVariant |
12 |
Variant (used only with arrays of Variants)
This has been added for completeness. If a Variant element of an array is queried by TypeName or VarType, the type of the Variant is usually returned; this type is only retuned to describe the Array in general IF the elements are Variants. |
Objects can be stored in Variant variables. This can be useful when you need to gracefully handle a variety of data types, including objects. For example, all the elements in an array must have the same data type. Setting the data type of an array to Variant allows you to store objects alongside other data types in an array.
Local a, b
OpenW 1 // Open a window
Set a = Win_1 // Assign the window to a ...
Debug TypeName(a) : Debug VarType(a) // ... and show the Typename and VarType
Set b = CreateObject("InternetExplorer.Application") // Create an instance of IE and assign to b...
Debug TypeName(b) : Debug VarType(b) // ... and show the Typename and VarType
Set b = Nothing // Clear the instance of IE
Debug.Show
The IsObject() function can be used to determine if a Variant holds an OCX or IDispatch type value.
To initialise a Variant for an Object, you can set the Variant to Nothing first. This will ensure that, even if no Object has yet been assigned, the Variant will still return TRUE if interrogated with IsObject.
Ocx TextBox txt = "TextBox", 10, 40, 100, 100
Local vnt As Variant
Debug "vnt = "; Iif(IsNothing(vnt), "Nothing", vnt)
Debug "Is vnt as Object? ... " & IsObject(vnt)
Set vnt = Nothing
Debug "vnt = "; Iif(IsNothing(vnt), "Nothing", vnt)
Debug "Is vnt as Object? ... " & IsObject(vnt)
Set vnt = txt
Debug "vnt = "; Iif(IsNothing(vnt), "Nothing", vnt)
Debug "Is vnt as Object? ... " & IsObject(vnt)
Debug.Show
Do : Sleep : Until IsNothing(Me)
Arrays in Variants Show
This section refers only to Variants of Array type (called form here on in as Variant Arrays) being stored in a Variant variable, not GFABASIC arrays containing Variants.
Arrays in Variants can be made up of either just one variable type or many as is shown in the code below:
Local a, b, n%
a = Array(CSng(12.2), 4, 5.5, CCur(14.44)) // Create a multi-type array
b = Array(1, 2, 3, 4) As Byte // Create an array of Byte variables
For n% = 0 To 3
Debug TypeName(a(n%)), TypeName(b(n%)) // Show the variable types of both arrays
Next n%
Debug.Show
When trying to identify the variable type of an array, it should be noted that the VarType function never returns the value for Array (8192) by itself. It is always added to some other value to indicate an array of a particular type. So in the example above, Array a would return 8192 (Array) + 12 (Variant - or different variable types) = 8204, and Array b would return 8192 (Array) + 17 (Byte) = 8209.
Similarly, Typename returns the name of the variable type followed by a pair of brackets '()' to indicate that it is an array, as shown by this example:
Local vnt As Variant = Array(1, 2, 3) As Int16
Print VarType(vnt), TypeName(vnt) // Prints 8194 and Short()
For more information on Variant Arrays, see here.
Variants passed to Procedures
Generally, Variants act in the same way as their GFABASIC counterparts when passed as parameters to Procedures, except in the following caase:
- Variants containing references to external data such as strings and objects are passed ByRef by default in all procedures rather than ByVal; hence, to pass Variants of this type by value only, ByVal must be specified.
- Variant Arrays can be passed ByVal or ByRef but an internal element of such can only be passed ByVal; attempting to pass them ByRef results in a 'Type mismatch' error.
Known Issues
There is an odd bug when passing Boolean values to an optional variant parameter in a function IF the function is called form a procedure containing a Gosub...Return structure - an Access Violation Error is returned for no apparent reason pointing to the line containing Return. This is illustrated by the code examples below:
trial
Procedure trial
Local enb As Boolean = True
VarTrial(10, enb)
GoSub Here
Return
Here:
Print "Go to here"
EndProcedure
Function VarTrial(a%, Optional v As Variant)
Print a, v
EndFunction
This is an error within the compiler and, currently, unfixable. If you experience this, simple workarounds are: use a different variable type in the calling procedure (anything but Boolean seems to work); or change the optional parameter in the called Function to type Boolean.[Reported by James Gaite, 11/03/2018]
{Created by James Gaite; Last updated: 15/01/2023 by James Gaite}