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.
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.
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.
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.
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.
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]
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.