Dear John, How Do I... Use Byte Arrays?

One of the main reasons Byte arrays were created was to allow the passing of binary buffers to and from the 32-bit API functions. One of the differences to be aware of between 16-bit and 32-bit Visual Basic applications is that 32-bit version strings are assumed to contain Unicode characters, which require 2 bytes for each character. The system automatically converts 2-byte Unicode sequences to 1-byte ANSI characters, but if the string contains binary data the contents can become unintelligible. To prevent problems, you should get into the habit of passing only character data in strings and passing binary data in Byte arrays.

Passing Byte Arrays Instead of Strings

Byte arrays contain only unsigned Byte values in the range 0 through 255. Unlike the contents of strings, Byte array contents are guaranteed not to be preprocessed by the system. You can pass Byte arrays in place of strings in many API functions.

For example, the following code, which uses the GetWindowsDirectory Windows API function to find the path to the Windows directory, demonstrates the changes you'll need to make to the function declarations and function parameters. The code is shown in two versions: the first passes a string in which the API function can return the Windows directory, and the second passes a Byte array instead. When you place either of these code examples in a form, run it, and click on the form, you'll see the path to your Windows directory, as shown in Figure 3-2.

Figure 3-2. The API function that returns the path to your Windows directory.

By carefully noting the differences in these two examples, you'll gain insight into the differences between string and Byte array parameters. The string example is shown here:

Option Explicit

Private Declare Function GetWindowsDirectory _
Lib "kernel32" _
Alias "GetWindowsDirectoryA" ( _
    ByVal lpBuffer As String, _
    ByVal nSize As Long _
) As Long

Private Sub Form_Click()
    Dim intN As Integer
    Dim strA As String
    `Size the string variable
    strA = Space$(256)
    intN = GetWindowsDirectory(strA, 256)
    `Strip off extra characters
    strA = Left$(strA, intN)
    Print strA
End Sub

In this first code example, the string parameter lpBuffer returns the path to the Windows directory. I presized the variable strA to 256 characters before calling the function, and I stripped off the extra characters upon returning from the function.

WARNING

Before making the function call, always presize a string or a Byte array that an API function fills with data. Your program will probably crash if you forget to do this!

Here's the Byte array example:

Option Explicit

Private Declare Function GetWindowsDirectory _
Lib "kernel32" _
Alias "GetWindowsDirectoryA" ( _
    ByRef lpBuffer As Byte, _
    ByVal nSize As Long _
) As Long

Private Sub Form_Click()
    Dim intN As Integer
    Dim bytBuffer() As Byte
    Dim strA As String
    `Size the Byte array
    bytBuffer = Space$(256)
    intN = GetWindowsDirectory(bytBuffer(0), 256)
    strA = StrConv(bytBuffer, vbUnicode)
    `Strip off extra characters
    strA = Left$(strA, intN)
    Print strA
End Sub

Take a close look at the function declaration for the GetWindowsDirectory API function in this second example. I changed the declaration for the first parameter from a ByVal string to a ByRef Byte array. The original parameter declaration was:

ByVal lpBuffer As String

I changed the ByVal keyword to ByRef and changed String to Byte in the new form of this declaration:

ByRef lpBuffer As Byte

A ByVal modifier is required for passing string buffers to API functions because the string variable actually identifies the place in memory where the address of the string contents is stored—in C terminology, a pointer to a pointer. ByVal causes the contents of the memory identified by the string name to be passed, which means that the value passed is a memory address to the actual string contents. Notice that in my function call I pass bytBuffer(0), which, when passed using ByRef, is passed as the memory address for the contents of the first byte of the array. The result is the same. In case of either byte array or string, the GetWindowsDirectory API function will blindly write the Windows directory path into the addressed buffer memory.

Further along in the code is this important line which requires some explanation:

strA = StrConv(bytBuffer, vbUnicode)

This command converts the Byte array's binary data to a valid Visual Basic string. Dynamic Byte arrays allow direct assignment to and from strings, which is accomplished like this:

bytBuffer = strA
strB = bytBuffer

When you assign a string to a dynamic Byte array, the number of bytes in the array will be twice the number of characters in the string. This is because Visual Basic strings use Unicode, and each Unicode character is actually 2 bytes in size. When ASCII characters are converted to a Byte array, every other byte in the array will be a 0. (The second byte is used to support other character sets, such as for European or Asian languages, and becomes important when internationalization of your applications is necessary.) In the case of the GetWindowsDirectory API function, however, the returned buffer is not in Unicode format, and we must convert the Byte buffer's binary contents to a Unicode string ourselves, using the StrConv function as shown previously. In the first code example, we let Visual Basic perform this housekeeping chore automatically as it filled our string parameter.

The conversion to Unicode converts each character in the buffer to 2 bytes and actually doubles the number of bytes stored in the resulting string. This isn't readily apparent when you consider that the function Len(strA) reports the same size as would UBound(bytBuffer) after the Unicode conversion in the above case. However, the function LenB(strA) does report a number twice the size of the number reported by UBound(bytBuffer). This is because the Len function returns the number of characters in the string, whereas the LenB function returns the number of bytes in the string. The character length of a Unicode string (remember, this includes all 32-bit Visual Basic strings) is only half the number of actual bytes in the string because each Unicode character is 2 bytes.

To summarize, when converting an API function parameter from a string buffer to the Byte array type, change the ByVal keyword to ByRef, pass the first byte of the array instead of the string's name, and if the binary data is to be converted to a string, remember to use StrConv with the vbUnicode constant.

Copying Between Byte Arrays and Strings

To simplify the transfer of data between Byte arrays and strings, the designers of Visual Basic decided to allow a special case assignment between any dynamic Byte array and any string.

NOTE

You can assign a string to a Byte array only if the array is dynamic, not if it is fixed in size.

The easiest way to declare a dynamic Byte array is to use empty parentheses in the Dim statement, like this:

Dim bytBuffer() As Byte

The following Dim statement creates a fixed-size Byte array, which is useful for a lot of things but not for string assignment. It will, in fact, generate an error if you try to assign a string to it.

Dim bytBuffer(80) As Byte