3. API Declarations

Page 24
3. API Declarations
As we mentioned earlier, the Windows API contains well over 2,000 functions. Of course, since the API is intended to be used in the VC++ environment, all of the declarations are in the Visual C++ language (that is, the C++ language with Microsoft's VC++ extensions). Here is an example of an API declaration taken from the Win32 documentation on the MSDN Library CD:
LRESULT SendMessage(
  HWND hWnd,          // handle of destination window
  UINT Msg,           // message to send
  WPARAM wParam,      // first message parameter
  LPARAM lParam       // second message parameter
);
One possible translation into VB (and there are others, as we will see) is:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" ( _
   ByVal hwnd As Long, _
   ByVal lMsg As Long, _
   ByVal wParam As Long, _
   ByRef lParam As Any) _
As Long
In this chapter, we want to lay down a few basic principles for making such translations. We will assume that you are familiar with the concepts of passing parameters by value and by reference.
For convenience, we will refer to any procedure (function or subroutine) that resides in a DLL and is intended for export as an external function. (The procedure is external to the VB application that calls this function.)

Page 25
The VB Declare Statement
The Visual Basic Declare statement is used to call external functions that reside in DLLs. The statement must be used at the module level, rather than the procedure level. There are two syntaxes one for functions and one for subroutines:
[Public | Private] Declare Sub name Lib "libname" _
   [Alias "aliasname"] [([arglist])]
and:
[Public | Private] Declare Function name Lib "libname" _
   [Alias "aliasname"] [([arglist])] [As type]
where the items in square brackets are optional and the bar indicates alternative choices. You can get the complete details on this syntax in the VB help system. Let us discuss some of the highlights.
Public or Private?
When using the Declare statement in a standard code module, we can use the Public keyword to make the function accessible to the entire VB project. Within a form or class module, however, we must use Private or VB will complain.
The Calling Name
The name keyword should be replaced by the name of the function, as we intend to call it from our VB code. As we will see, at times there are good reasons for this name to differ from the actual function name as defined in the DLL. To use a different name, we must employ the Alias syntax, discussed below. It is important to note that, unlike names in VB, DLL function names are case-sensitive (as is everything in the VC++ language)!
The Export Library
The phrase:
Lib "libname"
is used to indicate the source DLL that exports the function. For Win32 API calls, this is usually (but not always) one of the "big three":
KERNEL32.DLL
USER32.DLL
GDI32.DLL
For these three DLLs, you can omit the extension .DLL in libname, as we did in the example at the beginning of this chapter.

Page 26
Aliases
As mentioned, there are several reasons for calling a DLL function using a name that is different from its true name, as defined in the DLL. This is done using the Alias keyword. Here are three compelling reasons to use aliases.
Avoiding VB keywords and illegal characters
Some API functions have the same names as VB functions. SetFocus is a case in point:
HWND SetFocus(HWND hWnd);
We cannot declare this in VB as:
Declare Function SetFocus Lib "user32" (ByVal hwnd As Long) As Long
because SetFocus is a VB keyword. But we can declare it as:
Declare Function SetFocusAPI Lib "user32" Alias "SetFocus" ( _
   ByVal hwnd As Long) As Long
In addition, some API functions use characters that are illegal in VB, as with the file functions _hread, _hwrite, _lclose, and so on, which begin with an underscore. We can alias these names to names that omit the leading underscore.
Fortunately, not many API functions either are VB keywords or contain illegal characters, but when it does happen, we would not be able to use them in VB were it not for aliases.
ANSI versus Unicode
The Win32 API functions that use string parameters generally come in two versions one for ANSI and one for Unicode. The SendMessage function defined at the beginning of this chapter is a case in point.
There are actually two different SendMessage functions defined in the USER32.DLL library. The ANSI version is called SendMessageA and the Unicode version is called SendMessageW (the W is for wide). In the jargon, we would say that there are two entry points for the SendMessage function. Rather than sprinkling our code with As or Ws, it makes more sense to make the choice once in the declaration and then use the unadorned word SendMessage throughout the code. For one thing, this would make any future changes simpler.
As we have said, Windows NT supports Unicode. It therefore implements the Unicode entry points for API functions that have string parameters. For compatibility, Windows NT also implements the ANSI entry points, but these implementations generally just make the necessary conversions, call the corresponding Unicode version of the function, and convert back to ANSI!

