Pointers and memory for Variant variables

In the Component Object Model (COM) Automation framework, the VARIANT structure provides a wrapper for passing around any type of data, and a suite of manipulation functions facilitate using the VARIANT as a platform-level dynamically-typed variable. I say platform-level because the structures, enumerations, and functions that implement VARIANTs exist at the Windows API level. Any language — including those that are not dynamically typed — can use the API to accomplish something like dynamic types.

VBA does provide dynamically typed variables, and calls them Variants, just like the supporting structures in the COM API. When writing VBA code you never have to call the API functions like VarAdd or VarXor. The compiler and runtime do it for you behind the scenes. But when you pop the hood and start directly working with the bits and bytes of Variant variables and pointers it’s important to know what you’re really dealing with — namely, a COM VARIANT structure.

The details of the layout of the 16 bytes in the VARIANT structure are covered in detail in What’s in a variable. The full code of the memory utility functions used in the examples in this post are included in Scalar Variables and Pointers in Depth.

Variants that contain numeric scalars, strings, or arrays

Everything described in the my previous posts about pointers to numeric scalar variables, strings, and arrays applies to the same data types stored in Variants. The only difference is that the content stored at the pointer to a statically typed variable of these types is instead found 8 bytes after the pointer to the start of the Variant. The first 8 bytes of the Variant are the flag that indicate what type of data the Variant is currently storing, and then some unused space. I’ll try to clarify this with a few examples:

Example 1: Static Long vs Variant Long



Here’s what the variable table looks like:

Name Type Address
myLong Long 0x0031F070
myVar Variant 0x0031F060

The layout for the statically-typed Long variable is simple:

Address 0 1 2 3
0x0031F07x 4E 61 BC 00
= 0x00BC614E = 12,345,67810

The layout of the variant is almost as simple. The first two bytes are the flag, the next 6 bytes are empty, and then bytes 8-11 have the actual content — the four bytes containing the 32-bit integer.

Address 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x0031F06x 03 00 00 00 00 00 00 00 4E 61 BC 00 00 00 00 00
03 00
= 0x0003 ⇒ VT_I4 ⇒ 4-byte signed integer (see VARENUM)
00 00
= [unused]
00 00
= [unused]
00 00
= [unused]
4E 61 BC 00
= 0x004E61BC ⇒ 12,345,67810
00 00 00 00
= [unused]

We can see that the Long value is stored starting byte offset 8. The “real” content of a variant always starts at byte offset 8 (except for a DECIMAL…covered later). Because of this, we can do some pointer arithmetic and get a pointer directly to the Long inside the Variant. This is what we do in the following line of code:

Now ptrToVarLong is in fact a pointer to a Long. That Long just happens to be inside a Variant. The value we get by directly reading from this address (the number 12,345,678) is the exact same as the value we get from directly reading from the address of the variable actually declared as a Long, which also has the value 12,345,678.

Example 2: Static String vs Variant String



For brevity I won’t break out all the variables and byte layout for this example. Just note that adding 8 to the Variant pointer in this case yields a pointer that behaves exactly like a pointer to a statically typed String variable. In other words, the content of the String variable is a pointer to a BSTR structure. The typed content of the Variant String is also a pointer to a BSTR structure. But the typed (String) content of the Variant variable starts in the 8th byte of the Variant as a whole. This parallels the previous example, where the content (a 32-bit integer literal) of a statically type Long was the same as the content of a Variant Long, except that the typed (Long) content of the Variant started at the 8th byte of the Variant as a whole.

Example 3: Static Array vs Variant Array



Once again I won’t explain each point in the example code and output. For a much more thorough discussion of pointers to arrays, see Array Variables and Pointers in Depth. The point here is that getting the content of the Variant/Array by stepping 8 bytes forward into the VARIANT structure gives us a pointer (to a SAFEARRAY) that behaves exactly like a pointer obtained directly by calling VarPtrArray on a statically-typed array variable.

More on variant pointers…

We’re not done with pointers to Variants yet! Two categories remain: Decimals and ByRef Variants. Those topics are big enough for their own posts, which will follow in the near future.