4. Data Types

Page 41
4. Data Types
In order to understand the Windows API and how to use its functions, it is vital to have a thorough understanding of the concept of a data type. Indeed, the Win32 API uses more than a thousand different ''data types" (not including structures)!
One of the challenges to successfully using the Windows API from within Visual Basic is to figure out how to translate the required Windows data types into one of only a handful of available Visual Basic data types. This is relatively easy in most cases, but as we will see, there are some tricks when it comes to unsigned data types, strings, and structures.
What Is a Data Type?
It seems like every computer science book that addresses the concept of data type defines the concept somewhat differently. We will adopt a definition that will help us deal effectively with the issue of data type translation between VC++ and VB.
For us, a data type (or just type) is an object with the following properties:
A set of values, which we refer to as the underlying set for the data type.
A way to represent the values in computer memory. For those data types for which the representation of every value in the underlying set uses the same number of bits, this number of bits is the size of the data type. For instance, under Visual Basic, each member of the Integer data type occupies 16 bits of memory, and so this integer data type has size 16 bits and is thus called a 16-bit data type. On the other hand, the VC++ integer data type is a 32-bit data type.
A set of operations that can be performed on the values of this type. It will seldom be necessary for us to enumerate this set of operations explicitly.

Page 42
In addition, some data types require an ancillary data type. For instance, the ancillary data type for the type pointer to integer is the integer data type. More generally, the ancillary data type for a pointer data type is the target data type of the pointer. The ancillary data type for an array data type is the type of the individual array elements. (We won't deal with arrays in which the elements can have different data types.)
Examples of Data Types
To illustrate the definition, let us take a look at a few examples of data types.
The Win32 unsigned integer data type
Under Win32, the unsigned integer data type has an underlying set consisting of the mathematical integers from 0 to 232-1 inclusive. Each unsigned integer is represented as a 32-bit binary string, so this is a 32-bit data type. The set of operations for this integer data type is the familiar set of binary arithmetic operations, with overflow detection, along with several other types of operations, such as logical operations, which we will not enumerate.
The Win32 integer data type
Under Win32, the integer data type (also referred to as the signed integer data type) consists of the set of mathematical integers from -231 to 231-1 inclusive. Each such value is represented as a 32-bit binary string using two's complement representation.
Simply put, two's complement representation uses the leftmost bit, called the sign bit, to indicate the sign of the number. Negative numbers have sign bit 1 and positive numbers have sign bit 0. Of course, this has a profound effect on the arithmetic operations that are part of the integer data type, and they are certainly different from the operations of the unsigned integer data type.
We probably should illustrate this quickly. Addition of unsigned integers (that is, elements of the unsigned integer data type), works as you might have learned in elementary school:
  0100 0000 0000 0000 0000 0000 0000 0000
+
  0100 0000 0000 0000 0000 0000 0000 0000
---------------------------------------
  1000 0000 0000 0000 0000 0000 0000 0000
Clearly, this is not correct for the signed integer data type, because the sum of two positive numbers (sign bit = 0) cannot equal a negative number. Thus, addition must be defined differently for these two data types.
We will discuss signed and unsigned data types and the two's complement representation in detail in the next chapter.

Page 43
   
  The Visual Basic integer data type  
   
  Visual Basic's integer data type is not the same as Win32's integer data type (as implemented by VC++, for instance). The VB integer data type is a 16-bit signed data type. In particular, its set of values is the set of integers from -215 to 215-1, each of which is represented by a 16-bit two's complement binary string.  
   
  The Visual Basic BSTR data type  
   
  As we will see in Chapter 6, Strings, the data type for a VB string is called the BSTR data type. It is a pointer data type whose ancillary data type is described as a length-preceded, null-terminated array of Unicode characters.  
 
  Fundamental and Derived Data Types  
   
  A fundamental data type is one that is not derived from another data type. For instance, the integer and long VB data types are fundamental. A derived data type is one that is constructed from fundamental data types. Some examples of derived types are:  
   
  Arrays  
   
  User-defined types (which we will refer to from now on by their C++ name, structures)  
   
  Pointers  
   
  (Arrays and pointers have ancillary data types whereas structures do not.)  
 
  Visual Basic Data Types  
   
  The Visual Basic data types are listed in Table 4-1.  
Table 4-1. VB Data Types
Type Size in Bytes Range of Values
Byte 1 0 to 255
Boolean 2 True and False keywords
Integer 2 -32,768 to 32,767
Long (long integer) 4 -2,147,483,648 to 2,147,483,647
Single (single-precision real) 4 Approximately -3.4E38 to 3.4E38
Double (double-precision real) 8 Approximately -1.8E308 to 4.9E324
Currency (scaled integer) 8 Approximately -922,337,203,685,477.5808 to 922,337,203,685,477.5807


   
  (table continued on next page.)  
Page 44
   
  (table continued from previous page.)  
Table 4-1. VB Data Types (continued)
Type Size in Bytes Range of Values
Date 8 1/1/100 to 12/31/9999
Object 4  
String (BSTR) See Chapter 6.  
Fixed-length string 2 bytes for each character  
Variant 16  
Array    
User-defined type (or structure)    


   
  Let us make a few comments about these data types:  
   
  The Boolean data type is a special 16-bit data type with underlying set {True, False}. Nonetheless, True is stored as -1 and False as 0.  
   
  The Byte data type is the only unsigned data type in VB.  
   
  The term string is used in an ambiguous way in Visual Basic. We will clarify this in Chapter 6.  
   
  We will often refer to user-defined types as structures (which is the term used in VC++).  
   
  Visual Basic does not have pointer data types, but BSTRs are pointers, as we will see in Chapter 6.  
   
  Variants  
   
  Variants do not occur very often in Win32 API programming, but they do occur in OLE-related situations, so let us discuss them briefly. A Variant is a special data type that is designed to hold data of different types. It can hold data of any other VB type except fixed-length string. This includes the derived types: array and user-defined type. In addition, a Variant can contain one of four special types of values:  
 
  Null
Indicates that the variable does not contain valid data
 
 
  Nothing
A special object type indicating that the Variant has type object (as reported by VarType) but does not currently point to a valid Object
 
 
  Empty
Indicates that the variable has not yet been assigned a value; that is, the variable is uninitialized
 
 
  An error code
Indicates that the contents are an error number
 
Page 45
   
  To help understand these values and variants in general, we need to take a look at the internal workings of a Variant.  
   
  All variants are 16 bytes long. The first two bytes contain a code that indicates the current type of data stored in the Variant. This code is returned by the VarType function. Specifically, the return values of VarType are shown in Table 4-2.  
Table 4-2. The Return Values of VarType
Constant Value Description
vbEmpty 0 Empty (uninitialized)
vbNull 1 Null (no valid data)
vbInteger 2 Integer
vbLong 3 Long integer
vbSingle 4 Single-precision floating-point number
vbDouble 5 Double-precision floating-point number
vbCurrency 6 Currency value
vbDate 7 Date value
vbString 8 String
vbObject 9 Object
vbError 10 Error value
vbBoolean 11 Boolean value
vbVariant 12 Variant (used only with arrays of variants)
vbDataObject 13 A data access object
vbDecimal 14 Decimal value
vbByte 17 Byte value
vbUserDefinedType 36 Variants that contain user-defined types
vbArray 8192 Array


   
  The TypeName function returns a string description of the type stored in a Variant.  
   
  The ShowVariant procedure in Example 4-1 can be used to peek inside a Variant.  
   
  Example 4-1. The ShowVariant Procedure  
   
  Sub ShowVariant()

Dim i As Integer
Dim aBytes(1 To 50) As Byte
Dim v As Variant

GoSub PrintVariant
Set v = Nothing
GoSub PrintVariant
v = Null
GoSub PrintVariant
 
Page 46
   
  Example 4-1. The ShowVariant Procedure(continued)  
   
  v = Empty
GoSub PrintVariant
v = CVErr(123)
GoSub PrintVariant
v = aBytes
GoSub PrintVariant
v = 123
GoSub PrintVariant
v = 100000000
GoSub PrintVariant
v =  sssssssssssssssssssssssss
GoSub PrintVariant
v = CSng(1.1)
GoSub PrintVariant
v = 10000000000.9
GoSub PrintVariant
v = CCur(23.34)
GoSub PrintVariant
v = CByte(123)
GoSub PrintVariant
v = True
GoSub PrintVariant
v = #12/12/1998#
GoSub PrintVariant

Exit Sub
PrintVariant:
Dim bVar(1 To 16) As Byte
CopyMemory bVar(1), v, 16
Debug.Print VarType(v) &  / ; TypeName(v) &  : ;
For i = 1 To 16
   Debug.Print bVar(i) &  / ;
Next
Debug.Print
Return
End Sub
 
   
  The output is:  
 
  0/Empty:0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/
9/Nothing:9/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/
1/Null:1/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/
0/Empty:0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/
10/Error:10/0/0/0/0/0/0/0/123/0/10/128/0/0/0/0/
8209/Byte():17/32/0/0/0/0/0/0/208/254/31/0/0/0/0/0/
2/Integer:2/0/0/0/0/0/0/0/123/0/18/0/0/0/0/0/
3/Long:3/0/0/0/0/0/0/0/0/225/245/5/0/0/0/0/
8/String:8/0/0/0/0/0/0/0/116/34/29/0/0/0/0/0/
4/Single:4/0/0/0/0/0/0/0/205/204/140/63/0/0/0/0/
5/Double:5/0/0/0/0/0/0/0/51/51/7/32/95/160/2/66/
6/Currency:6/0/0/0/0/0/0/0/184/143/3/0/0/0/0/0/
17/Byte:17/0/0/0/0/0/0/0/123/0/3/0/0/0/0/0/
11/Boolean:11/0/0/0/0/0/0/0/255/255/3/0/0/0/0/0/
7/Date:7/0/0/0/0/0/0/0/0/0/0/0/160/165/225/64/
 
Page 47
   
  Note that TypeName returns an indication when the data contained in a Variant is an array by appending a pair of parentheses after the type name of the ancillary array type.  
   
  Note also that in order to set a Variant to Nothing, we must use the Set statement, which makes sense because Nothing is a special type of object variable.  
   
  As you may know, the purpose of the Error data type is to allow a function to return an error code that can be distinguished from an otherwise meaningful numeric return value. For instance, consider the following function:  
 
  Function vLength(s As String) As Variant
   If s <> "" Then
      vLength = Len(s)
   Else
      ' Convert return value to error code and return
      vLength = CVErr(1)
   End If
End Function
 
   
  This function is meant to return a long value indicating the length of a nonempty string. If the string is empty, the return value is 1. But how do we tell from the return type the difference between an empty string and a string of length 1? The answer is given in the following code:  
 
  Debug.Print IsError(vLength("help"))
Debug.Print IsError(vLength(""))
 
   
  The first line returns False whereas the second line returns True. We trust that you can infer from this trivial little example how to use error codes. (If you are interested, there is more on this issue, along with a whole chapter on error handling, in my book Concepts of Object-Oriented Programming with Visual Basic, published by Springer-Verlag, New York.)  
 
  Basic VC++ Data Types  
   
  The world of data types is considerably more complex in VC++ than it is in VB. We want to deal first with the fundamental data types, as seen by the designer of the C++ language (Bjarne Stroustrup) as well as by Microsoft. Before doing so, let us discuss the concept of a typedef.  
   
  TypeDefs  
   
  The purpose of a VC++ typedef statement is to allow the programmer to define a new name, or synonym, for an existing data type. The syntax is:  
 
  typedef type-declaration synonym;  
   
  For instance:  
 
  typedef long LPARAM;  
Page 48
   
  defines LPARAM as a synonym for the long data type. It is also possible to typedef more than one synonym at a time, as in:  
 
  typedef long LPARAM, WPARAM;  
   
  Visual Basic has no direct counterpart to the VC++ typedef statement.  
   
  Now we can proceed to a discussion of VC++ data types.  
   
  Char Data Types  
   
  The character data types are the most complex of the fundamental types. This is due primarily to the fact that these types are used to represent the characters that are supported by the operating system, and Windows supports both ANSI and Unicode characters (in general).  
   
  Designer of C++  
   
  The char type is designed to hold the characters of the basic character set used by the operating system.  
   
  The char, signed char, and unsigned char data types are different, although they may be implemented in the same manner by the compiler. They each must consume the same amount of memory.  
   
  Microsoft  
   
  To quote the documentation:  
  Type char is an integral type that usually contains members of the execution character set in Microsoft C++, this is ASCII. The C++ compiler treats variables of type char, signed char, and unsigned char as having different types. The char type is signed by default (but this can be changed through a VC++ compiler switch).  
   
  Thus, we should treat char as a single-byte signed data type, equivalent to signed char, with range -128 to 127. On the other hand, unsigned char is a single-byte data type with range 0 to 255.  
   
  It follows that unsigned char is the more natural type for representing ASCII (or ANSI) characters, which may lead you to wonder why Microsoft chose not to treat char as unsigned by default. In any case, Microsoft does define the BYTE data type as a synonym for unsigned char.  
   
  Unicode character types  
   
  What does Microsoft do about Unicode characters?  
   
  VC++ has another data type named wchar_t, which is a 16-bit unsigned integer data type that holds wide (Unicode) characters. Thus char and wchar_t handle the two types of characters in Win32.  
Page 49
   
  VC++ also defines some synonyms for these two character data types:  
 
  typedef char CHAR;
typedef wchar_t WCHAR;
 
   
  Furthermore, in order to allow programmers to write a single program that can be used under either an ANSI or a Unicode setting, VC++ defines the generic TCHAR data type using conditional compilation as follows:  
 
  #ifdef UNICODE

typedef WCHAR TCHAR;
typedef WCHAR TBYTE;

#else

typedef char TCHAR;
typedef unsigned char TBYTE;

#endif
 
   
  (Conditional compilation works in VC++ just like it does in VB.) If the conditional compilation constant UNICODE is defined in a program, then the first set of typedefs are valid:  
 
  typedef WCHAR TCHAR;
typedef WCHAR TBYTE;
 
   
  Thus, TCHAR and TBYTE are synonyms for the Unicode character WCHAR, which, in turn, is a synonym for wchar_t. On the other hand, if UNICODE is not defined, then TCHAR and TBYTE are synonyms for char and unsigned char, respectively.  
   
  In summary, we have the following character data types (along with their unsigned versions):  
 
  char and CHAR
ANSI character
 
 
  wchar_t and WCHAR
Unicode character
 
 
  TCHAR and TBYTE
Generic character, could be either ANSI or Unicode (but not both at the same time)
 
   
  Int Data Types  
   
  An integer data type is capable of expressing a whole number within a range defined by the number of bytes allocated to it. C++ supports a number of integer data types.  
Page 50
   
  Designer of C++  
   
  Three sizes of integer data types are possible: declared as short int, int, and long int. Using the sizeof operator to indicate the number of bits that a data type consumes in memory, we must have:  
 
  sizeof(short int) <= sizeof(int) <= sizeof(long int)  
   
  Note the equal signs, however, which allow all three integer types to have the same size.  
   
  The size of int should have the natural size determined by the machine architecture. Otherwise, the issue of size is compiler dependent. It is not uncommon for int to have the same size as either short int or long int. The following terms are equivalent:  
 
  short int = short = signed short int = signed short
int = signed int
long int = long = long short int = long short
 
   
  For each type short int, int, and long int there are corresponding unsigned versions, which have the same size as their signed counterparts. The terms unsigned int and unsigned are synonymous.  
   
  Microsoft  
   
  As with character data types, Microsoft follows the description of the designer of C++, setting the following sizes for Win32:  
 
  sizeof(short) = 16
sizeof(int) = 32
sizeof(long) = 32
 
   
  All of the char and int data types and their variations are considered integral data types.  
   
  Floating Point Data Types  
   
  A floating point data type is capable of representing a number within a fractional component defined by its format and the number of bytes allocated to it.  
   
  Designer of C++  
   
  There are three floating types: float, double, and long double. Each type, in turn, has at least as great precision as its predecessor.  
   
  Microsoft  
   
  Microsoft also defines these three floating point data types, with sizes:  
 
  sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 8
 
Page 51
   
  and states that ''the representation of long double and double is identical. However, long double and double are separate types." We will have very little occasion to use these nonintegral data types.  
   
  Other Data Types  
   
  Let us take a brief look at some other commonly occurring data types.  
   
  Void  
   
  The designer of C++ and Microsoft both define the void data type as a special data type that can be used as the return type for functions, but no variable can be declared as having type void. This type does have one other use, however, and that is to designate a pointer whose target type is unknown, as in the following example:  
 
  void *pWhatever;  
   
  FARPROC  
   
  The FARPROC data type appears quite often in Win32 API function. It is documented as a "pointer to a callback function." We will discuss callback functions in Chapter 15, Windows: The Basics, but the point here is that FARPROC, being a pointer, is a 32-bit unsigned long.  
   
  Handle  
   
  Windows is full of different types of objects, such as windows, bitmaps, fonts, cursors, menus, hooks, metafiles, pens, brushes, and so on. When an object of some type is created, Windows generally returns a handle to that object. This handle is used to manipulate the object programmatically. That is, most API functions that manipulate an object require a handle to that object.  
   
  Handles are not totally unknown to VB programmers, for we can access the handles of many objects through their hWnd property.  
   
  Although the type HANDLE is a synonym for void*, the data type occurs so often it seems reasonable to consider it a more or less fundamental data type.  
   
  Boolean data types  
   
  VC++ also defines some boolean data types:  
 
  bool
A 1-byte data type that takes on the values true and false (both of which are keywords, as in VB)
 
Page 52
 
  BOOL (remember that VC++ is case sensitive)
A 32-bit data type that is also designed to take on the values
true and false (don't ask me!)
 
 
  boolean and BOOLEAN
Also 32-bit data types that can take on the values true and false
 
   
  Summary  
   
  Table 4-3 summarizes the basic (fundamental and almost fundamental) VC++ data types that we will see frequently in dealing with Win32 API function declarations.  
Table 4-3. Basic VC++ Data Types
Type Size in Bytes Closest VB Counterpart Range of Values/Notes
char 1 Byte -128 to 127
signed char 1 Byte -128 to 127
unsigned char 1 Byte 0 to 255
BYTE 1 Byte 0 to 255
wchar_t 2 Integer 0 to 65,535
TCHAR 1 or 2 Byte/Integer See "Char Data Types"
(signed) short (int) 2 Integer -32768 to 32,767
(unsigned) short (int) 2 Integer 0 to 65,535
(signed) int 4 Long -2,147,483,648 to 2,147,483,647
unsigned int 4 Long 0 to 4,294,967,295 (=232-1)
(signed) long (int) 4 Long -2,147,483,648 to 2,147,483,647
unsigned long 4 Long 0 to 4,294,967,295 (=232-1)
float 4 Long Approximately -3.4E38 to 3.4E38
double 8 Double Approximately -1.8E308 to 4.9E324
long double 8 Double Approximately -1.8E308 to 4.9E324 (Not used often.)
bool 1 Byte true or false
BOOL 4 Long true or false
boolean 4 Long true or false
* 4 Long Denotes a pointer
void* 4 Long A pointer to an unknown data type
HANDLE 4   A handle to an object. Officially a void*.
FARPROC 4 Long Pointer to a callback function


   
  (table continued on next page.)  
Page 53
   
  (table continued from previous page.)  
Table 4-3. Basic VC++ Data Types (continued)
Type Size in Bytes Closest VB Counterpart Range of Values/Notes
__int8, __int16,
__int32, __int64
1,2,4,8 Byte/Integer/Long/none Microsoft specific. Not used often.
LONGLONG 8 Double 64-bit signed integer. Not used often.
VARIANT 16 Variant Same as the VB Variant


   
  As you can see from Table 4-3, we will not have too much trouble converting from a basic VC++ data type to a VB data type. The main complication concerns the fact that VB does not have unsigned versions of its types. We will discuss this issue when we talk in more detail about the nature of signed (two's complement) and unsigned representations in the next chapter.  
 
  Translating Derived Data Types  
   
  It might seem as though once we understand how to translate fundamental data types from VC++ to Visual Basic, it should be more-or-less routine to translate derived types as well. This is true with one or two exceptions.  
   
  After all, since pointers are 32 bits in length, a VC++ pointer should be translated as a VB long. It doesn't matter what the data type of the target is, at least as far as translating the pointer data type itself. Of course, we will probably need to translate this target data type as well.  
   
  Also, an array in VC++ is an array in VB only the ancillary data type needs translating.  
   
  There is a wrinkle, however, when it comes to translating VC++ structures into VB structures (user-defined types). We will discuss the matter of structure member alignment in detail (with experiments) a little later.  
 
  Win32 Typedefs  
   
  Although the fundamental VC++ data types number only about a dozen, it sometimes seems a cruel fact that Win32 defines literally hundreds of synonyms for these data types through typedef statements.  
   
  VC++'s various typedef statements are contained in a set of more than 300 include files. These are text files that usually have extension .h.  
Page 54
   
  The rpiAPIData Utility  
   
  The CD that accompanies this book contains a database application called rpiAPIData. This application can display data on Win32 data types, as shown in Figure 4-1.  
   
  0054-01.gif  
   
  Figure 4-1.
rpiAPIData: data type view
 
   
  rpiAPIData, which is little more than a front end for a database of Win32 typedefs, makes it easier to cut through this typedef alphabet soup and find the basic VC++ data type that most closely corresponds to a given synonym. The database contains more than 650 typedefs.  
   
  Before we continue, it is worth reiterating that, unlike VB, VC++ and Win32 are case-sensitive environments. This case-sensitivity is bound to catch all of us sooner or later.  
   
  We should point out that the include files contain many hundreds more typedefs than are included in the database. However, there is no need to catalog these individually, because they can often be easily recognized.  
   
  First, many typedefs are used to define both ANSI and Unicode versions of the same data type. For instance, the keywords OPENFILENAMEA and OPENFILENAMEW are the ANSI and Unicode versions, respectively, of the same data type (in this case, a structure). Accordingly, there are two typedefs defining OPENFILENAME (as with the TCHAR typedef discussed earlier in "Char Data Types").  
Page 55
   
  Second, Win32 is fond of defining new data type synonyms for pointers by prefixing the target data type name with either a P, NP, or LP. To illustrate, consider the int data type. Win32 makes the following typedefs:  
 
  typedef int* PINT;
typedef int
* LPINT;
 
   
  This happens for hundreds of different base data types. In an effort to make the rpiAPIData application more usable, it seemed appropriate not to include these variations.  
   
  In case you are wondering, the prefixes NP and LP stand for near pointer and far pointer, respectively. This terminology is a remnant of the days of 16-bit Windows and Intel 80286 processors, when addresses were written in a segment:offset format in order to accommodate the fact that CPU registers were 16 bits wide and could therefore address only 216 = 64KB of memory. A near pointer was a pointer within the same 64KB memory segment, and thus required only an offset value. A far pointer pointed to another memory segment. If this is not familiar terminology, then you can consider yourself fortunate. (For more on all of this, allow me to suggest my book Understanding Personal Computer Hardware, published by Springer-Verlag, New York.)  
   
  The designers of VC++ seem very fond of making typedefs using other typedefs. For instance, we find in two different include files:  
 
  typedef unsigned long DWORD;  
   
  and:  
 
  typedef DWORD LPARAM;  
   
  This is not a problem for VC++ programmers, but it does create a problem for us. After all, a VC++ programmer couldn't care less what size an LPARAM data type is, because he or she just uses the LPARAM keyword, as in:  
 
  LPARAM lparam;  
   
  However, we need to replace LPARAM with a suitable VB data type, so we need to know its size!  
   
  Thus, from our perspective, it would have been nice if all typedefs were given in terms of some small set of basic data types, Instead, we must engage in typedef chasing. The origin field in Figure 4-1 is intended to refer (more or less) to the end of this chase, that is, to the basic type from Table 4-3, for which the name in question is a synonym.  
   
  Please note that this is the first incarnation of rpiAPIData and it no doubt has many errors. Caveat emptor! Please send me an email when you find errors.  
Page 56
   
  Why All These Typedefs?  
   
  There is a reason why Microsoft makes so many typedefs (although one could argue that the situation has gotten a bit out of hand). Simply put, the numerous typedefs are intended to clarify programming code. (You can be the final judge of that.)  
   
  For instance, according to the Win32 documentation, an LPARAM is an unsigned long with a particular mission in life it is a 32-bit message parameter. To be more specific, it appears in the SendMessage API function declaration:  
 
  LRESULT SendMessage(
  HWND hWnd,         // handle of destination window
  UINT Msg,          // message to send
  WPARAM wParam,     // first message parameter
  LPARAM lParam      //  second message parameter
);
 
   
  Thus, for instance, the VC++ variable declaration:  
 
  LPARAM avar;  
   
  tells us that avar is intended to hold a 32-bit message parameter, for use in SendMessage. The alternative is:  
 
  unsigned long avar;  
   
  which tells us nothing about the intended use of avar.  
   
  The point is that the LPARAM typedef imparts a meaning, or interpretation, to any variable declared using that synonym. Otherwise, there is no difference between the declarations:  
 
  LPARAM avar1;
unsigned long avar2;
 
   
  If two data types are linked by a typedef statement, as in:  
 
  typedef DWORD LPARAM;  
   
  we may refer to them as synonymous data types.  
 
  An Example  
   
  We will get plenty of practice doing data type translation throughout the book, but let us pause now to do a short example translating the SendMessage function:  
 
  LRESULT SendMessage (
  HWND hWnd,         // handle of destination window
  UINT Msg,          // message to send
  WPARAM wParam,     // first message parameter
  LPARAM lParam      //  second message parameter
);
 
Page 57
   
  Since the Win32 documentation says that SendMessage is exported by USER32.DLL, and since we want the ANSI entry point, we can do a partial translation as follows:  
 
  Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
   HWND hWnd, _
   UINT Msg, _
   WPARAM wParam, _
   LPARAM lParam) _
As LRESULT
 
   
  Next, we need to translate the data types from VC++ to VB and decide on the use of ByVal or ByRef for each parameter.  
   
  Checking the origin and VB Declaration fields for each data type in the rpiAPIData utility gives the following table:  
VC++ Type Origin VB Type
LRESULT long Long
HWND void* Long
UINT unsigned int Long
WPARAM unsigned int Long
LPARAM long Long


   
  Next, we check the meaning of the parameters in the documentation. According to this documentation, the hWnd parameter holds a handle to the window that will receive the message. Since VB thoughtfully provides us with this handle as returned by the hWnd property, we should pass the parameter by value, as in:  
 
  Sendmessage(lstWhatever.hWnd,   
   
  The Msg parameter is a constant that identifies the message (the message ID). Again we pass this by value.  
   
  The parameters WPARAM and LPARAM are "message-specific" values, so we don't get much of a clue from the SendMessage documentation as to whether to pass these by value or by reference. Indeed, this may depend upon the message being sent.  
   
  Thus, we arrive at the following VB declaration for SendMessage:  
 
  Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
   ByVal hwnd As Long, _
   ByVal lMsg As Long, _
   wParam As Any, _
   lParam As Any _
) As Long
 
   
  As it happens, we often can be more specific about the declarations of wParam and lParam when using this function in a particular situation.  
Page 58
   
  For instance, to search for an item in a list box, we would send the message LB_FINDSTRING, that is, wMsg should be set to the symbolic constant LB_FINDSTRING. We will discuss how to find the value of symbolic constants in the Chapter 16, Windows Messages.  
   
  In this case, wParam is the index of the list box entry from which to start the search, or -1 to start from the beginning. Clearly, we will pass this number by value. Also, lParam is the string to search for. We will discuss strings in the next chapter, but for now we note that the appropriate choice is:  
 
  ByVal lParam As String  
   
  Thus, for sending the LB_FINDSTRING message (and a great many other LB messages), we can use the declaration:  
 
  Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" ( _
   ByVal hwnd As Long, _
   ByVal lMsg As Long, _
   ByVal wParam As Long, _
   ByVal lParam As String _
) As Long
 
   
  Similarly, when lParam is a long, we can use the declaration:  
 
  Declare Function SendMessageByLong Lib "user32" Alias "SendMessageA" ( _
   ByVal hwnd As Long, _
   ByVal lMsg As Long, _
   ByVal wParam As Long, _
   ByVal lParam As Long _
) As Long
 
 
  Structures and User-Defined Types  
   
  We mentioned earlier that there is a potential wrinkle when it comes to translating VC++ structures into VB user-defined types. Although this wrinkle appears very rarely, it is important to understand it, especially since Win32 uses literally hundreds of structures in its function declarations.  
   
  We might think at first that it is simply a matter of translating each member's data type from VC++ to VB. For instance, the VC++ structure:  
 
  struct tagMisaligned {
   char aByte;
   short int anInteger;
}
 
   
  might be translated as:  
 
  Type utMisaligned
   aByte As Byte
   anInteger As Integer
End Type
 
   
  Unfortunately, there is a problem.  
Page 59
   
  When Visual Basic stores a structure in memory (and only in memory this does not apply to storage on disk) it aligns the members of the structure on what are referred to as their natural boundaries. Quite simply, the natural boundary of a variable that takes up n bytes of memory is any memory address that is a multiple of n. Thus, we have:  
   
  A Byte variable can be located at any memory address.  
   
  An Integer variable is located at a memory address that is a multiple of 2.  
   
  A Long variable is located at a memory address that is a multiple of 4.  
   
  A Double variable is located at a memory address that is a multiple of 8.  
   
  A pointer variable, which includes the VB string type BSTR, is located at a memory address that is a multiple of 4.  
   
  A Unicode character is located at a memory address that is a multiple of 2. In particular, this applies to the characters in a fixed-length string.  
   
  In addition, the entire structure is aligned on a memory address that is divisible by 4, but this is not relevant to the issue at hand.  
   
  Let's test this with the structure:  
 
  Public Type utMisaligned
   aByte As Byte
   aLong As Long
   anInteger As Integer
   aString As String
End Type
 
   
  The following code makes a copy of the bytes of the structure as Windows sees that structure:  
 
  Dim i As Integer
Dim aBytes (1 To 50) As Byte
Dim ma As utMisaligned

ma.aByte = &H11
ma.aLong = &H22334455
ma.anInteger = &H6677
ma.aString = "help"

CopyMemory aBytes(1), ma, LenB(ma)

' Print the length of ma using both Len and LenB
Debug.Print "Len:" & Len(ma), " LenB:" & LenB(ma)

' Print the byte array
For i = 1 To LenB(ma)
   Debug.Print Hex(aBytes(i)) & "/";
Next
Debug.Print
 
Page 60
   
  The output is:  
 
  Len:11           LenB:16
11/0/0/0/55/44/33/22/77/66/0/0/B4/4A/20/0/
 
   
  This shows first that Len (ma) and LenB (ma) report different values. As the VB documentation states, Len (ma) returns the number of bytes used by the structure itself, which is 1 + 4 + 2 + 4 = 11 (a VB string is a 4-byte pointer). On the other hand, LenB (ma) returns the number of bytes used to store the structure in memory, which, in this case, is 16. Looking at the byte array, we see:  
   
  1 byte for ma.aByte  
   
  3 bytes of padding so that ma.aLong can start on its natural boundary  
   
  4 bytes for ma.aLong  
   
  2 bytes for ma.anInteger, which does not require realignment since it is already on its natural boundary  
   
  2 bytes of padding so that ma.aString can start on its natural 4-byte boundary  
   
  4 bytes for ma.aString, which is an address  
   
  Fortunately, the vast majority of API structures have members that are naturally aligned, so the VB user-defined type exactly mirrors the VC++ structure. In the rare cases where the members of a VC++ structure are not naturally aligned, we must resort to some manual realigning. Since this situation is rare, it is simpler to use a third-party program for this purpose. An alternative would be to create a byte array and carefully fill it with data at the appropriate locations.  
 
  Flags  
   
  A flag is a binary word that indicates the state of a certain object. Often, flags are 1 bit long. For instance, to indicate whether or not a window is active requires only a single bit 0 for inactive and 1 for active. On the other hand, some flags require more than one bit. As you no doubt know, an n-bit flag can represent any of 2n states.  
   
  In Win32, several flags are often embedded in a single 16-bit or 32-bit word, which would generally be defined as an unsigned short or unsigned int, respectively. Often the entire word is referred to as a flag.  
   
  Thus, it is important to be able to get at the individual bits of a flag. This is easy to do in VC++ and not too much harder in VB.  
   
  Let us first discuss a bit of terminology. When a numeric variable is being thought of as a flag, we will refer to it as a binary word. The word is also said to be bit-significant. The least significant bit is the rightmost bit, which is also referred to as  
Page 61
   
  bit 0. (The term least significant loses its meaning for bit-significant words, but the terminology is still used.)  
   
  Setting a bit means setting the bit to 1, and clearing a bit means setting the bit to 0.  
   
  Masking Bits  
   
  Visual Basic's logical And, Or, and Not operators also function as bitwise operators, meaning that when applied to numeric data types, they perform bitwise conjunction and disjunction, respectively. In particular:  
 
  0 And 0 = 0
0 And 1 = 0
1 And 0 = 0
1 And 1 = 1

0 Or 0 = 0
0 Or 1 = 1
1 Or 0 = 1
1 Or 1 = 1

Not 0 = 1
Not 1 = 0
 
   
  These operators can be used to set or clear any bit position within a binary word. To set a bit position, we Or it with a 1. To clear a bit position, we And it with a 0.  
   
  For instance, if x is an integer, then we can set its sixth bit by writing:  
 
  x Or 100000(binary)  
   
  Of course, VB does not recognize binary words, so we have the choice between:  
 
  x Or &H20  
   
  and:  
 
  x Or 2^5  
   
  This also makes it easy to extract the bit, as in:  
 
  FifthBit = IIf((x And &H20) = 0, 0, 1)  
   
  It can be a bit tedious trying to figure out what number to use as a mask. Here are some guidelines that use Table 4-4.  
   
  Masks are more easily constructed in hexadecimal format.  
   
  To set up a mask for clearing bits in a 16-bit word (for instance), begin by writing the all 1s mask:  
 
  (1111) (1111) (1111) (1111)  
 
  Next, change the 1 to a 0 in those positions that you wish to clear. For instance, to clear positions 0, 2, 5, and 10, use the mask:  
 
  (1111) (1011) (1101) (1010)  
Page 62
 
  Convert this to hexadecimal using Table 4-4. This gives &H0FBDA, so to clear these positions, write:  
 
  x = x And &H0FBDA  
   
  To set up a mask for setting bits in a 16-bit word (for instance), begin by writing the all 0s mask:  
 
  (0000) (0000) (0000) (0000)  
 
  Next, change the 0 to a 1 in those positions that you wish to set. For instance, to set positions 1, 7, 8, and 15, use the mask:  
 
  (1000) (0001) (1000) (0010)  
 
  Convert this to hexadecimal using Table 4-4. This gives &H8182. So, to set these positions, write:  
 
  x = x Or &H8182  
   
  If you want to set some bits and clear others, this should be done in two steps.  
Table 4-4. Some Hex/Binary Equivalents
Hex Binary
0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111


 
  Symbolic Constants  
   
  To put it simply, Win32 is full of symbolic constants well over 6,000 of them. These constants are defined in include files (files with extension .h) along with the typedefs. For instance, the Winbase.h file contains the following constant definition:  
 
  #define MAX_COMPUTERNAME_LENGTH 15  
Page 63
   
  In VB this is equivalent to:  
 
  Public Const MAX_COMPUTERNAME_LENGTH = 15  
   
  Now, VC++ programmers can include the appropriate include files in their programs with a statement such as:  
 
  #include <Winbase.h>  
   
  and then use the constants by name. In VB, we need to add a Const statement for each constant that we want to use. However, the problem is that the Win32 documentation does not give the values of these constants because VC++ programmers don't need to know them!  
   
  Accordingly, VB programmers need a good text searching utility that can be used to search through include files. For instance, searching for lines that contain both #define and MAX_COMPUTERNAME_LENGTH turns up the declaration of this constant.  
   
  An additional problem arises when searching in this way because a given #define statement may be part of a conditional complication clause and we need to examine the entire clause to be certain that we have the correct value.  
   
  The rpiAPIData application mentioned earlier lists almost 7000 constants and their values.  


WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net