Page 27
On the other hand, Windows 9x generally does not support Unicode, except for some limited situations. According to Microsoft:
Windows 95 does not implement the Unicode (or wide character) version of most Win32 functions that take string parameters. With some exceptions, these functions are implemented as stubs that simply return success without modifying any arguments.
This seems rather ill advised. Would it not be better to return a value that does not indicate success when the function has done nothing?
In any case, there are some exceptions to this rule. In particular, Windows 9x does support Unicode entry points in the OLE-related API functions, as well as a handful of other API functions, as shown in Table 3-1.
Table 3-1. The Unicode API Functions Implemented in Windows 9x
EnumResourceLanguages
FindResourceEx
GetTextExtentPoint
EnumResourceNames
GetCharWidth
lstreln
EnumResourceTypes
GetCommandLine
MessageBoxEx
ExtTextOut
GetTextExtentExPoint
MessageBox
FindResource
GetTextExtentPoint32
TextOut


In addition, Windows 9x implements two conversion functions: MultiByteToWideChar and WideCharToMultiByte.
Let us take special note of the fact that Windows 9x does implement the Unicode version of lstrlen (which is called lstrlenW). This function returns the number of characters in a null-terminated Unicode character array. This will prove to be useful to us later in the book.
As we will see in Chapter 6, Strings, the best course of action for a VB programmer is simply to call the ANSI entry points, since they are compatible with both Windows 9x and Windows NT.
Parameter type declarations
The third reason to use aliases has to do with the type declarations of the parameters in a Declare statement. We will examine this issue in detail later in this chapter.
Suffice it to say now that we may want to declare several versions of a single external function using different data types for some of the parameters. One reason for this is to take advantage of VB's type-checking capabilities to catch type-declaration errors before they reach the DLL, when they will probably cause a General Protection Fault (GPF). To do so, we will need to use aliases.

Page 28
The Parameter List
The arglist is a list of parameters and their data types. Its (simplified) syntax is:
[ByVal | ByRef] varname[( )] [As type]
where the optional parentheses following varname are required for array variables.
The type keyword can be any of the following: Byte, Boolean, Integer, Long, Currency, Single, Double, Date, String (variable length only), Variant, user-defined, or object. (Actually, fixed-length strings can appear as procedure arguments, but they are converted to variable-length strings before being passed to the function.)
The VC-to-VB Translation Game Plan
Generally speaking, translating a VC++-style API function declaration into VB involves the following steps:
1. Get the library name (DLL name and path) for the Lib clause of the VB declaration.
2. Translate the return type of the function from a VC++ data type to a VB data type.
3. Translate each parameter declaration from VC++ to VB. This involves choosing a VB data type and also deciding whether to use ByVal or ByRef.
4. Decide upon an alias name, if necessary. This can be done for descriptive reasons or for the reasons discussed earlier.
No doubt the most difficult part of this translation process is the parameter data type translation process, so let us elaborate on this.
Passing Parameters to an External Function
The procedure for parameter declarations and parameter passing for external DLL functions is quite similar to that of ordinary VB functions except in three cases: strings, addresses, and structures (user-defined types). However, handling strings and structures just involves handling addresses, so it all boils down to addresses, that is, to pointers. So let us discuss pointers now. We will consider strings in Chapter 6 and structures in Chapter 4, Data Types.
The most important thing to remember about parameter declaration and parameter passing when it comes to external DLL functions is that we are really contending with two declarations the defining declaration in the DLL as well as the VB

Page 29
declaration in the Declare statement. Our job is to match the VB Declare statement to the defining DLL declaration.
In particular, it is important to keep in mind that any ByVal and ByRef keywords that appear in a Declare statement are instructions to Visual Basic, not to the DLL.
An Example to Illustrate the Possibilities
We can illustrate these issues using a simple external function:
short WINAPI rpiAddOne(short *pVar)
{
   return ++(
*pVar);
}
Note that this function expects a pointer to a VC++ short, which is the same as a VB integer, as an argument. In other words, we need to pass the address of an integer variable. The function then just increments the value of that integer, but this really doesn't matter; we are interested only in how to declare and pass the parameter.
Now, there are three cases to consider. Note that these are not mutually exclusive we will often have a choice between them.
Case 1
In some cases, we will have the address of the integer variable in another variable, that is, we will have a pointer to the integer variable. This may happen because this address was returned to us by another API function, for instance. Say the address is in the long variable pTargetVar. In this case, we want to pass the contents of the pointer variable. For this, we should make the VB declaration (note the alias, because there will be other versions):
Public Declare Sub rpiAddOneByValAsLong Lib "rpiAPI.dll" Alias "rpiAddOne" ( _
   ByVal pTargetVar As Long)
