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 toLong
orLongLong
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
andCurrency
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 thevbArray
flag (Variant of array of Variant) -
vbString
,vbArray
andvbObject
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