03 April 2024

Variant in an UDT?

Can you put a Variant in an user-defined type? It depends.

User defined type
A user-defined type is a data structure that can store multiple related variables of different types. To create a user-defined type, use the Type…End Type statement. Within the Type…End Type statement, list each element that is to contain a value, along with its data type.

Type SomeType
  Left As Long
  FirstName As String * 5
  Ages (1 To 5) As Byte
End Type

This code fragment shows how to define an user-defined type, it contains a 32-bit integer, a fixed string occupying 5 characters (bytes) and a 1-dimensional array of type Byte, also occupying 5 bytes. In total (theoretically) the user-defined type occupies 14 bytes. To get the actual size of the UDT you would use the SizeOf(SomeType) function, which reports a size of 16 bytes. The member Left is located at offset 0, FirstName at offset 4, and Ages starts at offset 9. Because the size of the structure is a multiple of 4, due to the default Packed 4 setting, the type is padded with empty bytes to fill it up until it is a multiple of 4. (A Packed 1 setting would return a size of 17.)

It is important to note that all data is stored inside the UDT. GB32 does not allow data to be stored outside the boundaries of the UDT. So, any Ocx type, Object type, or String type are forbidden, because they are pointer types that store their data somewhere else. (If it would be allowed, a Type with only one string member would have a size of 4, because only the 32-bits pointer to the string data would be reserved.) Because pointer types aren't allowed as Type members, GB32 does not call an 'UDT-destructor' when the UDT variable goes out of scope as it does with a String or Object variable as showed in the following sample:

Proc LocalStr()
  Local String s = "Hello"
  // ...
EndProc         // Destroys the string s

Now, suppose a dynamic String would be allowed in an UDT. When the UDT goes out of scope, GB32 would need to free the memory allocated by the string, which it cannot. In fact, GB32 does nothing at all when an UDT goes out of scope. A local UDT variable is located on the stack and GB32 simply restores the stack to its state it was before invoking the procedure.

Variant in UDT
Because of these restrictions, people are tempted to store a string in a Variant Type member, simply because GB32 does allow this. Then, when the Variant is then assigned a string (or an object) GB32 does not complain. However, when you assign a string to a Variant, the string is converted to a BSTR type and stored somewhere in COM-system memory (using SysAllocString*() APIs). Because GB32 does not release the Variant in the UDT, this memory is never freed. In addition, since the string memory isn't located on the program's heap, it won't even be released when the program is ended. It remains in COM-memory forever.

You can use a Variant in and UDT, but only to store simple numeric types. Dynamic data types (String and Object) are never released.