User ID:
Password:
Remember Me:
Forgot Password?
Not a member?
Click here for more information and to register.

    How Visual Basic 6 Stores Data



    This article describes how VB stores Bytes, Boolean, Integer, Long, Single, Double, Strings (Fixed- and Variable-Length), Currency, Date, Variants, Arrays, and UDTs.

    BTW: I have put in a few points of interest. These are all marked with BTW (by the way).

    Pointers

    There is no way I can explain the intricacies of pointers here. That is an entire article on its own. But, briefly put, a pointer is a block of memory that stores a value of memory location where some other data exists. In a 32-bit operating system (for example, Win 95 and Win NT), the size of the pointer is 32 bits (4 bytes) and has a range of 0 to 4,294,967,296 (approx 4 Gigs).

    BTW: In a few years, when memory sizes exceed 4 Gig, we are going to need new 64-bit operating systems.

    Big- and Little- Endian Data Formats

    In little-endian format, a multi-byte value is stored in memory from the lowest byte (the "little end") to the highest.

    For example, the value 0x12345678 is stored, using little-endian format, as shown here.

    0x78 0x56 0x34 0x12

    In big-endian format, a multi-byte value is stored in memory from the highest byte (the "big end") to the lowest.

    For example, the value 0x12345678 is stored, in big-endian format, as shown here:

    0x12 0x34 0x56 0x78

    Most operating systems (for example, DOS and all versions of Windows (so far)) are designed to run using little-endian format. A few UNIX systems use big-endian format.

    Simple Data Types

    The following data types are all pretty simple in how they are stored: Byte, Boolean, Integer, and Long. Let's look at each one individually.

    Byte

    A byte is simply a single byte of memory—8 bits. Thus, it has a range of 0 to 255.

    Note: Bytes can't be used to represent negative numbers (at least, not directly).

    Boolean

    A boolean variable is represented by 2 bytes. (In actual fact, you could get away with using only 1 bit, so 2 bytes is, in my opinion, overkill. I think it stems from legacy 16-bit computer architecture.)

    A FALSE in boolean is stored as 0 (in other words, all bits are 0).

    A TRUE in boolean is stored as -1 (in other words, all bits are 1). However, it should be noted that any non-zero value is also interpreted as TRUE.

    Remember that the bytes occur in little-endian format.

    Integer

    An Integer in VB is defined in a most unfortunate fashion. Traditionally, an integer is defined as one "word" of an operating system. So, traditionally, on a 16-bit architecture an integer is 16 bits, on 32-bit OS it is 32 bits, and so forth. (This is true of a C++ integer.) But, it seems that somewhere along the line VB's integer got stuck in the stone age and was cast in stone as a 16-bit number.

    Thus, an integer is stored as 2 bytes, and has a range of -32,768 to 32,767.

    Negative numbers are represented using the "two's complement" of the number.

    Remember that the bytes occur in little-endian format.

    Long

    A VB long is stored using 4 bytes (32 bits), and has a range of -2,147,483,648 to 2,147,483,647. Because the long is 32-bits, it has become the obvious choice to use when needing to store pointers for Windows (a 32-bit OS).

    Negative numbers are represented using the "two's complement" of the number.

    Remember that the bytes occur in little-endian format.

    Floating Point Numbers

    At last!! A VB data type that conforms to standard of some sort. All floating point numbers, in VB, are stored as either IEEE 32-bit (4 byte) floating-point number (for single) or as IEEE 64-bit (8 byte) floating-point number (for double).

    First, let's look, briefly, at floating point numbers.

    Mantissas and Exponents

    Consider the decimal number 1234; this may be represented as 1.234 x 103. The 1.234 part is called the mantissa and the 3 is called the exponent, so we can write this 1.234E+3 (the 10 is implied and so doesn't need to be stored). The same is true for floating-point numbers stored binary, except that the mantissa is multiplied by 2 raised to the exponent (as apposed to 10 raised to the exponent as done in decimal).

    One more thing to note is that, in the IEEE floating point standard, the mantissa is always considered to have the "point" to occur between the first and second bits. So, a mantissa of 11 (binary) is interpreted as 1.5 (decimal).

    So, the number 6.5 could be written as 1101E2 (in other words, mantissa 1101 and exponent 2).

    Similarly, the number 1.625 would be written as 1101E0 (mantissa 1101 and exponent 0).

    Note: These are NOT IEEE standard yet.

    Now, the mantissa can always be written as a number between 1 and 2; the exponent just needs to be modified accordingly. So, this means that a 1 will always occur before the "point," and so, because we know that it must always be there, we don't need to store it.

    Considering this, 6.5 would be stored as 101E2 (where a leading 1 is implied in the mantissa).

    Similarly, the number 1.625 would be stored as 101E0 (where, again, a leading 1 is implied in the mantissa).

    Note: These are NOT IEEE standard yet.

    The Sign Bit

    An IEEE number is recorded as being positive or negative using one bit: 0 for positive and 1 for negative.

    Note: The rest of the number is not changed, unlike integers where the twos compliment denotes negative numbers. So, the only difference between 3 and -3 is that the signed bit for 3 is 0, and for -3 is 1.

    The Exponents Offset

    In the IEEE format, the exponent is offset by half its maximum possible value. So, for a 32-bit floating-point number the exponent has an offset of 127, and similarly a 32-bit number has an offset of 1023.

    Thus, an exponent 6.5 would be stored as 101E129 (where a leading 1 is implied in the mantissa, and the exponent is offset by 127).

    Similarly, the number 1.625 would be stored as 101E127 (again, where a leading 1 is implied in the mantissa, and the exponent is offset by 127).

    Another example is the number 0.8125, which is stored as 101E126 (again, where a leading 1 is implied in the mantissa, and the exponent is offset by 127 [because it represents -1]).

    These, at last, conform to the IEEE floating-point standard.

    Single

    A VB single is stored using an IEEE 32-bits floating-point number. An IEEE 32-bit floating-point number consists of (from most significant to least significant bit) 1 sign bit, 8 bits for the exponent, and 23 bits for the mantissa.

    For example, consider the number 1.5 stored in a single variable. (Ignore the fact that we are using little-endian format for the moment.)

    This will be stored as:

    0

    0

    1

    1

    1

    1

    1

    1

    1

    1

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    Sign Bit

    Exponent

    Mantissa

    The first bit indicates that the number is positive because this bit is 0.

    The next 8 bits are the exponent, offset by 127, giving a value of 0 (= 127-127).

    The final 23 bits are the mantissa, with an implied leading 1, thus giving 1100000...... which equals 1.5 (remember the point is between the first and second bits).

    And, to confirm this is correct, 1.5 x 20 = 1.5.

    For a second example, consider -73.625.

    This is stored as:

    1

    1

    0

    0

    0

    0

    1

    0

    1

    0

    0

    1

    0

    0

    1

    1

    0

    1

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    0

    Sign Bit

    Exponent

    Mantissa

    The first bit indicates that the number is negative because this bit is 1.

    The next 8 bits are the exponent, offset by 127, giving a value of 6 (= 133-127).

    The final 23 bits are the mantissa, with an implied leading 1, thus giving 100100110100000000000000, which equals 1.150390625 (remember the point is between the first and second bits).

    And, to confirm this is correct, -1.150390625 x 26 = -73.625.

    A value of 0.0 is represented by placing zeros throughout the entire 4 bytes of the single.

    Remember that the bytes occur in little-endian format.

    Double

    A double variable is stored as an IEEE 64-bits floating-point number. An IEEE 64-bit floating-point number consists of (from most significant to least significant bit) 1 sign bit, 11 bits for the exponent, and 52 bits for the mantissa.

    The storage rules are exactly the same as those for a single variable, except that the exponent has an offset of 1023.

    Remember that the bytes occur in little-endian format.

    Strings

    A Brief History of the Storage of Strings

    The storage of strings is best understood by looking at the way strings work in C. In C (at its most basic level), there is no sting data type. There is, however, a character type (char). A char is a simple one byte variable that represents a single character. And so, a string in C is simply an array of characters; in other words, a whole bunch of characters strung one after the next in memory. To signify the end of a string, C uses a "null termination" character (which is simply 0, and usually written /0) as the final character of a string. So, a string can be drawn as follows:

    T

    h

    i

    s

    _

    i

    s

    _

    f

    u

    n

    !

    /0

    This is a simple 12 character string, but an extra character is needed for the null termination character. Thus, the number of bytes required in memory is 13. But, as with all things, there is a problem with this. It is the number of possible characters that can be represented by a single byte: 256. Now, 52 of those are taken up by roman characters (26 for upper case and 26 for lower), 10 for numerical characters (0-9), plus all the punctuation (colons, commas, and so forth), then there are the "modified" roman characters (i.e. â, ï, ò), not to mention the plus, minus, and all those. And then, a few more for "reserved characters." And, at the end of it all you're not left with much for any other languages' characters.

    So, the next thing that was realised is that two bytes should be used for each character. This gives a possible 65,536 different characters. While this is mostly true in VB, Windows actually uses UTF-16 so it can encode a lot more characters by using surrogates. Apparently, this is enough for most of the characters of the world's languages. So now, each character is two bytes (the original ASCII set is defined by a 0 in the high byte and then the character values in the low byte). This new representation of strings is known as "wide" characters. So, our string looks like this:

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    i

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    /0

    .

    Please note that the dots represent the extra byte per character. Note also that the extra byte occurs AFTER the character because it makes use of little-endian data formatting (See "Big- and Little- Endian Data Formats").

    BTW: DOS 1 through 6 and Windows 3.11 and 95 used the old style of 1 byte per character (ASCII). WinNT used wide characters (but accepted the original ASCII).

    Nowm let's look at how to store and address strings. To address a stringm one typically has a pointer that points to the first memory location of the string. And obviously, the rest of the string follows on from there. So, we have the following situation (for a old ASCII string):

    Pointer   String

    xx

    xx

    xx

    xx

    ---->

    T

    h

    i

    s

    _

    i

    s

    _

    f

    u

    n

    !

    /0

    This is known as a LPSTR. Then, this was combined with the new wide strings:

    Pointer   String

    xx

    xx

    xx

    xx

    --->

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    i

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    /0

    .

    This is known as LPWSTR (W for wide). You'll see a lot of these if you do API programming.

    Simultaneously, it was realised that many string operations could be made a lot more efficient if the length of the string was known. And so, this new standard was defined:

    Pointer   String Descriptor   String

    xx

    xx

    xx

    xx

    ---->

    Length Pointer

    LL

    LL

    xx

    xx

    ---->

    T

    h

    i

    s

    _

    i

    s

    _

    f

    u

    n

    !

    This was known as HLSTR. Here, a pointer points to a descriptor, which contains both the length of the string and a pointer to the string (which is NOT null terminated because this is no longer required). VB 3 used this style of string storage.

    This worked well for a while, but it has a HUGE fatal flaw. It is not compatible with the typical C++ way of doing things. And so, a new style was defined. In this, the pointer points to the beginning of the string (which is null terminated for backward compatibility). Preceding the first character of the string are 4 bytes which contain the number of characters in the string.

    Length String

    LL

    LL

    LL

    LL

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    i

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    /0

    .

     

    ^

    |

     

    xx

    xx

    xx

    Xx

      Pointer

    This is called a BSTR, and this is what is used by VB 6. Notice how you can pass it to any function that requires a LPWSTR and the function will work properly. (If you are wondering what happens if the location of the null termination character and the length descriptor don't match up, well... the length descriptor takes precedence over the null terminator. The null terminator is only there for backward compatibility.)

    The only slight problem occurs when you pass it to a LPSTR. In this case, the second byte of the string is interpreted as the null terminator (if one is using the ASCII character set). But, this problem seldom rears its head.

    Fixed-Length Strings

    A fixed length string is stored as a series of bytes, occurring in sequence in memory. The number of bytes required is twice as many as the length of the string. The string is then stored in the bytes, using wide characters.

    Fixed-length strings are NOT null terminated. Also, fixed-length strings do not utilise a pointer to point to the string.

    Thus, a fixed-length string would be stored as follows:

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    i

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    A fixed length string can be up to (approximately) 65,400 characters long.

    Note: There is a slight mistake in the MSDN. It states that a fixed length string requires as many bytes as characters in the string. It should read that 2 times the number of characters are required (remember, VB 6 uses wide characters). I am not too sure whether Microsoft has corrected this error in later versions.

    Variable-Length Strings

    Variable length strings are stored using BSTR as described above. It, therefore, consists of a pointer, the string's length, and the null-terminated string. This BSTR is suitable for passing to API functions (that utilise wide characters).

    The maximum length of a variable-length string is determined by the size of the length descriptor in front of the string, and is limited to 2 billion.

    Each time a new value is assigned to a string (even if it is the same length), VB fetches a new block of memory and stores this new memory location in the pointer, and then releases the previously utilised block of memory.

    Note: The pointer always remains in the same place in memory; only the string and its preceding length move.)

    This constant acquiring and releasing of memory means that appending characters to a string is slow. Consider the following (somewhat stupid) piece of VB code:

    Dim strTemp As String
    
    strTemp = ""
    
    For i = 1 To 100
       strTemp = strTemp & "*"
    Next i
    

    Each time the loop is executed, VB finds a new chunk of memory, changes the pointer, and releases the old chunk. This extra overhead can have quite an effect on the performance of VB. A much better way of doing this is to do the following:

    Dim strTemp As String
    
    'Define a string of length 100
    strTemp = String(100, " ")
    
    'Change the characters in the string
    
    For i = 1 To 100
       Mid(strTemp, i, 1) = "*"
    Next i
    

    This is a bit more efficient.

    Short Note on Strings

    Notice how incredibly differently fixed- and variable-length strings are stored. To the end user they look the same, but they are very different. This explains why fixed-length strings are such a pain to work with; they are completely different data types. (My humble suggestion: Don't use fixed-length strings unless you feel you have to, or you need the speed.)

    More Complex Data Types

    The Currency and Date data types are, in fact, very simply stored, but are just interperated differently.

    Currency

    A Currency variable is simply a 64-bit (8-byte) integer, which is scaled by 10000 to give a fixed-point number with 15 digits to the left of the decimal point and 4 digits to the right. So the number 5.846 is stored as the binary integer 0000000000000000000000000000000000000000000000001110010001011100.

    This equals 58460, and when divided by 10000 gives the number stored... 5.846.

    Remember that the bytes occur in little-endian format.

    Date

    A Date is stored as an IEEE 64-bit (8-byte) floating point number, just like a Double. Digits to the left of the decimal point (when converted to decimal) are interperated as a date between 1 January 100 and 31 December 9999. Values to the right of the decimal point indicate time between 0:00:00 and 23:59:59.

    The date section of the Date data type (before the decimal point), is a count of the number of days that have passed since 1 Jan 100, offset by 657434. That is, 1 Jan 100 is denoted by a value of -657434; 2 Jan 100 is denoted -657433; 31 Dec 1899 is 1; 1 Jan 2000 is 36526, and so on.

    The time section of the date (after the decimal point) is the fraction of a day, expressed as a time. For example, 1.5 indicates the date 31 Dec 1899 (as above) and half a day, i.e. 12:00:00. So, an hour is denoted by an additional 4.16666666666667E-02, a minute by 6.94444444444444E-04, and a second by 1.15740740740741E-05.

    Remember that the bytes occur in little-endian format.

    Variant Data Types

    Basically put, variants have no definite shape. They change their shape depending on what they are storing. The format of a variant is as follows (ignoring endian formats):

    The variant is 16 bytes large. It has 2 bytes to describe the type of data it is storing, 6 reserved bytes, and 8 bytes to store the data (each block represents a byte).

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Type

    Reserved

    Data

    And for some reason, the Type, Reserved, and Data descriptors occur in this order in memory, although the data stored within each occurs in little-endian format.

    The Type Descriptor

    The type describer takes on a value depending on what the variant contains. The following list is a selection of the more common values.

    Data Type

    VB Constant

    Value

    Empty (uninitialised) vbEmpty 0
    Null (no valid data) vbNull 1
    Integer vbInteger 2
    Long integer vbLong 3
    Single-precision floating-point number vbSingle 4
    Double-precision floating-point number vbDouble 5
    Currency value vbCurrency 6
    Date value vbDate 7
    String vbString 8
    Object vbObject 9
    Error value vbError 10
    Boolean value vbBoolean 11
    Variant (used only with arrays of variants) vbVariant 12
    Byte value vbByte 17
    User-Defined Types (UDT) vbUserDefinedType 36
    Array vbArray 8192

    The Data Descriptor

    The data descriptor is filled with data starting from the "left" side. For example, when storing a Boolean variable, which is only two bytes big, the "left" most bytes are used; for example:

    (shaded blocks are those utilised)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Type

    Reserved

    Data

    and, similarly for a Long variable,

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Type

    Reserved

    Data

    and for a Double,

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Type

    Reserved

    Data

    So when storing a Byte, Boolean, Integer, Long, Single, Double, Currency or Date, the data of value stored is simply placed into the data descriptor.

    Thus, for example (xx implies unknown),

    When storing a Byte of value 255:

    17

    0

    xx

    xx

    xx

    xx

    xx

    xx

    255

    xx

    xx

    xx

    xx

    xx

    xx

    xx

    Type

    Reserved

    Data

    When storing a Long of value 255:

    3

    0

    xx

    xx

    xx

    xx

    xx

    xx

    255

    0

    0

    0

    xx

    xx

    xx

    xx

    Type

    Reserved

    Data

    Now, for storing Strings (all strings in variants are variable-length), Arrays, UDT, or other data types (for example, objects), the data descriptor simply takes on the value of memory location of the data (in other words, the data descriptor acts as a pointer). Because the data descriptor is essentially a pointer the only the first 4-bytes are used. For example, the String "This is fun!" would be stored as follows:

     

    Length

    String

     

    12

    0

    0

    0

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    I

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    /0

    .

     

     

    ^

    |

    17

    0

    xx

    xx

    xx

    xx

    xx

    xx

    yy

    yy

    yy

    yy

    xx

    xx

    xx

    xx

    Type

    Reserved

    Data

    Arrays

    An array in VB is implemented as a "SafeArray." This means that you can use some of the API calls to access certain bit and pieces of the array that are otherwise inaccessible. A SafeArray contains 20 bytes of memory, plus 8 bytes per dimension, plus the memory required to store the data.

    Note: There is an error in the MSDN. It states that an additional 4-bytes per dimension are required. It should read 8 bytes.

    For example, consider an array defined using the following statement:

    Dim arr_intTemp(4)(3) As Integer

    This will have 20 bytes,

    Plus 8 for each dimension, another 16 bytes,

    Plus 12 (4x3) Integers of 2 bytes each, 24 bytes,

    Giving a total size of 60 bytes for the array.

    The safe array can basically be divided into three sections. The first is the safe array pointer, the second the "base" of the array, and the third the data area of the array.

    The Safe Array Pointer

    The safe array pointer is a pointer (4 bytes) that points to the base of the safe array. The location of this pointer remains fixed at all times; however, the locations of the "base" change each time the array is redimensioned (Redim).

    The Array "Base"

    The base of the array is defined as follows (each block represents a byte):

    Name

    Bytes

    Description

    cDim 2 A count of the number of dimensions in the array.
    fFeature 2 Flags
    cbElements 4 Size of each element in the array.
    cLocks 4 Used to lock the array.
    pvData 4 A pointer to the first data element in the array.
    rgsabound 8 * cDim Safe_Array_Bound array.

    cDim contains the number of dimensions that the array has. So, for example, a VB array defined using the code:

    Dim arr_intTemp(4, 3) As Integer

    cDim contains the value 2.

    The fFeature flags may be any combination of the following:

    Name

    Hexadecimal Value

    Description

    FADF_AUTO 0x0001 Array is allocated on the stack.
    FADF_STATIC 0x0002 Array is statically allocated.
    FADF_EMBEDDED 0x0004 Array is embedded in a structure.
    FADF_FIXEDSIZE 0x0010 Array may not be resized or reallocated.
    FADF_BSTR 0x0100 An array of BSTRs.
    FADF_UNKNOWN 0x0200 An array of IUnknown*.
    FADF_DISPATCH 0x0400 An array of IDispatch*.
    FADF_VARIANT 0x0800 An array of VARIANTs.
    FADF_RESERVED 0xF0E8 Bits reserved for future use.

    cbElements contains the size (in bytes) of the elements in the array.

    Note: If the elements are pointers (for example, for strings) the cbElements only records the length of the pointer, not the entire data structure.

    cLocks contains the number of times the array has been locked without corresponding unlock.

    pvData is a pointer to the first element of data in the array.

    rgsabound is an array of elements of type Safe_Array_Bound, and contains the bounds data for each dimension of the safe array. The rgsabound array is stored with the right-most dimension in rgsabound[0] and the left-most dimension in rgsabound[cDim . 1] (in other words, there is one Safe_Array_Bound element for each dimension in the array). The Safe_Array_Bound element is 8 bytes big and is allocated as follows:

    Name

    Bytes

    Description

    cElements 4 The number of elements in the dimension.
    lLbound 4 The lower bound of the dimension.

    So, for the array defined as:

    Dim arr_intTemp(4, 3) As Integer

    rgsabound will contain 2 elements. The first (element 0) will has cElements = 5 and lLbound = 0, whereas the second (element 1) has cElements = 3 and lLbound = 0.

    The Data Area

    The data array is simply a sequence of elements of the array. It therefore has a size of N * m, where N is the number of elements in the array and m is the size of each element in the array. It should be noted that, for an array of strings or objects, only the pointer is stored in the arrays data sequence (in other words, m equals 4 bytes, the size of a pointer). The data is stored in such a way that the first dimension is stored first, then the second, and so on.

    Example of VB Array Storage

    Consider a VB array defined with the following code:

    Dim arr_intTemp(0 to 1, 1 to 2) As Integer
    
    arr_intTemp(0, 1) = 1
    arr_intTemp(0, 2) = 2
    arr_intTemp(1, 1) = 3
    arr_intTemp(1, 2) = 4
    

    Will be stored as follows (each block is a byte):

    Remember that the bytes occur in little-endian format.

    Safe Array Pointer   Array "Base"  

    xx

    xx

    xx

    xx

    --->

    2

    0

    cDim
       

    128

    0

    fFeature
       

    2

    0

    0

    0

    cbElements
       

    0

    0

    0

    0

    cLocks
       

    xx

    xx

    xx

    xx

    pvData
       

    2

    0

    0

    0

    1

    0

    0

    0

    2

    0

    0

    0

    0

    0

    0

    0

    cElements
    lLbound
    cElements
    lLbound

    rgsabound

       

    |

    |

     
       
    1 0
    3 0
    2 0
    4 0

    Data Area

    (Pointed to by pvData in Array "Base")

    Notice the order of the rgsabound array. The last (right-most) dimension is the first element in the rgsabound array.

    Notice the order with which the data is stored in the data area. The first element is (0, 1) then (1, 1) then (0, 2) and finally (1, 2).

    Redimensioning An Array

    When a VB array is redimensioned (using the Redim command), the array changes as follows (although not necessarily in this order):

    1. A new memory location is found for the array base, and the old one is freed. This is required because the memory required by rgsabound will change. The new base will be repopulated with the data required.
    2. A new memory location is found for the data area. This is required because the memory requirements of the data area will change. If the "Preserve" keyword is used with the Redim, the old data is copied to the new memory location. This memory copy occurs such that the first byte of the old area is copied to the first byte of the new area, second to second, etc. This is why the following statements are made in the MSDN:

      If you use the Preserve keyword, you can resize only the last array dimension and you can't change the number of dimensions at all.

      Similarly, when you use Preserve, you can change the size of the array only by changing the upper bound; changing the lower bound causes an error.

      Once the memory copy is complete (if required), the old data area is freed.

    3. The pvData pointer is changed to point to the new location of the data area.
    4. The safe array pointer is changed to point to the new location of the base.

    BTW: See the note below on how to actually test that this is how an array is stored...

    User Defined Types (UDT)

    A UDT is stored a sequence of consecutive memory blocks, as per the data types used. Often, padding is used between elements. The padding differs from one configuration to another. There doesn't seem to be a distinct pattern to it. The best way to determine whether you have padding anywhere is to us the algorithm given below.

    For example, the UDT defined using:

    Type udtMyType
       intVar1  As Integer
       intVar2  As Integer
       strVar   As String
       sngVar   As Single
    End Type
    

    Will be stored as (using arbitrary data):

    udtMyType

    1

    0

    0

    0

    xx

    xx

    xx

    xx

    0

    0

    193

    63

    intVar2

    Padding

    strVar

    (String Pointer)

    sngVar

    |

    |

    T

    .

    h

    .

    i

    .

    s

    .

    _

    .

    i

    .

    s

    .

    _

    .

    f

    .

    u

    .

    n

    .

    !

    .

    /0

    .

    String as pointed to by strVar.

    BTW: If you put in a second integer, no padding is used.

    Pointers and Hacking VB's Memory

    To gain access to the "hidden" data in VB (and to REALLY play around with variables), you need a few things that VB doesn't let you do. Namely pointers.

    Or does it?

    There are actually ways of working with pointers in VB, but it is a bit of a hack... You are going to need the following commands to gain access to pointers: VarPtr, StrPtr, and ObjPtr. Can't find any documentation on these commands? Well, here's the bad news; Microsoft will all but deny that they exist in Visual Basic. This is because if you start mucking around with them you could break VB quite spectacularly; and, furthermore, the way VB stores data differs from version to version, so the commands will not always act as expected on other versions. Thus, there is no backward or forward compatibility. But don't let this scare you.

    VarPtr returns the pointer (VB type long) to a variable. It points directly to the data for Boolean, byte, integer, long, single, double, currency, and UDTs. But, for strings, it returns the pointer to the string pointer, and for variants it points to the beginning of the variant.

    StrPtr returns a pointer to the string data (in other words, it is the same as using VarPtr to get the string pointer and the returning the contents of the string pointer).

    ObjPtr returns a pointer to an object.

    To complete your arsenal of pointer functions, you'll also need an API function known as CopyMemory. This command copies memory from one location to another. It is requires the following define statement:

    Private Declare Sub CopyMemory Lib "kernel32" Alias
    "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal
    ByteLen As Long)
    

    BTW: You may also want to look at MoveMemory, FillMemory, and ZeroMemory

    BTW: You may also want to take a look at CopyMemoryVlm, MoveMemoryVlm, FillMemoryVlm, and ZeroMemoryVlm if you need 64-bit versions.

    A Useful Algorithm for Printing Memory

    I use a module to test how VB stores its variables. It is called as follows:

    Text1.Text = Text1.Text & PrintOut(VarPtr(intTemp),
    LenB(intTemp), "Memory Dump For Integer")
    

    Here is the module.

    Option Explicit
    
    'Functions:
    '==========
    '   PrintOut
    '   FUNC:   Returns a string, containing a printout of a memory
    '           block specified
    '   INPUT:  StrtMem As Long  The memory location from where the
    '                            printout begins.
    '   Length As Long           The length of the memory to be printed
    '                            out, in bytes.
    '   [Title] As String        Optional. A heading to be printed of
    '                            the printout. If blank ("", default),
    '                            then no heading is printed.
    '   [Reverse] As Boolean     Optional. If TRUE the memory printout
    '                            is from the highest byte to the lowest.
    '                            Default is FALSE.
    '   RETURN: As String        The format of the printout is as follows:
    '                            [Heading]
    '                            =========
    '                            0  ([StrtMem+0]) >> Value0_Char
    '                            Value0_Hex (Value0_Dec) [Value0_Bin]
    '                            1  ([StrtMem+1]) >> Value1_Char
    '                            Value1_Hex(Value1_Dec) [Value1_Bin]
    '                            2  ([StrtMem+2]) >> Value2_Char
    '                            Value2_Hex (Value2_Dec) [Value2_Bin]
    '                            " >> " " " "
    '                            " >> " " " "
    '                            L  ([StrtMem+L]) >> ValueL_Char
    '                            ValueL_Hex (ValueL_Dec) [ValueL_Bin]
    '
    
    Private Declare Sub CopyMemory Lib
    "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any,
    ByVal ByteLen As Long)
    
    'FUNC:  Returns a string, containing a printout of a memory block specified
    Public Function PrintOut(ByVal StrtMem As Long, _
                             ByVal Length As Long, _
                             Optional Title As String = "", _
                             Optional Reverse As Boolean = False) As String
    
       Dim i                 As Long
       Dim strUnderline      As String
       Dim lng1              As Byte
       Dim ptrToLong         As Long
    
       Dim lngForStart       As Long
       Dim lngForFinish      As Long
       Dim lngForStep        As Long
    
       Dim strTempReturn     As String
    
       strTempReturn = ""
    
       'Setup the for loop variables
       If Not (Reverse) Then
          lngForStart = 0
          lngForFinish = Length - 1
          lngForStep = 1
       Else
          lngForStart = Length - 1
          lngForFinish = 0
          lngForStep = -1
       End If
    
       lng1 = 0
       ptrToLong = VarPtr(lng1)
    
       'Print the heading
       If Title <> "" Then
          strTempReturn = strTempReturn & Title & ":" & vbNewLine
          strUnderline = String(Len(Title) + 1, "-")
          strTempReturn = strTempReturn & strUnderline & vbNewLine
       End If
    
    '   For i = Length - 1 To 0 Step -1
       For i = lngForStart To lngForFinish Step lngForStep
          CopyMemory ByVal ptrToLong, ByVal (StrtMem + i), 1
          'Write the relative, and absolute memory addresses
          If (i < 10) Then
             strTempReturn = strTempReturn & i & " (" & Hex(StrtMem + i) & ") >> "
          Else
             strTempReturn = strTempReturn & i & " (" & Hex(StrtMem + i) & ") >> "
          End If
    
          'Write the content as a character
          If (lng1 >= 32) And (lng1 <= 255) Then
             strTempReturn = strTempReturn & Chr(lng1)
          Else
             strTempReturn = strTempReturn & "."
          End If
    
          'Write the content as a hexidecimal number
          If Len(Hex(lng1)) = 1 Then
             strTempReturn = strTempReturn & " " & Hex(lng1) & "h "
          Else
             strTempReturn = strTempReturn & " " & Hex(lng1) & "h "
          End If
    
          'Write the content as a decimal number
          If (lng1 < 10) Then
             strTempReturn = strTempReturn & " (" & lng1 & ") "
          ElseIf (lng1 < 100) Then
             strTempReturn = strTempReturn & " (" & lng1 & ") "
          Else
             strTempReturn = strTempReturn & "(" & lng1 & ") "
          End If
    
          'Write the content as a binary number
          strTempReturn = strTempReturn & "[" & Dec2Bin(lng1) & "]"
    
          'Write a new line character
         strTempReturn = strTempReturn & vbNewLine
       Next i
       PrintOut = strTempReturn & vbNewLine
    End Function
    
    
    Private Function Dec2Bin(ByVal intDec As Byte) As String
       Dim strHex          As String
       Dim strTempReturn   As String
       Dim strTempNibble   As String * 4
       Dim i               As Integer
    
       'Initialise the return variable
       strTempReturn = String(8, "*")
    
       'Get the hexidecimal value of the binary number
       strHex = Hex(intDec)
    
       'Test if strTempHex is 1 character long
       If (Len(strHex) = 1) Then
          strHex = "0" & strHex
       End If
    
       'Convert the hexidecimal number to binary
    
       For i = 1 To 2
          Select Case (Mid$(strHex, i, 1))
          Case "0"
             strTempNibble = "0000"
          Case "1"
             strTempNibble = "0001"
          Case "2"
             strTempNibble = "0010"
          Case "3"
             strTempNibble = "0011"
          Case "4"
             strTempNibble = "0100"
          Case "5"
             strTempNibble = "0101"
          Case "6"
             strTempNibble = "0110"
          Case "7"
             strTempNibble = "0111"
          Case "8"
             strTempNibble = "1000"
          Case "9"
             strTempNibble = "1001"
          Case "A"
             strTempNibble = "1010"
          Case "B"
             strTempNibble = "1011"
          Case "C"
             strTempNibble = "1100"
          Case "D"
             strTempNibble = "1101"
          Case "E"
             strTempNibble = "1110"
          Case "F"
             strTempNibble = "1111"
          End Select
    
          If i = 1 Then
             Mid(strTempReturn, 1, 4) = strTempNibble
          Else
             Mid(strTempReturn, 5, 4) = strTempNibble
          End If
       Next i
    
       'Return the value in binary
       Dec2Bin = strTempReturn
    End Function
    

    Testing and Hacking the VB Array

    This section is purely for your interest.

    If you try the following:

    Dim arrTemp(1)   As Integer
    Dim ptrVarPtr    As Long
    
    ptrVarPtr = VarPtr(arrTemp)
    

    You'll get a nice "Type Mismatch" error. VB absolutely will not let you pass an array to VarPtr (and passing an element just returns the data area). So, how do you ever test the memory storage of an array? Or for that matter, ever hack it? Well, you wrap it in a UDT and then get the address from that... but that doesn't work directly. Try this:

    Private Type udtMyType
       arrbytVar(1)    As Byte
    End Type
    
    :
    :
    
    Dim udtMyTemp   As udtMyType
    
    udtMyTemp.arrbytVar(0) = 1
    udtMyTemp.arrbytVar(1) = 2
    
    Text1.Text = Text1.Text & PrintOut(ptrToVar, _
                 LenB(udtMyTemp), "ptrToVar Dump For udtMyTemp")
    

    And hey, presto, it actually prints out the data elements of the array (in other words, each element of the array is an element of the UDT; it isn't stored as a SAFE_ARRAY)! Now, try dimensioning the array only later in code:

    Private Type udtMyType
       arrbytVar()    As Byte
    End Type
    
    :
    :
    
    Dim udtMyTemp   As udtMyType
    
    ReDim udtMyTemp.arrbytVar(1)
    
    udtMyTemp.arrbytVar(0) = 1
    udtMyTemp.arrbytVar(1) = 2
    
    Text1.Text = Text1.Text & PrintOut(ptrToVar, LenB
    (udtMyTemp), "ptrToVar Dump For udtMyTemp")
    

    Bingo. There you go. You get a pointer in the printout. Follow the pointer if you like and you'll get the "base" of the array. Here is the complete code for doing this:

    Dim udtMyTemp       As udtMyType
    Dim ptrToVar        As Long
    Dim ptrToArrBase    As Long
    Dim ptrToArrData    As Long
    
    ReDim udtMyTemp.arrbytVar(0 To 1, 1 To 2)
    
    udtMyTemp.arrbytVar(0, 1) = 1
    udtMyTemp.arrbytVar(1, 1) = 2
    udtMyTemp.arrbytVar(0, 2) = 3
    udtMyTemp.arrbytVar(1, 2) = 4
    
    ptrToVar = VarPtr(udtMyTemp)
    
    Text1.Text = Text1.Text & "ptrToVar = " & Hex
    (ptrToVar) & "h (" & ptrToVar &")" & vbNewLine
    
    Text1.Text = Text1.Text & PrintOut(ptrToVar, LenB
    (udtMyTemp), "ptrToVar Dump For udtMyTemp")
    
    'Follow the safe array "base" pointer
    CopyMemory ByVal VarPtr(ptrToArrBase), ByVal ptrToVar, 4
    
    Text1.Text = Text1.Text & "ptrToArrBase = " & Hex
    (ptrToArrBase) & vbNewLine
    Text1.Text = Text1.Text & PrintOut(ptrToArrBase, 32,
    "ptrToArrBase Dump For Array")"
    
    'Follow the data area pointer
    CopyMemory ByVal VarPtr(ptrToArrData), ByVal ptrToArrBase + 12, 4
    
    Text1.Text = Text1.Text & "ptrToArrData = " & Hex
    (ptrToArrData) & vbNewLine
    Text1.Text = Text1.Text & PrintOut(ptrToArrData, 4, "ptrToArrData Dump For Array")
    

    IT Offers


    Top Authors