and then make the call:
rpiAddOneByValAsLong pTargetVar
Case 2
If we are not given the address, we can always generate it using VarPtr or rpiVarPtr. Suppose that the target variable is iTargetVar. Then we can use the same declaration as in the previous case and make the call:
rpiAddOneByValAsLong VarPtr(iTargetVar)
In either situation, we are passing the actual address of the integer variable, as desired.

Page 30
Case 3
If we are not given the address explicitly and do not want to use the VarPtr or rpiVarPtr functions, we can pass the target variable by reference, letting VB generate and pass its address for us. After all, that is precisely what passing by reference means! In this case, we make a different VB declaration:
Public Declare Sub rpiAddOneByRefAsInteger Lib "rpiAPI.dll" Alias "rpiAddOne" ( _
   ByVal Var As Integer)
and then make the call:
rpiAddOneByRefAsInteger iTargetVar
Note that the ByRef declaration is type-specific, in this case to the type Integer.
Passing a Parameter as Any
Many API functions expect a pointer as an argument, but don't care about the type of the target variable. In fact, my rpiVarPtr is one such function. To emphasize this fact, I could have defined this function in rpiAPI.dll as follows:
void* WINAPI rpiVarPtr(void *pVar)
{
   return pVar;
}
The parameter declaration:
void *pVar
says that pVar is a void pointer, which is VC++'s way of saying that it doesn't care what the pointer is pointing to. We will encounter void pointers often in API functions.
In any case, since I didn't care what type of pointer was coming in to the function, and since all pointers are VC++ integers, I made the equivalent definition:
int WINAPI rpiVarPtr(int pVar)
{
   return pVar;
}
The point here is that to use a VB declaration of the form of case 3 above, using ByRef parameter passing, we would need a separate declaration (alias) for each target data type, as in:
Public Declare Function rpiVarPtrByRefAsByte Lib "rpiAPI.dll"_
Alias "rpiVarPtr" ( _
   ByRef pVar As Byte _
) As Long

Public Declare Function rpiVarPtrByRefAsInteger Lib "rpiAPI.dll" _
Alias "rpiVarPtr" ( _

Page 31
   ByRef pVar As Integer _
) As Long

Public Declare Function rpiVarPtrByRefAsLong Lib "rpiAPI.dll" _
Alias "rpiVarPtr" ( _
   ByRef pVar As Long _
) As Long
To avoid this, VB supports the As Any declaration:
Public Declare Function rpiVarPtrByRefAsAny Lib "rpiAPI.dll"_
Alias "rpiVarPtr" ( _
   ByRef pVar As Any _
) As Long
This type-insensitive declaration is more flexible, but also has the disadvantage that it suppresses VB's data type checking. This can be dangerous, because if a type error gets past VB and reaches the DLL, it is likely to cause a GPF.
In any case, the use of As Any can be a time saver. While some authors advise against it, I say it's up to you. Use it with circumspection and you can save some extra coding work. Use it carelessly and the amount of work you save will pale in comparison to the amount of time you will spend chasing down the cause of GPFs!
ByVal Again
We should conclude with a little-known fact about ByVal. Namely, we can override the default ByRef setting for an external function and only for an external function by including the word ByVal in the function call. For example, consider the CopyMemory function:
VOID CopyMemory(
  PVOID Destination,  // pointer to address of copy destination
  CONST VOID 
*Source, // pointer to address of block to copy
  DWORD Length        // size, in bytes, of block to copy
);
The first parameter is the address of the destination variable of the copy operation. (We will discuss this function in detail a bit later, so don't worry about that now.)
One possible VB declaration of CopyMemory is:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
   Dest As Any, _
   Source As Any, _
   ByVal cbCopy As Long)
