7.3.7 Calling Procedures with INVOKE

INVOKE generates a sequence of instructions that push arguments and call a procedure. This helps maintain code if arguments or langtype for a procedure is changed. INVOKE generates procedure calls and automatically handles the following tasks:

Converts arguments to the expected types

Pushes arguments on the stack in the correct order

Cleans up the stack when the procedure returns

If arguments do not match in number or if the type is not one the assembler can convert, an error results.

If VARARG is an option in a procedure, INVOKE can pass arguments in addition to those in the parameter list without generating an error or warning. The extra arguments must be at the end of the INVOKE argument list. All other arguments must match in number and type.

The syntax for INVOKE is

INVOKE expression [[,arguments]]

where expression can be the procedure's label or an indirect reference to a procedure, and arguments can be an expression, a register pair, or an expression preceded with ADDR. (The ADDR operator is discussed below.)

Procedures that have these procedure prototypes

addup PROTO NEAR C argcount:WORD, arg2:WORD, arg3:WORD

myproc PROTO FAR C, argcount:WORD, arg2:VARARG

and these procedure declarations

addup PROC NEAR C, argcount:WORD, arg2:WORD, arg3:WORD

myproc PROC FAR C PUBLIC <callcount> USES di si,

argcount:WORD,

arg2:VARARG

may have INVOKE statements that look like this:

INVOKE addup, ax, x, y

INVOKE myproc, bx, cx, 100, 10

The assembler can convert some arguments and parameter type combinations so that the correct type can be passed. The signed or unsigned qualities of the arguments in the INVOKE statements determine how the assembler converts them to the types expected by the procedure.

The addup procedure, for example, expects parameters of type WORD, but the arguments passed by INVOKE to the addup procedure can be any of these types:

BYTE, SBYTE, WORD, or SWORD

An expression whose type is specified with the PTR operator to be one of those types

An 8-bit or 16-bit register

An immediate expression in the range –32K to +64K

A NEAR PTR

If the type is smaller than that expected by the procedure, MASM widens the argument to match.

7.3.7.1 Widening Arguments

For INVOKE to correctly handle type conversions, you must use the signed data types for any signed assignments. This list shows the cases in which MASM widens an argument to match the type expected by a procedure's parameters.

Type Passed Type Expected  
BYTE, SBYTE WORD, SWORD, DWORD, SDWORD  
WORD, SWORD DWORD, SDWORD  

Summary: When possible, MASM widens arguments to match parameter types.

The assembler generates instructions such as XOR and CBW to perform the conversion. You can see these generated instructions in the listing file by using the /Sg command-line option. The assembler can extend a segment if far data is expected, and it can convert the type given in the list to the types expected. If the assembler cannot convert the type, however, it generates an error.

7.3.7.2 Detecting Errors

When the assembler widens arguments, it may require the use of a register that could overwrite another argument.

For example, if a procedure with the C calling convention is called with this INVOKE statement,

INVOKE myprocA, ax, cx, 100, arg

where arg is a BYTE variable and myproc expects four arguments of type WORD, the assembler widens and then pushes the variable with this code:

mov al, DGROUP:arg

xor ah, ah

push ax

As a result, the assembler generates code that also uses the AX register and therefore overwrites the first argument passed to the procedure in AX. The assembler generates an error in this case, requiring you to rewrite the INVOKE statement for this procedure.

The INVOKE directive uses as few registers as possible. However, widening arguments or pushing constants on the 8088 and 8086 requires the use of the AX register, and sometimes the DX register or the EAX and EDX on the 80386/486. This means that the content of AL, AH, AX, and EAX must frequently be overwritten, so you should avoid using these registers to pass arguments. As an alternative you can use DL, DH, DX, and EDX, since these registers are rarely used.

7.3.7.3 Invoking Far Addresses

You can pass a FAR pointer in a segment::offset pair, as shown below. Note the use of double colons to separate the register pair. The registers could be any other register pair, including a pair that a DOS call uses to return values.

FPWORD TYPEDEF FAR PTR WORD

SomeProc PROTO var1:DWORD, var2:WORD, var3:WORD

pfaritem FPWORD faritem

.

.

.

les bx, pfaritem

INVOKE SomeProc, ES::BX, arg1, arg2

However, you cannot give INVOKE two arguments, one for the segment and one for the offset, and have INVOKE combine the two for an address.

7.3.7.4 Passing an Address

You can use the ADDR operator to pass the address of an expression to a procedure that is expecting a NEAR or FAR pointer. This example generates code to pass a far pointer (to arg1) to the procedure proc1.

PBYTE TYPEDEF FAR PTR BYTE

arg1 BYTE "This is a string"

proc1 PROTO NEAR C fparg:PBYTE

.

.

.

INVOKE proc1, ADDR arg1

See Section 3.3.1 for information on defining pointers with TYPEDEF.

7.3.7.5 Invoking Procedures Indirectly

You can make an indirect procedure call such as call [bx + si] by using a pointer to a function prototype with TYPEDEF, as shown in this example:

FUNCPROTO TYPEDEF PROTO NEAR ARG1:WORD, ARG2:WORD

FUNCPTR TYPEDEF PTR FUNCPROTO

.DATA

pfunc FUNCPTR OFFSET proc1, OFFSET proc2

.CODE

mov si, Num ; Num contains 0 or 2

INVOKE FUNCPTR PTR [si] ; Selects proc1 or proc2

You can also use ASSUME to accomplish the same task. The ASSUME statement associates the type PFUNC with the BX register.

ASSUME BX:FUNCPTR

mov si, Num

INVOKE FUNCPTR PTR [bx+si]

7.3.7.6 Checking the Code Generated

The INVOKE directive generates code that may vary depending on the processor mode and calling conventions in effect. You can check your listing files to see the code generated by the INVOKE directive if you use the /Sg command-line option.