Visual Basic 6 Win32 API Tutorial, Page 5
Arrays are pretty simple to use, but they tripped me up the first time I had to use them in a call. Granted, I didn't know what I was doing, so I'm trying to stop you from wasting time here.
Anyway, let's take a look at a call that we'll be using a lot throughout this book. It's called ReadFile:
Declare Function ReadFile Lib "kernel32" Alias "ReadFile" _ (ByVal hFile As Long, lpBuffer As Any, ByVal _ nNumberOfBytesToRead As Long, lpNumberOfBytesRead As _ Long, lpOverlapped As OVERLAPPED) As Long
This comes straight from the API Viewer. Remember what I said about not taking that tool at its word? We need to make some changes here:
Declare Function ReadFile Lib "kernel32" Alias "ReadFile" _ (ByVal hFile As Long, lpBuffer As Byte, ByVal _ nNumberOfBytesToRead As Long, lpNumberOfBytesRead As _ Long, ByVal lpOverlapped As Long) As Long
We'll deal with UDTs shortly. We've already dealt with the traps that can bite you by using the Any keyword for data types, which is why I've explicitly declared the type. I'm more concerned here with the lpBuffer variable. If you read the documentation on this call, it will tell you that this is a buffer that the DLL will put file information into. That's why we declare it as a Byte data type, since it seems very natural to handle file information in a Byte format. If we had decided to store the file information in a Long array that would be fine as well, but there are many data conversions that we would have to deal with. For example, say we needed to read in a file that was 16 bytes long. If we used a long array, we'd only have 4 elements, but we would have to do some bit manipulation to separate out each Byte from a Long array element. Since VB doesn't have a lot of intrinsic bit manipulation functions (especially the really cool ones like shift-left and shift-right), it's easier to use a type that maps to the file extremely well.
Now that we've got some of the data declaration issues out of the way, we can tackle the array issue. If an API is going to fill a buffer for us and we decide to use an array, we have to pass in the first element of the array to the call. Note that I didn't say "element 1". It doesn't matter what the first element is - in fact, we could pass in any element of our array into the call. For example, each of the ReadFile calls would work in this example:
Dim bytFile(1 to 10) as Byte Dim bytFile2(1 to 10) as Byte Dim lngHFile as Long Dim lngRet as Long Dim lngTotalBytes as Long ' Code to open the file would go here. ' lngHFile is the handle to the file. lngRet = ReadFile(lngHFile, bytFile(1), 10&, lngTotalBytes, 0&) lngRet = ReadFile(lngHFile, bytFile2(6), 5&, lngTotalBytes, 0&)
In the first call, we're trying to read 10 bytes from a file and passing that information into bytFile. In the second example, we're only reading 5 bytes and we're starting at element 6. The first five elements in bytFile2 won't be changed after the second ReadFile call.
When I tried to pass in an array to a DLL the first time, I wrote this:
lngRet = ReadFile(lngHFile, bytFile(), 10&, lngTotalBytes, 0&)
This didn't work. When you pass in an array, VB is actually passing in a pointer to the element of the array that you specify (which is why the parameter is passed in by reference). If I had read up on the documentation, I would have saved myself hours of frustration. I had assumed that the call would be able to "know" what it should do with my buffer.
The only place you should be concerned with arrays and API calls is telling the DLL how much space you've allocated. For example, let's rewrite the first call to ReadFile to virtually guarantee that we crash our application:
Dim bytFile(1 to 10) as Byte Dim bytFile2(1 to 10) as Byte Dim lngHFile as Long Dim lngRet as Long Dim lngTotalBytes as Long ' Code to open the file would go here. lngRet = ReadFile(lngHFile, bytFile(1), _ 15&, lngTotalBytes, 0&)
In this case, we've told the DLL that there are 5 more elements from the starting point than what we've allocated for in memory. As we saw with strings, there's no way that the DLL knows how long your array is - you have to specify that yourself. In this case, the DLL is going to try and write to elements 11, 12, 13, 14, and 15. However, the area of memory that exists after our array is off limits, so the chance of a memory exception occurring is quite high when the DLL goes beyond the array boundaries - if you want to experiment with this don't forget to save your work.