Thus, the following VB code will copy the contents of the integer variable iSource to the integer variable iDest.

Page 32
Dim iSource As Integer
Dim iDest As Integer
iSource = 5

CopyMemory iDest, iSource, 2
After running this code, iDest will contain 5.
Alternatively, we could replace the call to CopyMemory with:
CopyMemory ByVal VarPtr(iDest), iSource, 2
Admittedly, while there is no reason to do this here, there will be cases when we want to pass by value instead of by reference but don't want to create a separate VB declaration for this purpose.
CopyMemory A VB Hacker's Dream
One of the API functions we will use most often is CopyMemory. The purpose of CopyMemory is simply to copy a block of memory byte-by-byte from one memory address to another. This opens up a whole new set of possibilities, since VB does not have this sort of capability, except in the rather restricted form of LSet, and even then, the documentation recommends against using LSet for this purpose.
The CopyMemory function has an interesting history. Actually, it is an alias for the RtlMoveMemory API function. Apparently, the name CopyMemory was first coined by Bruce McKinney, the author of Hardcore Visual Basic. Nevertheless, CopyMemory is now in the official Microsoft documentation as:
VOID CopyMemory(
  PVOID Destination,  // pointer to address of copy destination
  CONST VOID 
*Source, // pointer to address of block to copy
  DWORD Length        // size, in bytes, of block to copy
);
The keyword CONST simply means that the function CopyMemory guarantees not to make any changes to the argument Source. Since PVOID is a synonym for VOID *, the two parameters Source and Destination have the same data type a pointer to a target variable of unknown type.
The simplest VB declaration for CopyMemory is:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpDest As Any, _
lpSource As Any, _
ByVal cbCopy As Long)
In this case, lpDest is the address of the first byte of the destination memory, lpSource is the address of the first byte of the source memory and cbCopy is the number of bytes to copy (the cb in cbCopy stands for count of bytes).

Page 33
We will use this form often, but also take advantage of the fact that we can override the ByRef setting by including ByVal in the call to this function, as described earlier.
A Simple Example
Let us consider an example. The following code:
Dim lng As Long
Dim i As Integer
Dim bArray(1 To 4) As Byte

lng = &H4030201

CopyMemory bArray(1), lng, 4
For i = 1 To 4
   Debug.Print Hex(bArray(i));
Next
copies the long variable lng, byte-by-byte, to the 4-byte array and then prints those bytes. The output is:
1 2 3 4
This code shows three interesting things. First, the bytes of a long are stored in memory with the lowest order byte stored at the lowest address, as shown in Figure 3-1.
0033-01.gif
Figure 3-1.
The long value &H4030201 stored in memory at address Addr
This method of storing words in memory is referred to as little endian. (For more on such things, allow me to refer you to my book Understanding Personal Computer Hardware, published by Springer-Verlag, New York.)
Second, the address of a long is the address of the lowest order byte. Third, the code is also one way to extract bytes from an integer or a long.
A More Interesting Example
CopyMemory is very useful for exploring beneath the hood of VB and its interactions with the Win32 API. To illustrate, let's try a little experiment with strings.

Page 34
Given a VB string, we allocate a byte array, move the bytes from the string to that array, and then examine those bytes.
Here is the code. We will discuss the reason for using ByVal with the source string in Chapter 6, so don't worry about that now:
Dim sString As String
Dim aBytes(1 To 20) As Byte
Dim i As Integer

sString = "help"

CopyMemory aBytes(1), ByVal sString, LenB(sString)

' Print bytes
For i = 1 To 20
   Debug.Print aBytes(i);
Next
The output from this program is:
104 101 108 112 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
The string officially ends with the first null character (ASCII 0). Note that it appears that the string is in ANSI format one byte per character. But wait.
Another approach to getting the bytes from the VB string is to declare a long variable and point it to the same character array as the string. We will go into the details of this in Chapter 6, but here is the code:
Dim sString As String
Dim aBytes(1 To 20) As Byte
Dim i As Integer
Dim lng As Long

sString = "help"

' Get contents of BSTR variable
lng = StrPtr(sString)

' Now copy as array
CopyMemory aBytes(1), ByVal lng, LenB(sString)

' Print bytes
For i = 1 To 20
   Debug.Print aBytes(i);
