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 (4×3) 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")

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read