13.4. Understanding C Parameters

Most APIs were written by C programmers, so most APIs are written specifically for the C language. Because of this, there are many APIs that are completely unusable by VBA, because VBA does not support some of the data types required by these APIs. Of those APIs that are accessible to VBA, most require consideration with regard to the data types used. Use a wrong data type and your computer will quickly let you know all about it.

The following sections describe the C data types often specified for API parameters, the VBA data types that should be used with them, the recommended calling conventions, and, where applicable, the technique that converts signed integer values from unsigned integer values and vice versa.

13.4.1. Signed and Unsigned Integers

The first thing to note at this point is that the C language uses something that is unknown to VBA: signed numbers. VBA only uses unsigned numbers. For example, Figure 13-6 shows an 8-bit byte. Notice that having a binary 1 in the most significant bit (the eighth bit) signifies that the number contained within the byte is a negative number. A 0 in the same position indicates that the number is positive.

Figure 13.6. Figure 13-6

Although the total number of values that a single byte can hold (employing both positive and negative number ranges) increases, the number of possible positive numbers decreases by a factor of 27, 215, or 231, depending on the width of the byte.

Sometimes you'll run across parameters that require a signed value. There is a general algorithm to apply to supply this kind of value.

Convert the value to its next highest data type, subtract the maximum value that the data type can carry, and then convert it back to the original data type. The following table shows how to convert values from unsigned to signed, and vice versa, for the bit widths of values supported by VBA.

TypeConvert unsigned to signedConvert signed to unsigned
8-bitsigned = Cbyte(unsigned – 255)unsigned = (CInt(signed) And 255)
16-bitsigned = Cint(unsigned – 65535)unsigned = (CLng(signed) And 65535)
32-bitsigned = CLng(unsigned – 1.79769313486232E308)unsigned = (CDbl(signed) And 1.79769313486232E308)

13.4.2. 8-Bit Numeric Parameters

There are three or four 8-bit (1-byte) parameter types: char, uchar, BYTE, and (if using the ANSI character set) 8-bit TCHAR. Although it is unlikely that you will run across them underWin32, they should be explained just in case.

Signed 8-bit values range between −128 and 127, whereas unsigned values range between 0 and 255. The reason for this is explained in the preceding section. VBA only supports unsigned Byte values, regardless of size.

If you do ever have to use unsigned 8-bit parameters, the VBA Byte data type works just fine. Supplying 8-bit signed values is not as straightforward and requires a small algorithm to produce the required value. This algorithm is shown in the previous section, "Signed and Unsigned Integers."

VBA parameter: ByVal param As Byte

