Variant objects

Variant is a Javascript system class representing a native COM VARIANT value.

The COM VARIANT type is essentially the native-code (C/C++) representation of the Visual Basic "ANY" type, which can hold any VB value. VARIANT is so named because the type of data stored in a particular VARIANT varies at run-time. Any type of value can be stored there, and the type of value in a particular VARIANT can be changed dynamically each time a new value is stored.

A VARIANT variable can hold any of a large number of native types, as listed in the type code table below. A VARIANT is tagged with the specific type of value it's currently holding, so a caller can always determine how to properly interpret the value. Only one type of value is stored in a given VARIANT at any one time. Setting it to a new value replaces the old value and can change it to a new type.

VARIANTs are widely used in Windows COM interfaces, especially in scripting-oriented interfaces designed to interoperate with Visual Basic and ActiveX control hosts. VARIANT is implemented in C/C++ code as a struct type, so in principle, you could define the struct yourself in terms of the general-purpose native type mechanisms provided by the DLL Importer. But you wouldn't want to, because the raw VARIANT C struct is complex, tedious to work with, and demands careful handling to manage its memory properly. Microsoft really didn't design it to be human-programmer-friendly; it was designed only for internal use by computer language processors. As such, PinballY's Javascript provides the built-in "Variant" type that makes it much easier to work with VARIANTs when you need them for COM calls or other purposes. The Javascript Variant object provides properties for convenient conversion between Javascript and native types, and it handles the required memory management details automatically.

The Variant type is really only intended for use with COM and other native-code APIs that are written in terms of VARIANTs. You probably won't want to use Variants in purely Javascript code, as Variant does essentially the same thing as Javascript's own built-in type system, but less efficiently and less conveniently, since it's an external object type.

Creation

You can create a Variant value with "new", just like any other type.

let v = new Variant();

Value access

A Variant is a Javascript object, so the underlying value that it stores has to be accessed through properties. The main property for accessing the internal value is the .value property.

message("v = " + v.value); v.value = "String Value"; v.value = 100;

When you read the .value property, it translates the native type stored in the internal VARIANT struct to the closest Javascript equivalent. Integer and floating point types are returned as Javascript number values, for example, and native string types are returned as Javascript strings. Similarly, when you assign a new Javascript value to the .value property, the value is translated to a native type and stored in the internal VARIANT, with the VARIANT's type tag automatically set to the new native type.