Next
This time, the output is:
104 0 101 0 108 0 112 0 0 0 0 0 0 0 0 0 0 0 0 0
which is definitely Unicode (two bytes per character), which seems to contradict the previous output.

Page 35
As we will see, the fact is that when we pass a string to an external function, such as CopyMemory, VB translates the string from Unicode to ANSI. This is why the first output is in ANSI format. However, we were able to bypass VB's translation in the second call to CopyMemory by avoiding VB strings altogether. This is why the output is Unicode.
We will explain this in more detail in Chapter 6. For now, we wanted simply to point out how useful CopyMemory can be for peering into the inner workings of VB.
Implementing Indirection in Visual Basic
As we mentioned during our discussion of pointers in Chapter 2, Preliminaries, CopyMemory provides a simple way to implement the C++ indirection operator (*), which retrieves the value of the variable pointed to by a pointer variable. In particular, suppose we have a pointer variable pVar and we want the contents of the target variable, which in C++ would be easily obtained using the syntax *pVar.
In VB, we can use the following CopyMemory alias:
Declare Sub VBGetTarget Lib "kernel32" Alias "RtlMoveMemory" ( _
   Target As Any, _
   ByVal lPointer As Long, _
   ByVal cbCopy As Long)
To use this, we need to declare a variable of the same type as the target:
Dim Target As TargetType
and make the call:
VBGetTarget Target, lPointer, BytesToCopy
Since Target is passed by reference, the function receives the address of this variable. On the other hand, since Pointer is passed by value, the function gets the address of the actual target. Thus, the contents of the target are placed in the Target variable. Voil .
If you want to give this a try, just run the following code:
Dim Pointer As Long
Dim Target As Integer
Dim i As Integer
i = 123
' Get a pointer
Pointer = VarPtr(i)
' Get the target
VBGetTarget Target, Pointer, LenB(Target)
Debug.Print Target
As an alternative, you can use the following functions in the rpiAPIDLL.

Page 36
rpiGetTargetByte
rpiGetTargetInteger
rpiGetTargetLong
rpiGetTarget64
These functions are defined in the DLL as follows:
unsigned char WINAPI rpiGetTargetByte(unsigned char *pByte)
{
   return 
*pByte;      // return the byte target value
}

short int WINAPI rpiGetTargetInteger(short int 
*pShort)
{
   return 
*pShort;     // return the integer target value
}

int WINAPI rpiGetTargetLong(int 
*pLong)
{
   return 
*pLong;      // return the long target value
}

_int64 WINAPI rpiGetTarget64(_int64 
*p64)
{
   return 
*p64;        // return the long target value
}
Each function takes a pointer to the target variable passed by value and simply returns the target value. Here are the VB declarations:
Public Declare Function rpiGetTargetByte Lib "rpiAPI.dll" ( _
   ByVal pByte As Long) As Byte

Public Declare Function rpiGetTargetInteger Lib "rpiAPI.dll" ( _
   ByVal pInteger As Long) As Integer

Public Declare Function rpiGetTargetLong Lib "rpiAPI.dll" ( _
   ByVal pLong As Long) As Long

Public Declare Function rpiGetTarget64 Lib "rpiAPI.dll" ( _
   ByVal p64 As Long) As Currency
Dealing with API Errors
In some sense, two types of errors can occur when calling API functions: errors that crash the application and errors that do not crash the application.
When the Program Crashes
If a call to an API function results in a GPF, such as that shown in Figure 3-2, then we might be able to get some help from the memory address shown in the dialog box, but for the most part, we are on our own.

Page 37
0037-01.gif
Figure 3-2.
A GPF
Here are some things to do, not necessarily in this order:
Place a breakpoint in your code on the API call that caused the GPF. It may take some experimenting (and several more crashes) to figure out which line actually caused the GPF. Inspect the arguments to the API function very carefully to see if there is an obvious inappropriate value. For instance, often an argument that should be an address or handle is equal to 0. This is definitely going to cause a GPF.
Check the function declaration for the correct use of ByVal and ByRef. It is hard to overestimate the number of times that this is the culprit.
Check the translation from C++ to VB to make sure that the parameters were translated correctly.
When the Program Does Not Crash Win32 Error Messages
Assuming that a crash does not occur, many API functions will return some useful error information. This can happen in a variety of ways. Some functions return zero on success and a nonzero value on failure. This nonzero value may or may not indicate the source of the problem. You will need to check the documentation to determine the meaning of such returned values. Many API functions return zero on failure and a nonzero value on success. In this case, the return value is no help in tracking down the problem. However, there is still hope.
LastDLLError
Several hundred of the API procedures set an internal error code that is accessible using the GetLastError API function, whose syntax is simply:
DWORD GetLastError(void);

