UDT's

"User-Defined Types" or "Unidentified DWord Terminology"


Contents:

1.  Understanding UDT structure alignment
2.  "SizeOf"  - using Len or LenB ?
3.  Nested UDT's
4.  Strings and Fixed Length strings
5.  Tips and terminology


1.  Understanding UDT structure alignment

A UDT in Visual Basic is similar to a C structure.  In VB, a UDT might look something like this:

Type MyUdt
     lngSize as Long
     strName as String
     options(5) as Byte
End Type

The above UDT has three members, ( a long, a string and an array of bytes).  To understand how VB stores these members inside the UDT it is easiest if you just think of the UDT as a consecutive series of bytes.  Each member of the UDT must have it's first byte at an alignment boundary based on that member's "natural size".  This internal alignment of the members of a UDT is sometimes also referred to as internal padding.

The following table from KB article Q171583 lists the alignment or "natural size" of different members of a UDT.

Type         Alignment   Size

Byte          1 byte     1 byte
String * n    1 byte     2 bytes per UNICODE character
Integer       2 bytes    2 bytes
Boolean       2 bytes    2 bytes
String        4 bytes    4 byte pointer - UNICODE data not in structure
Long          4 bytes    4 bytes
Single        4 bytes    4 bytes
Double        4 bytes    8 bytes
Currency      4 bytes    8 bytes
Date          4 bytes    8 bytes
Variant       4 bytes    16 bytes - may point to data not in structure
Object        4 bytes    4 byte pointer - object not in structure

So, from the table above, we have the alignment size of a long or a double (etc) as being 4 bytes.  That means the first byte of that member must align in the UDT structure at a four byte boundary. 

In addition to this internal padding before members of a UDT, Visual Basic also applies external padding to the UDT structure to ensure that an array of the UDT would have all the members of all the UDTs in the array align at their "natural size" boundaries.  What this equates to is: the size of the UDT must be a multiple of the largest "natural size" of it's members.

The following examples should help make this clear.

UDT size and alignment
Type myUDT
     a as Long
     b as String
     c as Long
End Type
size of structure = 12 bytes
 a  a  a  a  b  b  b  b  c  c  c  c 
Type myUDT
     a as byte
     b as Long
     c as byte
End Type
size of structure = 12 bytes
 a  .  .  .  b  b  b  b  c  .  .  . 
Type myUDT
     a as byte
     b as Boolean
     c as byte
End Type
size of structure = 6 bytes
 a  .  b  b  c  . 
Type myUDT
     a as byte
     b(0 to 2) as byte
     c as byte
End Type
size of structure = 5 bytes
 a  b  b  b  c 
the cells marked with  " . "  indicate padding

 

2.  "SizeOf"  - using Len or LenB ?

Often when you are working with UDTs for use with API functions you need to specify the size of the structure.  You can calculate it yourself or if you have VB6 (or later ?) you can use the LenB function to get the size of the structure. 

If on the other hand, you are working with reading or writing to a file, use the Len function as it returns the sum of the size of all the members of the structure.

To summarise:

LenB  - returns the size of the structure including internal and external padding. 

Len - returns the sum of the sizes of the members of the structure.  Does NOT include any padding.

Note: in VB5 LenB only returned the size of the members of the structure and the internal padding, but did not include external padding. Using the four examples above LenB in VB5 would return 12, 9, 5,and  5 , which is really as good as useless when trying to use in an API function.

To test the size of your udt in VB5 you can declare it as an array nested inside another UDT. eg:

Type TestUDT
       t(0) as myUDT
End Type

When you use LenB to get the size of the TestUDT, it will return the correct size for the myUDT structure.

Note 2:  If your UDT contains fixed length strings the size LenB calculates is for the Unicode string (2 bytes per character). - this may not be appropiate for your use in a Ansi API function.

 

3. Nested UDTs

When a member of a UDT is another UDT, the child UDT will be aligned according to it's "natural size" which is the largest of the "natural sizes" of its members.  For example:

Type myUDT
     a as byte
     b as Long
     c as byte
End Type
size of structure = 12 bytes
 a  .  .  .  b  b  b  b  c  .  .  . 
"natural size" of UDT is 4 bytes
Type myUDT2
     d as byte
     e as myUDT
     f as byte
End Type
size of structure = 20 bytes
 d  .  .  .  a  .  .  .  b  b  b  b  c  .  .  .  f  .  .  . 

Warning !  In VB5 the nested UDT (myUDT in the above example), is inserted at an alignment boundary according to it's "natural size", however it does not include any external padding bytes, so the output for the above example would be:

size of structure = 16 bytes
 d  .  .  .  a  .  .  .  b  b  b  b  c  f  .  . 

 

However there is a workaround for VB5.  When declaring a nested UDT declare it as an array eg:

Type myUDT2
     d as byte
     e(0) as myUDT
     f as byte
End Type

the output for this udt will be properly aligned 

size of structure = 20 bytes
 d  .  .  .  a  .  .  .  b  b  b  b  c  .  .  .  f  .  .  . 

 

 

4. Strings and fixed length strings

The way Visual Basic handles UDTs with fixed length strings in them, is quite different from the way it handles other udts.

First, lets look at how VB handles strings and fixed length strings.
VB stores all strings internally as Unicode, that is, two bytes per character.  So a fixed length string, such as 
    Dim myString as String * 3  
is stored internally as 6 bytes.

However when you pass a string to an api you normally pass it ByVal, in which case an Ansi copy is made in memory and that is passed instead.  The Ansi copy is only one byte per character, so your 3 character string is 6 bytes internally and 3 bytes externally.  Note: you can pass your string out to an api function as Unicode by using the StrPtr function.  eg ByVal StrPtr(MyString) will pass a pointer to your Unicode string.

Now when you go and put a fixed length string in a UDT you may encounter a few "quirks" when you try to use that UDT in an API call.

If you pass the UDT ByRef, VB creates a copy of the UDT in memory but uses Ansi instead of Unicode for the string.  So the size of the UDT inside of VB is not the same as the size of the UDT passed in memory.  You will have to calculate the size of the UDT created in memory yourself.  LenB will only give you the size of the UDT inside of VB.

If you pass the UDT ByVal, VB actually passes a pointer to the UDT.  It is the same as using ByVal VarPtr(MyUDT).  In this case the Unicode size is applicable, so you can use LenB.

So remember, when you pass a string ByVal you are passing the Ansi copy, but when you pass a UDT ByVal you are passing the Unicode original.

This behaviour is the same for arrays of UDTs that have fixed length strings in them, except for one very important difference.  If you try to pass an array of UDTs (with fixed length strings in them) ByRef, VB creates an Ansi copy of the first member only. and in some circumstances will give you a false "Out of Bounds" error.  This means you cannot pass the array out as Ansi.  However, you can pass the whole array out though as Unicode by passing it out ByVal.  This ,as mentioned above, is the same as passing a pointer to the first member of the array.

5. Tips and Terminology.