Variant objects also have a .vt property that lets you get or set the current type indicator. ("vt" stands for "variant type"; it's the name of the element from the C struct, so we use the same name for the sake of people used to the C version.) This lets you determine the native type being used for the current value. You can also assign a value to .vt, which has the effect of deleting any prior value in the native VARIANT struct and replacing it with a "default" value of the new type, usually zero for numeric types and null or empty for other types. Some COM interfaces that return values through a VARIANT expect the caller to set the type tag of the VARIANT to the desired return type before making the call, which you can do by setting the .vt property.

Arithmetic

A Variant is a Javascript object, so you can't use the normal Javascript arithmetic operators directly on a Variant, even if the Variant happens to contain a numeric value. Instead, you'll have to retrieve the contained value as a Javascript number using the .value method, perform the desired arithmetic operations on the Javascript number, and store the result back in the Variant by assigning it to the .value property or to one of the type-specific assignment properties (which we'll come to shortly).

Default type assignments

When you assign a value to a Variant object through the .value method, the native VARIANT type is automatically inferred from the Javascript type assigned. The type mappings are shown in the table below.

Javascript Type Assigned to .valueResulting Variant TypeExample
NumberVT_R8 (double-precision "real number")v.value = 100
StringVT_BSTR (Basic string)v.value = "string"
BooleanVT_BOOL (Variant Boolean)v.value = true
DateVT_DATE (Variant Date)v.value = Date.now()
undefinedVT_EMPTY (Empty)v.value = undefined
nullVT_NULL (Null)v.value = null

Setting a specific type

You'll notice from the full table of type codes below that VARIANT has many more sub-types than Javascript variables do. For example, VARIANT includes all of the native integer and floating point types common on Intel hardware, plus some special numeric types used in Visual Basic and the Windows OLE subsystem (Currency, Decimal), whereas Javascript only has the one numeric type "Number".

Some COM interfaces that take VARIANT parameters require specific subtypes to be stored in the VARIANT arguments, and will fail with an error if the wrong subtype is used. The defaults listed above therefore won't always work for all callees. While the .value property only lets you set the types listed above, the Variant object has other type-specific properties that allow setting an exact subtype. For the full list, see the type code table below. The "Set Via" column shows the property used to set each specific type. For example:

v.bVal = 100; // set the type to VT_UI1 (Byte) and set the byte value to 100 v.fltVal = 123.456; // set the type to VT_R4 (Float) and set the float value to 123.456

Note that each type-specific property sets the value and the internal type tag in the native VARIANT. It's not necessary to set the type code separately when assigning through these properties. If you read the .vt property after making one of these assignments, it will yield the VT_xxx value for the type of the last assigned value.

You can also read values from the "Set Via" properties (e.g., let x = v.bVal). However, these properties will only work when the Variant contains the corresponding type; they'll throw an error otherwise.

v.iVal = 2000; // set the type to VT_I2 (Short) let x = x.iVal; // okay, v contains an iVal; reads back the 2000 we just stored let y = v.bVal; // THROWS AN ERROR: v does not contain a VT_UI1 (Byte) value

Passing VARIANT* arguments to COM calls

Nearly all COM interfaces that work with VARIANT values require pointer to VARIANT parameters, written in C function prototypes as VARIANT*.

dllImport.define(` interface IPropertyBag "55272A00-42CB-11CE-8135-00AA004BB851" : IUnknown { HRESULT Read(LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog); HRESULT Write(LPCOLESTR pszPropName, VARIANT *pVar); };`);

When calling a function that takes a VARIANT* parameter, you can simply pass a Variant value. The DLL importer will automatically pass the address of the native VARIANT struct contained in the Javascript Variant object.

let v = new Variant(); propBag.Read("Name", v, null);

Type codes

The native VARIANT's "vt" struct element is an integer value that indicates the current type of value stored in the struct. The type names from the Windows SDK headers are predefined as Javascript const values in the PinballY system files, so you can use the same names you'd see in C++ examples.

The table below lists only the type codes that are valid with Variants. The Windows headers define a number of other type codes that can be used in other contexts, so you'll find additional codes in the system files, but only the codes listed below can be used with Variants.

The Set Via column shows how to explicitly store a value for each type. The type-specific property names match the member names in the native C VARIANT struct, to make it easier to translate any C/C++ COM coding examples you might refer to.

Namevt ValueDescription.value reads asSet via
VT_EMPTY0Empty; similar to Javascript undefinedundefinedv.value = undefined
VT_NULL1SQL style NULL; similar to Javascript nullnullv.value = null
VT_I222-byte (16-bit) signed int (C "__int16")Numberv.iVal = Number
VT_I434-byte (32-bit) signed long int (C "__int32")Numberv.lVal = Number
VT_R444-byte real (C "float")Numberv.fltVal = Number
VT_R858-byte real (C "double")Numberv.dblVal = Number
VT_CY6Currency (96-bit fixed point, scaled by 10000)Numberv.cyVal = Number
VT_DATE7VARIANT DateDatev.date = Date
VT_BSTR8BSTR (Basic String)Stringv.bstrVal = String
VT_DISPATCH9IDispatch* (COM core scripting interface)COMPointerv.value = COMPointer
VT_ERROR1032-bit signed error code (SCODE)Numberv.scode = number
VT_BOOL11VARIANT BOOL: 16-bit signed int, 0=false, -1=trueBooleanv.boolVal = Boolean
VT_VARIANT12VARIANT* (pointer to VARIANT); Requires VT_BYREFNativeObjectv.value = Variant
VT_UNKNOWN13IUnknown* (root COM interface)COMPointerv.value = COMPointer
VT_DECIMAL1416-byte floating point with decimal scalingNumberv.decVal = Number
VT_I1161-byte (8-bit) signed byte (C "char")Numberv.cVal = Number
VT_UI1171-byte (8-bit) unsigned byte (C "unsigned char")Numberv.bVal = Number
VT_UI2182-byte (16-bit) unsigned int (C "unsigned __int16")Numberv.uiVal = Number
VT_UI4194-byte (32-bit) unsigned long int (C "unsigned __int32"Numberv.ulVal = Number
VT_INT22Machine signed int type (C "int"; same as __int32)Numberv.intVal = Number
VT_UINT23Machine unsigned int type (C "unsigned int"; same as unsigned __int32)Numberv.uintVal = Number
VT_RECORD36User-defined struct typeNot supportedNot supported
VT_ARRAY0x2000Array (bit flag: combines with element type code)Not supportedNot supported
VT_BYREF0x4000Pointer (bit flag: combines with element type code)NativeObjectv.value = NativePointer

Properties

variant.value: Gets or sets the value stored in the native VARIANT. Reading .value translates the stored native value to the closest Javascript equivalent and returns the value. See the table of types above for the .value results for the native types.

Setting .value to a new value translates the Javascript value to the closest native equivalent. Numbers are stored as variable "double" values, which are identical to Javascript's internal number format. Strings are stored as BSTR (Basic string) values.

variant.vt: Gets or sets the internal "vt" field of the native VARIANT struct, which indicates the current type of the value stored in the VARIANT struct. Reading .vt returns the current type code, which is one of the VT_xxx values defined above.

Setting .vt to a new value clears any value currently in the native VARIANT, and updates the internal "vt" field in the native struct to the new type code. The old value is automatically cleared when you set the "vt" property to avoid any chance that the old value would be misinterpreted, which could corrupt memory and crash the program in some cases. So be aware that setting "vt" will have the side effect of changing the "value" result as well.

Type-specific getter/setter properties

In addition to the properties listed above, Variant has a property for each of the native VARIANT subtypes, such as .iVal for the "short int" type and .dblVal for the "double precision float" value. These are listed in the type code table above, in the Set Via column.

When you assign a value to one of the type-specific properties, it sets the internal type tag field ("vt") in the VARIANT object to the corresponding VT_xxx code, converts the assigned value from the Javascript format to the corresponding native type format, and stores the converted native type in the VARIANT's internal data field.

The per-type properties are mostly for setting the VARIANT value, but you can also read from these properties, as long as the stored type matches the property. An error is thrown if you try to read a type-specific property and the underlying VARIANT object contains a different type. When you read the type-specific property, the result is a Javascript representation of the native value. (The native value in the VARIANT isn't affected. It remains stored in the native representation. The returned Javascript value is just a copy.) It's generally easier to read from the .value property instead, since that automatically converts from whichever native type the VARIANT stores, using the automatic conversions listed in the type code table under the ".value Reads As" column.

Limitations

Some VARIANT subtypes are unsupported or only partially supported: