February 02, 2024

Memory layout of VBA variable types

Last modified: Fri Mar 15 09:58:23 UTC+0100 2024 © Attila Tarpai

Memory layout of VBA variable types

In VBA the Dim statement declares variables and allocates memory space. This is a quick overview of the main variable types and memory layout.

Some differences between 32- and 64-bit VBA:

  • In 64-bit VBA every pointer is 8 bytes - in 32-bit 4 bytes
  • LongLong is only valid in 64-bit VBA together with its type-character (^)
  • LongPtr is not a real datatype: compiles to Long or LongLong on 32/64-bit VBA
  • 64-bit Variant is larger and 64-bit SAFEARRAY structure is larger

Scalar variable types

  • The native or primitive datatypes for the CPU/FPU: integers and floating-point numbers IEEE 754.
  • Boolean is Integer with value restrictions.
  • Date and Currency are composed datatype structures based on underlying COM/Automation.
LongLong (signed 64-bit integer)   +--+--+--+--+--+--+--+--+
Double (64-bit floating-point)     |        8 bytes        |
Date and Currency                  +--+--+--+--+--+--+--+--+

                                   +--+--+--+--+
Long (signed 32-bit integer)       |  4 bytes  |
Single (32-bit floating-point)     +--+--+--+--+

                                   +--+--+
Integer (signed 16-bit integer)    |  2  |
Boolean (0 = False, -1 = True)     +--+--+

                                   +--+
Byte (unsigned 8-bit integer)      | 1|
                                   +--+

Declaring any of these datatypes with Dim allocates (aligned) n-bytes and initializes the memory with zeroes. So the default value zero = False for Boolean, #12:00:00 AM# for Date and 0 for Currency. It is also the default value for Functions of these types.

Pointer variable types

These VBA variable types store pointers:

  • String variable
  • Object types (all Object, Collection, Class, etc.)
  • Dynamic Array variable
0           4           8
+-----------+-----------+
|  pointer      64-bit  |
+-----------+-----------+

 Dim str as String        ---> to BSTR STRUCTURE

 Dim o as Object          ---> to IDispatch

 Dim varr()               ---> to SAFEARRAY STRUCTURE

After Dim and for functions returning these types the default value is zero, a null-ptr:

  • StrPtr() is zero for String
  • ObjPtr() is zero = Nothing for Object types
  • No such thing for Dynamic Arrays

VBA Variant

Speciality of VBA/COM is the Variant data type. Variant can store all variable types, including arrays. Conversion is implicite. It is a structure of tagVARIANT in Oaidl.h, which compiles to different sizes on 32- or 64-bit VBA and operated by COM routines:

0     2                 8                       16
+--+--+-----+-----+-----+-----------------------+
|TYPE |    reserved     |     8-byte data       |   32-bit VBA Variant: 16 bytes
+--+--+-----+-----+-----+-----------------------+

0     2                 8                       16                      24
+--+--+-----+-----+-----+-----------------------+-----------------------+
|TYPE |    reserved     |     8-byte data       |                       |   64-bit VBA Variant: 24 bytes
+--+--+-----+-----+-----+-----------------------+-----------------------+

Every Dim and each elements of Variant Arrays allocate 16- or 24 bytes.
The reason for larger size is this structure in tagVARIANT for UDT-s, when it stores two 64-bit pointers (for vbUserDefinedType Variant):

struct {
          PVOID       pvRecord;
          IRecordInfo *pRecInfo;
        } __VARIANT_NAME_4;

