| ||
To begin with, consider how to call API functions. You can open the help file and choose any API function; for now, choose MessageBox:
int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
This function displays a message box with one or more command buttons that allow you to close it:
hWnd The descriptor (handle) of the message box window.
lpText The text that will be displayed in this message box.
lpCaption The window header text.
uType The window type; in particular, it allows you to define the number of command buttons.
Now, consider the parameter types. All parameters are 32-bit integer numbers :
HWND 32-bit integer
LPCTSTR 32-bit pointer to the string
UINT 32-bit integer
For a reason that will be explained later, you will have to add the A suffix to function names . Besides this, when using MASM, it is necessary to add the @16 suffix to the function names. Thus, the call to the previously described function will look as follows :
CALL MessageBoxA@16
What about parameters? They must have been loaded beforehand into the stack using PUSH commands. Memorize a simple rule: from left to right, and from bottom to top. Thus, assume that the window descriptor is located by the address HW , the strings are located by the addresses STR1 and STR2 , and the message box type is a constant. The simplest type has the zero value and is called MB_OK. Thus, you'll have the following:
MB_OK equ 0 . . STR1 DB "Invalid entry! ", 0 STR2 DB "Error message.", 0 HW DWORD ? . . PUSH MB_OK PUSH OFFSET STR1 PUSH OFFSET STR2 PUSH HW CALL MessageBoxA@16
As you can see, everything is easy and straightforward, as if you were calling this function from a program written in C or Pascal. The result returned by any function is usually an integer number returned into the EAX register.
Strings deserve to be considered separately. Generally, when speaking about strings, one usually means a string address (or pointer). In most cases, it is assumed that the string is terminated by the character with the code 0. However, one of the parameters of an API function often specifies the string length (in documentation often called the buffer) defined by another parameter. You should bear this in mind, because it might happen that the returned string won't be terminated by zero, and zero is required to use that string in another function. If this possibility is not taken into account, this might result in phantom errors in your program (i.e., such errors that appear or disappear like ghosts).
Note | Microsoft declares that when returning from an API function, the registers EBX , EBP , ESP , ESI , and EDI must be preserved. The function value is normally returned into the EAX register. The contents of other registers are not guaranteed to be preserved. |
C structures can be easily reproduced in Assembly language using a similar approach. For example, consider the following structure defining a system message:
typedef struct tagMSG { //Msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;
This message, supplied with detailed comments, will appear in one of the examples provided later. In Assembly language, this structure will appear as follows:
MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS
As you can see, in Assembly language everything looks even simpler. To tell the truth, the only thing difficult to understand is why Microsoft has muddled everything to such an extent with its attitude about variable types.
Note | When calling an API function, an error can occur related either to invalid input parameters or to the impossibility of obtaining the desired results because of a specific system condition. In such cases, the contents returned in the EAX register serve as an indicator. Unfortunately, for different functions, these values might be different. Most frequently this is 0, but it also can be ˆ 1 or any other nonzero value. In this relationship, recall that 0 in most programming languages is the synonym of the FALSE value, and the true value is mapped to 1. In any particular case, I recommend that you consult the documentation. Using the GetLastError function, it is possible to get the code of the error that occurred last. This will explain why the API function couldn't complete successfully. Examples using the GetLastError function will be provided later. For decoding error codes, it is convenient to use the ERRLOOK.EXE program supplied as part of Microsoft Visual Studio.NET. |
| ||