Data TypePrefixDescription
CharCh8-bit signed integer
BYTE, ucharCh8-bit unsigned integer
TCHARCh8-bit or 16-bit signed (depending on whether you're using ANSI or Unicode)

13.4.3. 16-Bit Numeric Parameters

There are three or four 16-bit (2-byte) numeric parameter types: short, unsigned short, WORD and (if using the Unicode character set) 16-bit TCHAR. The VBA Integer data type can be used to supply values for the signed parameters (short and 16-bit TCHAR), because VBA Integer values range from −32,768 to 32,767.

To supply unsigned values to unsigned short and WORD parameters, you must first convert the value to something that the API will recognize as an unsigned value. Refer to the conversion formula shown in the section "Signed and Unsigned Integers."

VBA parameter: ByVal param As Integer

DatatypePrefixDescription
ShortC16-bit signed integer
unsignedW16-bit unsigned integer
short, WORD TCHARCh8-bit or 16-bit signed (depending on whether you're using ANSI or Unicode)

13.4.4. 32-Bit Numeric Parameters

The six 32-bit (4-byte) numeric parameters include int, unsigned int, long, unsigned long, and DWORD.

The VBA Long Integer data type ranges between −2,147,483,648 and 2,147,483,647 and is equivalent to the signed C Integer types (int and long). As such, it can be used anywhere they appear. The unsigned type must be converted; see the conversion formula in the section "Signed and Unsigned Integers."

VBA parameter: ByVal param As Long

DatatypePrefixDescription
IntN32-bit signed integer
LongL32-bit signed integer
Unsigned int,N32-bit unsigned integer
UINT  
Unsigned long, DWORDDw32-bit unsigned integer (also referred to as a double-word)

13.4.5. Currency Parameters

The Win32 API does not use a Currency data type. There should be no conversion issues for any that do.

VBA parameter: None.

13.4.6. Floating-Point Parameters

There are very few APIs that use the Single (Float) or Double data types.

Single values are 32-bit (4-bytes) wide and range between −3.402823E38 and −1.401298E–45 for negative values, and from 1.401298E–45 to 3.402823E38 for positive values.

Double values are 64-bit (8-bytes) wide, and range from −1.79769313486231E308 to −4.94065645841247E–324 for negative values and from 4.94065645841247E–324 to 1.79769313486232E308 for positive values.

If you are supplying values to DLLs that use floating-point parameters, you must ensure that they are compatible with VBA, because not all are.

VBA parameter: Check DLL documentation.

13.4.7. Boolean Parameters

The VBA Long Integer data type can be used to supply a value for the BOOL parameter type.

However, both C and VBA define the Boolean False as zero. By default, that means all nonzero values must not be False, thereby True.

The C language defines a Boolean True to be 1, whereas VBA defines True as −1.

Supplying a VBA True value can sometimes lead to problems when using some API functions, due to the way in which Boolean values are interpreted in the C language, because some APIs return any nonzero value as True.

The C language includes both logical and Boolean operations. The logical Not operation in C is distinguished by a tilde preceding the number or variable (~myvar). The Boolean Not operator is distinguished by an exclamation mark (!myvar).

If (in C) you perform a logical Not operation on the True value (~1), you get zero. If you do the same thing for the VBA True (~−1), you get zero. No problem, as long as both values are nonzero. Since VBA doesn't have a logical Not operator, performing a Not against both values returns −2 and 0, respectively.

To get around this problem, specify Abs(booMyVar), when supplying a Boolean parameter, and Not (booMyVar = False), when checking a return value.

DatatypePrefixDescription
BOOLB32-bit Boolean value. Zero is False; one is True (although any nonzero VBA value will equate to True).

13.4.8. Handle Parameters

AWindows handle is a 32-bit (4-byte) number that identifies the memory location where the definition of a Windows object, such as a window, can be found. Handles come in different flavors, including HANDLE, hwnd, hDC, and so on.

For example, the hwnd of the Access application is aWindows handle that can be discovered using VBA code. The following table illustrates how to find the hwnd for familiar Access objects:

To find the hwnd of this Do this: Access object: 
The Access applicationApplication.hWndAccessApp
An Access formMe.hwnd
A user control on a form
Private Declare Function GetFocus Lib "user32" () As Long
Function GetCtrlhWnd(ctrl As Control) As Long
'Set the focus to the control
ctrl.SetFocus
'Get the hwnd from the API
GetCtrlhWnd = GetFocus
End Function


Since handles are all 32-bits wide, the VBA Long data type suits this purpose nicely.

VBA parameter: ByVal param As Long

DatatypePrefixDescription
HANDLE, hwnd,H32-bit unsigned integer. Handle to a Windows object.

13.4.9. Object Parameters

As far as the Windows API is concerned, the Object data type has no meaning. However, the OLE 2.0 API set does support parameters of this type, namely, LPUNKNOWN and LPDISPATCH, which are pointers to COM interfaces.

Therefore, if you ever have to specify an Object pointer, use the VBA Object data type.

VBA parameter: ByVal param As Object

Data TypePrefixDescription
LPUNKNOWN, LPDISPATCHDw32-bit pointer to an IUnknown OLE 2.0 interface

13.4.10. String Parameters

Strings are represented in C by the LPSTR data type, which is a pointer to the memory location where the string begins. Most DLLs require Null-terminated strings, which VBA can pass ByVal. Some C-language DLLs might return LPSTR pointers that can be copied to VBA strings using API functions, but only those APIs that were specifically written for VB (like APIGID32.dll) actually accept or return VBA strings. Those that do so will declare its parameter using $ or As String.

A Null-terminated string is not the same as a null string or an empty string. A Null-terminated string is a string that ends in an ASCII Null character. To form such a string, you append an ASCII zero:

param = "abc" & Chr$(0)

A Null string (empty string), on the other hand, is an empty string, which is formed like this:

param = ""

A Null value string is formed by appending a Null character, or by using the vbNullString constant:

param = Chr(0), or
param = vbNullString

Special note: DLLs do not always return strings directly. They do so by modifying strings that you pass as one of their parameters. When you pass a string parameter to an API that will modify that string, you must preinitialize the string with sufficient characters to take the value that will be returned. The API function will provide another parameter to accept a number that represents the length of the string. You can preinitialize the string using the VBA Space() function, and the Len(strMyString)+1 construct can be used to specify its length. Notice that we used +1; this is because you must account for the length of the string you expect to get back, plus the terminating Null character.

For example, to find the Windows folder (the one that contains most of the Windows application and initialization files), use the following:

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

Private Const MAXLEN = 255

Public Function WindowsDir() As String
    Dim strDirectory As String
    Dim lngSize As Long
    Dim lngReturn As Long

    'Pre-initialize the string
    strDirectory = Space(MAXLEN) & Chr(0)
    'Initialize the string length
    lngSize = MAXLEN + 1

    'Retrieve the length of the string returned by the function
    lngReturn = GetWindowsDirectory(strDirectory, lngSize)

    If lngReturn <> 0 Then
    'Return the string containing the Windows directory,
    'using lngReturn to specify how long the string is.
        WindowsDir = left(strDirectory, lngReturn)
    Else
        WindowsDir = ""
    End If
End Function

VBA parameter: ByVal param As String

Data TypePrefixDescription
LPSTR, LPCSTR,lpsz32-bit (long) pointer to a C Null-terminated string
LPTSTR  

13.4.11. Variant Parameters

VBA Variant data types are not supported under the core Win32 APIs. The only ones to use it are those of the OLE 2.0 specification, in which case the VBA Variant data type can be used without conversion.

VBA parameter: ByVal param As Variant

13.4.12. Pointers to Numeric Values

There are a lot of pointers in the C language. Pointers to numeric values, such as LPINT and LPSHORT, can be passed by VBA simply by using the ByRef keyword (or just by omitting the ByVal keyword).

You must ensure, however, that the data type you pass matches what is required. For example, if a 32-bit data type is required, pass a Long Integer, not an Integer (16-bit). If you do pass an Integer when a Long Integer is required, the DLL will write not only into the 16 bits of the Integer, but also into the next 16 bits. This can cause all sorts of problems, from erroneous data to a system crash.

VBA parameters: ByRef param As Integer

param As Long

Data TypePrefixDescription
LPSHORTLps16-bit (short) pointer to a 16-bit signed integer
LPINTLpi32-bit (long) pointer to a 32-bit signed integer

13.4.13. Pointers to C Structures

C-language structures are essentially the same as VBA User-Defined Types (UDTs), which were described in Chapter 5. You pass a UDT as a DLL parameter ByRef, specifying the name declared using the Type keyword, but you must also ensure that all the UDT's members consist of data types that are compatible with the API, as described in this section.

You cannot pass a UDT ByVal.

VBA parameter: ByRef param As UDT name

Data TypePrefixDescription
LpstructnameLp32-bit (long) pointer to a structure or other data item

13.4.14. Pointers to Arrays

Passing arrays to APIs not specifically written for VBA is accomplished by ByRef, because those APIs expect a pointer to the first array element. Such APIs also expect a parameter that indicates the number of elements in the array.

There are three issues to be aware of when passing arrays. First, you cannot pass entire string arrays. You can pass single array elements; just not the entire thing.

Second, when passing an entire array, you do so by specifying the first array element in the call.

myArray(0)

Third, when specifying the number of elements, you must specify UBound(strMyArray)+1, because UBound() only returns the maximum numeric bound of the array, not the actual count of its elements. Remember also that specifying Option Base 1 will affect the number returned by UBound(). You can, of course, just specify a number; just make sure it reflects the actual number of array elements.

C-style APIs don't care much whether you're telling the truth about the number of elements in the array. If you tell it you have ten elements when you only have five, C will happily write to the space required for ten, regardless of whether they actually exist or not. Naturally this is going to have interesting side effects, which you may not be too happy about.

VBA parameter: ByRef param(0) As Long

Param(0) As Long

You can also pass array elements either singly or as a subset of the array. For example, if you want to get the hwnd of the window within which a specific xy co-ordinate exists, and you have an array that contains a number of xy co-ordinates, call the WindowFromPoint API like this:

Myhwnd = WindowFromPoint(lngPtArray(2), lngPtArray(3))

Arrays that were written specifically with VBA in mind (and these are rare) expect an OLE 2.0 SAFEARRAY structure, and so expect a pointer that is itself a pointer to the array. Therefore, in this case, you simply pass the VBA array. This makes sense if you consider a string variable as a single-element array.

VBA parameter: ByRef param() As Long

Param() As Long

13.4.15. Pointers to Functions

FARPROC and DLGPROC are examples of pointers to functions. These pointers are supplied so the API can execute a function as part of its own functionality. These functions are referred to as Callback functions.

You specify the memory address of a callback function using the VBA AddressOf operator, but it is worth noting that the AddressOf operator has certain limitations:

  • It can only be specified in a standard module—you can't use it in a class module.

  • It must precede an argument in an argument list, and the argument it precedes must be the name of a procedure (sub, function, or property).

The procedure whose location it returns must exist in the same project, so it can't be used with external functions declared with the Declare keyword, or with functions referenced from type libraries.

You can pass a function pointer to an As Any parameter (discussed in the next section), and also create your own callback functions in DLLs compiled in Visual Basic or C++. To work with AddressOf, these functions must use the _stdcall calling convention.

VBA parameter: AddressOf myFunc

Data TypePrefixDescription
FARPROC,lpfn32-bit (far) pointer to a function or procedure
DLGPROC  

13.4.16. The Any Datatype

Some DLL function parameters can accept different data types. Such parameters are declared using the As Any data type. Calling a DLL function with parameters declared As Any is inherently dangerous, because VBA doesn't perform any type checking on it. That is, VBA doesn't check that the data type you supply matches that which is required by the function. Therefore, you need to be absolutely certain that the data type you are supplying to the function is correct.

To avoid the hazards of passing such arguments, declare several versions of the same DLL function, giving each a different name (using the Alias keyword) and a different parameter data type.

VBA parameter: ByVal param As data type

ByRef param As data type

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.213.214