Declaring Procedure Parameters with the PROC Directive
|
int a = 3, b = 35; cout << addup(a, b); |
push b push a call addup add eSp, 8 call PutDec |
invoke addup, a, b call PutDec |
int addup(int x, int y) { return x + y; } |
addup PROC NEAR push eBp mov eBp, eSp mov eAx, [eBp+8] add eAx, [eBp+12] pop eBp ret addup ENDP |
addup PROC NEAR C, x:DWORD, y:DWORD mov eAx, x add eAx, y ret addup ENDP |
Reference - If the arguments for a procedure are pointers, the assembler does not generate any code to get the value or values that the pointers reference; your program must still explicitly treat the argument as a pointer. In the following example, even though the procedure declares the parameters as near pointers, you must code two MOV instructions to get the values of the parameters. The first MOV gets the address of the parameters, and the second MOV gets the parameter.
int a = 3, b = 35; cout << addup(a, b); |
push offset b push offset a call addup add eSp, 8 call PutDec |
invoke addup, ADDR a, ADDR b call PutDec |
int addup(int &x, int &y) { return x + y; } |
addup PROC NEAR push eBp mov eBp, eSp mov eBx, [eBp+8] mov eAx, [eBx] mov eBx, [eBp+12] add eAx, [eBx] pop eBp ret addup ENDP |
addup PROC NEAR C, x:NEAR PTR DWORD, y:NEAR PTR DWORD mov eBx, x mov eAx, [eBx] mov eBx, y add eAx, [eBx] ret addup ENDP |
Prototypes perform the same function as prototypes in C and other high-level languages. A procedure prototype includes the procedure name, the types, and (optionally) the names of all parameters the procedure expects. Prototypes usually are placed at the beginning of an assembly program or in a separate include file so the assembler encounters the prototype before the actual procedure. Prototypes enable the assembler to check for unmatched parameters and are especially useful for procedures called from other modules and other languages.
The following example illustrates how to define and then declare two typical procedures.
addup PROTO NEAR C, x : DWORD, y : DWORD
swap PROTO NEAR C, L : NEAR PTR DWORD, R : NEAR PTR DWORD
When you call a procedure with INVOKE, the assembler checks the arguments given by INVOKE against the parameters expected by the procedure. If the data types of the arguments do not match, MASM reports an error or converts the type to the expected type.
INVOKE generates procedure calls automatically which:
Converts arguments to the expected types.
Pushes arguments on the stack in the correct order specified by the model. C and stdcall models parameters are pushed right-to-left order.
Cleans the stack of parameters when the procedure returns.
Procedures with these prototypes
addup PROTO NEAR C, x : DWORD, y : DWORD
swap PROTO NEAR C, L : NEAR PTR DWORD, R : NEAR PTR DWORD
or these procedure declarations
addup PROC NEAR C, x : DWORD, y : DWORD
swap PROC NEAR C, L : NEAR PTR DWORD, R : NEAR PTR DWORD
can be called with INVOKE statements as:
INVOKE addup, eAx, eBx
INVOKE swap, ADDR A, ADDR B
In high-level languages, local variables are visible only within a procedure and are usually stored on the stack. In assembly-language programs, you can also have local variables. The LOCAL directive automatically allocates local variable space on the stack at the start of the procedure, defines a reference to the variable by its position in the stack, at the end of the procedure the local variable is deallocated by restoring the stack pointer.
In the following, the local variable sum is allocated by Sub eSp, 4 and deallocated by restoring the stack pointer in Mov eSp, eBp. Note that the local statement can also be used without the invoke.
int a = 3, b = 35; cout << addup(a, b); |
push b push a call addup add eSp, 8 call PutDec |
invoke addup, ADDR a, ADDR b call PutDec |
int addup(int x, int y) { int sum = x + y; return sum; } |
addup PROC NEAR push eBp mov eBp, eSp sub eSp, 4 mov eAx, [eBp+8] add eAx, [eBp+12] mov [eBp-4], eAx mov eAx, [eBp-4] mov eSp, eBp pop eBp ret addup ENDP |
addup PROC NEAR C, x:DWORD, y:DWORD local sum : DWORD mov eAx, x add eAx, y mov sum, eAx mov eAx, sum ret addup ENDP |
A slightly more challenging example that illustrates the added clarity of use of PROC/INVOKE/LOCAL directives.
int a = 5, b = 6; swap(a, b); |
.data A dd 5 B dd 6 .code main Proc Near push offset B push offset A call swap Add eSp, 8 Push 0 Call ExitProcess main Endp End main |
.data A dd 5 B dd 6 .code main Proc Near invoke swap, addr A, addr B invoke ExitProcess, 0 main Endp End main |
void swap(int &L, int &R) { int T; T=L; L=R; R=T; } |
swap proc near push eBp mov eBp, eSp sub eSp, 4 Mov eBx, [eBp+8] ; T = L Mov eAx, [eBx] Mov [eBp-4], eAx Mov eBx, [eBp+8] ; L = R Mov eSi, [eBp+12] Mov eAx, [eSi] Mov [eBx], eAx Mov eAx, [eBp-4] ; R = T Mov eSi, [eBp+12] Mov [eSi], eAx mov eSp, eBp pop eBp ret addup ENDP |
swap Proc Near C, L:NEAR PTR DWORD, R:NEAR PTR DWORD LOCAL T : DWORD Mov eBx, L ; T = L Mov eAx, [eBx] Mov T, eAx Mov eBx, L ; L = R Mov eSi, R Mov eAx, [eSi] Mov [eBx], eAx Mov eAx, T ; R = T Mov eSi, R Mov [eSi], eAx Ret swap Endp |
|