Table of Contents

2.5.2.1.3 User-Defined Type (UDT) Values

An instance of a UDT is a VBUserDefinedTypeValue.

The data type of a UDT value is defined by the UDT declaration of its declared type; see RD-VBAL §5.2.3 Module Declarations.

Location Identity

Like an object value, the underlying value of a UDT value is a unique addressable ID: a location in the heap.

A UDT has location identity. A UDT is never copied by value at the level of its underlying value (its addressable ID).

👉 UDT values must be passed by reference (ByRef). See RD-VBAL §5.3.1.5 Parameter Lists and RD-VBAL §5.3.1.11 Procedure Invocation Argument Processing.

Field Store

A UDT value carries a field store: one cell per declared field, in declaration order.

A UDT value carries its field store in the same way as an array value carries its element block (RD-VBAL §2.5.2.1.2 Array Values), and for the same reason: a UDT's real data is its fields, which a scalar managed value has nowhere to hold.

The declaration order of the field store is normative rather than incidental. It is:

Each field-store cell starts at its field's declared type's own default value, because VBA gives a UDT no initializer.

A UDT field is not an addressable symbol the way a variable is: a Let assignment to a UDT field writes the field's cell. See RD-VBAL §5.4.3.8 Let Statement.

Storage

Like an array, a UDT value is location-identified. Three pieces preserve a UDT value through storage:

Piece Role
VBRuntimeUserDefinedTypeValue boxing Boxes a UDT value when it is stored, into the handle a symbol's storage allocation reserves.
VBUserDefinedType.CreateValue(IBindingHandle) unboxing Unboxes the stored UDT value back out unchanged, rather than rebuilding one with default fields.
The VBUserDefinedTypeValue copy constructor Gives every with-derived copy a field store of its own.

Three kinds of value are location-identified this way: an array, a Variant, and a UDT (VBRuntimeUserDefinedTypeValue).

Reconstructing a UDT value from a handle, instead of unboxing it, would return a UDT with default fields, whatever had been assigned to it.

VBRuntimeUserDefinedTypeValue is a plain (non-record) wrapper, for the same reason VBRuntimeArrayValue is: structural equality would recurse into the value's own equality through the boxed value.

Copies

A copy of a UDT value gets a field store of its own. The copy is deep through a nested UDT. This is because VBA copies a UDT on assignment; see RD-VBAL §5.4.3.8 Let Statement.

Sizes

🎯 A UDT has two sizes: an in-memory size and an unpadded size. MS-VBAL §6.1.2.11 Strings distinguishes the two sizes where it defines Len and LenB (MS-VBAL §6.1.2.11.1.22 Len / LenB).

Size Reported by MS-VBAL definition Layout
In-memory LenB; VBUserDefinedTypeValue.Size LenB returns "the in-memory size, including any implementation-specific padding between elements". Each field is aligned to its own natural boundary. The type is padded up to the strictest boundary any field asked for.
Unpadded Len Len "returns the size as it will be written to the file". 👉 As MS-VBA reports it: the same memory widths added up with no padding at all, a fixed-length String counted as its characters. Not what Put writes: a variable-length String counts as its pointer.

Strings.Len and Strings.LenB make both sizes of a UDT observable to a program; see RD-VBAL §6.1.2.11 Strings. Put and Get are described in RD-VBAL §5.4.5.11 Put Statement and RD-VBAL §5.4.5.12 Get Statement.

In-Memory Layout

MS-VBAL calls the UDT in-memory padding "implementation-specific". RD-VBA chooses MS-VBA's padding, because a program measures a UDT's in-memory size with LenB and relies on it.

VBUserDefinedTypeLayout represents the in-memory (LenB) size of a UDT:

  • It holds MS-VBA's own natural alignment (padding) rule in one place.
  • It holds each field's offset.
  • VBUserDefinedTypeValue.Size reports the in-memory size of a UDT: its VBUserDefinedTypeLayout size.

Memory Widths and File Widths

👉 Memory widths of UDT fields are not file widths.

Field In memory (LenB) Len In a record (Put)
Variable-length String A pointer A pointer Its characters
Fixed-length String Unicode Its characters ANSI

The MS-VBAL warning that "Len might not be able to determine the actual number of storage bytes required when used with variable-length strings in user-defined data types" is about a record whose members include a variable-length String: MS-VBA's Len, and RD-VBA's, count the pointer and not the characters a Put writes.

Pointer Width

The pointer width of a UDT layout is a parameter of VBUserDefinedTypeLayout, and it defaults to 32-bit:

  • On a 64-bit host, a pointer field (a variable-length String, an object, a resizable array) and a LongPtr field are 8 bytes, and a Variant field is 24 instead of 16, as MS-VBA lays them out (recorded in Excel, 64-bit).
  • Len and LenB lay a record out for the environment's own pointer width.
Note

Not implemented. VBUserDefinedTypeValue.Size and a record's memory image still lay a UDT out for a 32-bit pointer.


⏮️ RD-VBAL §2.5.2.1.2 Array Values | ⏭️ RD-VBAL §2.5.2.1.4 Object Values