Page 38
Or in VB:
Declare Function GetLastError Lib "kernel32" () As Long
Apparently, however, Visual Basic may alter the value returned by GetLastError before we have a chance to examine that value. Fortunately, VB stores this return value in the LastDLLError property of its App object, so we can get at it in this way. The property should be accessed immediately after the function call, however.
Note that an API function error does not cause VB to raise an error, so we need to write our own error-trapping code to catch API errors.
FormatMessage
The FormatMessage API function can be used to retrieve the text of a Win32 error message, given the error code.
The rather formidable declaration of FormatMessage is:
DWORD FormatMessage(
   DWORD dwFlags, // source and processing options
   LPCVOID lpSource, // pointer to message source
   DWORD dwMessageId, // requested message identifier
   DWORD dwLanguageId, // language identifier for requested message
   LPTSTR lpBuffer, // pointer to message buffer
   DWORD nSize, // maximum size of message buffer
   va_list 
*Arguments // address of array of message inserts
);
Here is a VB version:
Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" ( _
   ByVal dwFlags As Long, _
   lpSource As Any, _
   ByVal dwMessageId As Long, _
   ByVal dwLanguageId As Long, _
   ByVal lpBuffer As String, _
   ByVal nSize As Long, _
   ByVal Arguments As Long _
) As Long
We will not go into the details of this complex function. Instead, we will just use it to return a formatted Win32 error message as shown in the GetErrorText function below. (Aside from incorporating this code into your VB projects, you could make a small add-in that returns the message from the error code for quick debugging.)
To use the function, you will, of course, need to include the VB declaration of FormatMessage, along with the following constant declarations:
Public Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Public Const FORMAT_MESSAGE_IGNORE_INSERTS = &H200

Public Function GetAPIErrorText(ByVal lError As Long) As String

Page 39
   Dim sOut As String
   Dim sMsg As String
   Dim lret As Long

   GetErrorText = ""
   sMsg = String$(256, 0)

   lret = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM Or _
                    FORMAT_MESSAGE_IGNORE_INSERTS, _
                    0&, lError, 0&, sMsg, Len(sMsg), 0&)

   sOut = "Error: " & lError & "(&H" & Hex(lError) & "): "
   If lret <> 0 Then
      ' Check for ending vbcrlf
      sMsg = Trim0(sMsg)
      If Right$(sMsg, 2) = vbCrLf Then sMsg = Left$(sMsg, Len(sMsg) - 2)
      sOut = sOut & Trim0(sMsg)
   Else
      sOut = sOut & "<No such error>"
   End If

   GetErrorText = sOut

End Function
Imagine now that we have just called an API function that returns 0 on failure and, according to its documentation, sets GetLastError. For example, the following code uses the GetClassName function to get the class name of a window from its handle. (We will discuss class names and handles in the chapters on windows.) The function returns 0 if there is an error:
Dim s As String
s = String(256, 0)
hnd = Command1.hwnd
lret = GetClassName(hnd, s, 255)

' Check for error
If lret = 0 Then
MsgBox GetAPIErrorText(Err.LastDllError)
End If
If Command1 is a real command button, this should work with no errors. If we replace Command1.hWnd with 0, the result will be the dialog box in Figure 3-3.
We will occasionally use the following function, which uses GetAPIErrorText to raise an error in VB. Note that the function adds a sufficiently large number to the original error number so as not to interfere with other error numbers:
Public Sub RaiseApiError(ByVal e As Long)
Err.Raise vbObjectError + 29000 + e, App.EXEName & ".Windows", GetAPIErrorText(e)
End Sub

Page 40
0040-01.gif
Figure 3-3.
An API error message
Now that we have set down some basic principles for dealing with API errors, we may, for the sake of space, ignore these principles for most of the examples in this book.



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