As I mentioned earlier, eVB is somewhat hindered even in the API calls it can make because it doesn't support callback functions or User-Defined Types (UDTs). While there really isn't anything to be done about the lack of callback support, you can work around the lack of UDT support. Note We will be doing a lot of bit manipulation throughout this section and therefore will be looking at most numbers in hexadecimal format. Numbers in hexadecimal are prefixed in VB and eVB with &H, so 255 in hexadecimal would be &HFF. Bit manipulation can be confusing, especially if you've never worked at a binary level before, so I highly recommend researching decimal, hexadecimal, and binary numbers if you're not comfortable with it. The source for this section is available from www.samspublishing.com. Although you can easily cut and paste the code from this section and it will work fine, understanding what the code is doing will allow you to take advantage of the techniques in other situations. User-Defined Data Types You first need to understand that simply put, a UDT is a single, contiguous block of memory that contains linear data. For example, let's look at the RECT UDT, which will be used in the ClipCursor sample later. A RECT is the definition of a rectangle. If you look in the eMbedded Tools Help, a RECT is defined like this: typedef struct RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT; Because a RECT can be used in only eMbedded Visual C++, the help file gives the C++ declaration. If it were written in Visual Basic 6, it would look like this: Public Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type If you looked at this in memory, there would be 16 contiguous bytes (a Long is 32 bits, so it takes up 4) like this: Bytes 14 | Bytes 58 | Bytes 912 | Bytes 1316 | Left | Top | Right | Bottom | To make things even trickier, the bytes aren't in the order you would expect, because x86 type processors (386, 486, Pentium, Athlon, and so on), and therefore all Windows desktop operating systems, use what's known as Little Endian architecture . This means that the little, or least significant bits are stored first. For example, if you had the short (VB Integer) number &H9832, it would be stored in memory like this: It works similarly with Longs, so &H12345678 would be stored in memory like this: Byte 1 | Byte 2 | Byte 3 | Byte 4 | &H78 | &H56 | &H34 | &H12 | Notice I said that all Windows desktop operating systems use Little Endian architecture. This is simply because the processors used for desktop PCs, and servers for that matter, dictate this. It should also hold true for Windows CE devices, providing the processor uses Little Endian architecture. Note Macintosh architecture is Big Endian, so the large end is stored first, and this is why there are no Windows clones for Mac or MacOS for x86 machines. Looking back at the RECT UDT, if you define a RECT with the following: Left = 100 Top = 50 Right = 1200 Bottom = 175 It would look like this in memory: Bytes 14 | Bytes 58 | Bytes 912 | Bytes 1316 | | Left | Top | Right | Bottom | | Decimal | 100 | 50 | 1200 | 1175 | Hex | &H00000064 | &H00000032 | &H000004B0 | &H00000497 | Little Endian | &H64000000 | &H32000000 | &HB0040000 | &H97040000 | Strings Now you know what a UDT looks like in memory, but how does it help you work around eVB's lack of UDT support? Well, the key is that a String is also a single, contiguous block of memory that contains linear data. What this means is that if you place the data from a UDT into a String, you can pass it as a parameter to an API and the receiver will handle it all the same. You simply need to figure out how to transfer the binary data into a binary string. You need to be aware of one more thing. PocketPCsand, in fact, all Windows CE devicesuse Unicode for storing strings, which means that each character takes up two bytes, not just one. The reason behind this is that many languages of the world can't be fully represented in 256 characters, but 2 bytes allows for more than 65,000 characters . What this means is that you can't use standard character manipulation functions to handle binary strings. Left(strMyString, 1) would return 2 bytes, and you need to be able to manipulate one byte at a time to achieve the Little Endian format. Fortunately, you have binary versions of the String methods Mid, Left, Right, and Asc, which are MidB, LeftB, RightB, and AscB, respectively, and they all work just like their non-binary counterparts. Getting a Byte Value To build a binary string in Little Endian format, the first thing you need to be able to do is to extract a single byte from your numeric data. If you consider the previous example and you have &H12345678, you need to extract each byte right to left and append them to a string left to right. The first step is to isolate the byte you're after. This can be accomplished by simply doing a bitwise And with a number that has only the desired byte's bits turned on. This number is called a mask. In essence, this means if you want to extract &H34 from your number, you would perform the following: &H12345678 And &H00FF0000 This results in &H00340000. The only exception is that the most significant bit, position 31 in our example, isn't used for holding a value in Windows, but is used instead to hold the sign. If the bit is on, the number is negative, if it's off, the number is positive. This means that if you want the first byte, &H12 in the example, you turn on all bits in that byte except bit 7 (which is bit 31 in our 4 byte number). This means you And it with &H7F000000. You can then divide the resulting number by a number with all the bits of lower significance (those to the right of the desired byte) turned on. So in this example of getting the &H34, you would do the following: &H00340000 / &H0000FFFF This results in &H00000034 or simply &H34. So you need a function that performs this. You can make a single function that works both for Integers or Longs, and it looks like the code in Listing 9.11. Listing 9.11 Extracting the Value at a Specific Byte Position in a Binary String Public Function GetByteValue(Number As Variant, BytePos As Integer) As Long Dim mask As Long On Error Resume Next ' cannot check byte positions other than 0 to 3 If BytePos > 3 Or BytePos < 0 Then Exit Function If BytePos < 3 Then ' build a mask of all bits on for the desired byte mask = &HFF * (2 ^ (8 * BytePos)) Else ' the last bit is reserved for sign (+/-) mask = &H7F * (2 ^ (8 * BytePos)) End If ' turn off all bits but the byte we're after GetByteValue = Number And mask ' move that byte to the end of the number GetByteValue = GetByteValue / (2 ^ (8 * BytePos)) End Function Now to get our &H34 value out of &H12345678, you could call GetByteValue with a BytePos of 2 (you wrote the function zero-based , and you count from the right) like this: MsgBox(GetByteValue(&H12345678, 2)) 'remember we've number 0 to 3 And you would get a MessageBox with 52, the decimal equivalent of &H34, as its text. Building a Binary String Now that you have a function to pull the byte value at a specific position, you need another function that takes the Long or Integer, steps through its bytes, and builds a binary string. If you could strongly type the variable, you could just check the length in the function, but because all eVB variables are really Variants trying to get the correct length of the variable is unreliable. Instead you'll need to tell the function how long the input number really is. To make things a little more readable in the calling code, you can declare constants for the only two values you'll ever use, 2 for Integers and 4 for Longs, like this: Public Const CE_INTEGER = 2 Public Const CE_LONG = 4 The function itself is quite short. The major work is done in a For...Next loop that just iterates through the bytes of the passed in number and appends them to the binary string. One thing you must do before you start building the string is to handle negative numbers. Negative numbers are easily recognizable because, as I mentioned before, the most significant byte is turned on. In fact, a negative number is stored in memory as the binary complement of the number's absolute value, plus 1. A binary complement is achieved by reversing the values of all the bits in a number, turning off those that are on and vice versa. This is done easily by Xoring the number with a mask number with all bits on. Look at the number 10 as an example. First, you need the absolute value, which is 10 (&H0A, or 00001010 in binary). We get the complement by Xoring with &HFF to get 11110101 in binary and then add 1 to get 11110110 or &HF6. "10" in hex | &H0A | "10" in binary | 0000 1010 | Mask with &HFF | 1111 1111 | Xor result | 1111 0101 | Add 1 | 1111 0110 | Back to hex | &HF6 | To make things a bit more complex, if you try Xoring the most significant bit of a Long, you get an overflow error, so you have to Xor it as you build the string so that you're Xoring only a single byte. The resulting function, ToBinaryString, looks like Listing 9.12. Listing 9.12 Iterating Through the Bytes of an Integer or Long and Returning a Binary String Equivalent Public Function ToBinaryString(Number As Variant, Bytes As Integer) As String Dim i As Integer Dim bIsNegative As Boolean ' cannot check byte positions other than 0 to 3 If Bytes > 4 Or Bytes < 1 Then Exit Function ' If the number is negative, we need to handle it last, _ ' so we'll set a flag If Number < 0 Then bIsNegative = True ' get the absolute value Number = Number * -1 ' get the binary complement (except the most sign. bit) Number = Number Xor ((2 ^ (8 * Bytes - 1)) - 1) ' add one Number = Number + 1 End If ' start at the least significant bit (0) and work backwards For i = 0 To Bytes - 1 If i = Bytes - 1 And bIsNegative Then ' If the number is negative we must turn on the most ' significant bit and then and append it to the string ToBinaryString = ToBinaryString _ & (ChrB(GetByteValue(Number, i) + &H80)) Else ' just append the byte to our string ToBinaryString = ToBinaryString & ChrB(GetByteValue(Number, i)) End If Next i End Function Converting from a Binary String Now that you can generate a binary string from a number, you can call any API that has UDTs that contain Integers or Longs as well as Strings, but if you need to retrieve a value set by an API in a UDT, you will need ToBinaryString's complementary function. Here, you can reliably check the length of the variable because it has been strongly typed by the C++ API, so you only need to pass the function the binary string. Again, this is just reverse engineering the ToBinaryString function. We step through the string, pulling each character (which is actually a byte), determining its numeric value, and adding it to the resulting number. Remember, the rightmost character contains the most significant, or sign, bit, so you have to check for it and if it's on, you have to Xor each byte with &HFF, add one to get the absolute value of the actual number, and then multiply by 1 to make it negative. Listing 9.13 shows my implementation of FromBinaryString. Listing 9.13 Iterating Through a Binary String's Bytes and Converting It Back to a Usable Number Format Public Function FromBinaryString(BString As String) As Variant Dim i As Integer Dim bIsNegative As Boolean bIsNegative = False ' start at the end of the string and work backwards For i = LenB(BString) - 1 To 0 Step -1 If i = LenB(BString) - 1 And (AscB(MidB(BString, i + 1, 2)) _ And &H80) Then ' check the signigicant bit ' If it's negative, set a flag bIsNegative = True End If If bIsNegative = True Then ' extract the binary complement of the byte FromBinaryString = FromBinaryString + _ ((AscB(MidB(BString, i + 1, 2)) Xor &HFF) * (2 ^ (8 * i))) Else ' extract the byte FromBinaryString = FromBinaryString + _ AscB(MidB(BString, i + 1, 2)) * (2 ^ (8 * i)) End If Next i ' if it is supposed to be negative, make it so If bIsNegative Then ' Subtract one FromBinaryString = FromBinaryString + 1 ' make it negative FromBinaryString = FromBinaryString * -1 End If End Function |