But Variant storing UDT-s is possible only for Types from external libraries, f. ex. mscorlib. User Types defined in a standard VBA Module or Class cannot be converted to Variant (VBA doesn't create an instance for IRecordInfo). This means that the last qword of all 64-bit Variant in normal VBA code is usually zero and unused.

Variant type-mapping and memory layout:

enum VARENUM      VbVarType       VarType()   |TYPE |    RESERVED     |        DATA           |    64-bit VBA only    |

VT_EMPTY=0        vbEmpty             0       |00 00|00 00 00 00 00 00|00 00 00 00 00 00 00 00|00 00 00 00 00 00 00 00|
VT_NULL=1         vbNull              1       |01 00|00 00 00 00 00 00|00 00 00 00 00 00 00 00|00 00 00 00 00 00 00 00|
VT_I2=2           vbInteger           2       |02 00|00 00 00 00 00 00|XX XX 00 00 00 00 00 00|00 00 00 00 00 00 00 00|
VT_I4=3           vbLong              3       |03 00|00 00 00 00 00 00|XX XX XX XX 00 00 00 00|00 00 00 00 00 00 00 00|
VT_R4=4           vbSingle            4       |04 00|00 00 00 00 00 00|XX XX XX XX 00 00 00 00|00 00 00 00 00 00 00 00|
VT_R8=5           vbDouble            5       |05 00|00 00 00 00 00 00|XX XX XX XX XX XX XX XX|00 00 00 00 00 00 00 00|
VT_CY=6           vbCurrency          6       |06 00|00 00 00 00 00 00|XX XX XX XX XX XX XX XX|00 00 00 00 00 00 00 00|
VT_DATE=7         vbDate              7       |07 00|00 00 00 00 00 00|XX XX XX XX XX XX XX XX|00 00 00 00 00 00 00 00|
VT_BSTR=8         vbString            8       |08 00|00 00 00 00 00 00|XX XX XX XX 64 64 64 64|00 00 00 00 00 00 00 00|
VT_DISPATCH=9     vbObject            9       |09 00|00 00 00 00 00 00|XX XX XX XX 64 64 64 64|00 00 00 00 00 00 00 00|
VT_ERROR=10       vbError            10       |0A 00|00 00 00 00 00 00|XX XX XX XX 00 00 00 00|00 00 00 00 00 00 00 00|
VT_BOOL=11        vbBoolean          11       |0B 00|00 00 00 00 00 00|XX XX 00 00 00 00 00 00|00 00 00 00 00 00 00 00|
VT_VARIANT=12     vbVariant          12
VT_UNKNOWN=13     vbDataObject       13
VT_DECIMAL=14     vbDecimal          14       |0E 00|XX XX XX XX XX XX|XX XX XX XX XX XX XX XX|00 00 00 00 00 00 00 00|
VT_UI1=17         vbByte             17       |11 00|00 00 00 00 00 00|XX 00 00 00 00 00 00 00|00 00 00 00 00 00 00 00|
VT_I8=20          vbLongLong         20       |14 00|00 00 00 00 00 00|XX XX XX XX XX XX XX XX|00 00 00 00 00 00 00 00|
VT_RECORD=36      vbUserDefinedType  36       |24 00|00 00 00 00 00 00|XX XX XX XX XX XX XX XX|64 64 64 64 64 64 64 64|
VT_ARRAY=0x2000   vbArray            8192     |XX 20|00 00 00 00 00 00|XX XX XX XX 64 64 64 64|00 00 00 00 00 00 00 00|
VT_BYREF=0x4000   -                  -        |XX 40|00 00 00 00 00 00|XX XX XX XX 64 64 64 64|00 00 00 00 00 00 00 00|

The default value for Variant after Dim is all zero, vbEmpty.

  • VT_ARRAY = vbArray is a flag. These are Variants of array type and store a Pointer to SAFEARRAY structure (PSA).
  • VT_BYREF is a flag used by VBA to pass other data types to procedures as ByRef Variant. The conversion is implicite. VT_BYREF stores a pointer and its operation is invisible for the programmer: callee automatically dereferences the pointer each time of access (see details in Calling mechanisms).
  • vbVariant is used only together with the vbArray flag (Variant of array of Variant)
  • vbString, vbArray and vbObject Variant stores a pointer to BSTR structure, SAFEARRAY structure and IDispatch Interface respectively.

These data types live only inside a Variant by conversion and are handled by COM routines:

  • vbDecimal by CDec()
  • vbError by CVErr()

CDec(number) returns Variant/Decimal. DECIMAL maps to the Variant structure and uses the reserved bytes for a 14-byte structure:

+---+---+-------+-------+-------+-------------------------------+
|VARTYPE|      reserved         |        8-byte data            |  32-bit VBA Variant
+---+---+-------+-------+-------+-------------------------------+

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|  res  |sc | S |     Hi32      |     Lo32      |     Mid32     |  DECIMAL
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

typedef struct tagDEC {
   WORD wReserved;
   BYTE scale;
   BYTE sign;
   ULONG Hi32;
   ULONGLONG Lo64;
 } DECIMAL;

CVErr(code) returns Variant/Error and store a 32-bit HRESULT value (see details in Error handling). The argument is 32-bit Long with acceptable range of 0-65535:

CVErr(0)         --->  0A00-000000000000-0000000000000000-0000000000000000    64-bit Variant/Error (S=0 success)

CVErr(1..65535)  --->  0A00-000000000000-xxxx0A8000000000-0000000000000000    64-bit Variant/Error (S=1 failure)

Passing variables and expressions as Byref Variant

Any variable, expression can be passed as Byref Variant and it is very common for several built-in functions as well.

Here Sub (v) receives one pointer, an address of a Variant and defers the actual value and type at run-time:

Sub (v)
                                                                                Pass:
                       _______________
               +--->  |_xxxx____xx____|  Variant variable                       Dim myVar As Variant
               |       _______________
               |      |_0800___->BSTR_|  Variant/String variable                Dim myStr As Variant
               |       _______________
               |      |_xx20___->SA___|  Variant/Array variable                 Dim myArr() As Variant
               |
               |
               |      or TEMP Variant made by caller:
               |       _______________
               +--->  |_0200____100___|  Variant/Integer                        100
               |       _______________
               |      |_0300___60000__|  Variant/Long                           60000
               |       _______________
 _________     |      |_0800___->BSTR_|  Variant/String to BSTR                 "hi"
|_________|->--+       _______________
     v         |      |_xx20___->SA___|  Variant/Array to SAFEARRAY             Array(1,2,3)
 address of    |
  Variant      |
               |      or for Non-Variant variable
               |      using TEMP VT_BYREF-Variant:
               |       _______________             ____
               +--->  |_xx40____ptr___|  ------>  |____|  Integer               Dim I As Integer
                       VT_BYREF-Variant            _________
                                                  |_________|  Long             Dim L As Long
                                                   _________
                                                  |__->BSTR_|  String           Dim str As String
                            ^                      _________
                            |                     |__->SA___|  Array            Dim varr()
                            |
                         VarPtr(v)

Note that VarPtr(v) will always return the address in v itself. Something to keep in mind when Non-Variant variables are passed.

No comments:

Post a Comment