18.2 The proc and invoke Macros

PModeLib uses the 32-bit C calling convention. See the first page of the PModeLib Reference (Appendix A) for a quick overview, or Chapter 8 for a more detailed look at how this calling convention works.

Because some of the functions in PModeLib can take many parameters, a set of macros which implement the C calling convention has been provided to make it easier to both write and use these functions.

18.2.1 Using invoke

We highly recommend using the invoke macro to call the library functions, as Example 18-1 demonstrates.

Example 18-1. Using the invoke macro

        invoke  _FindGraphicsMode, word 640, word 480, word 32, dword 1
        invoke  _SetGraphicsMode, ax
        invoke  _CopyToScreen, dword Image, dword 320*4, dword 0, \
                               dword 0, dword 320, dword 240, dword 160, dword 120
        invoke  _UnsetGraphicsMode

18.2.2 Writing Functions using proc

Using invoke makes calling functions using the C calling convention a lot easier, but what about the other side: what facility makes writing functions that use the C calling convention easier? The answer is proc (and its companion endproc). PModeLib itself uses the proc and endproc macros, so its source code (located in the src directory in V:\ece291\pmodelib) contains many examples of the use of these two macros.

Tip: Look at the PModeLib source as a guide on writing code in protected mode, as well as a guide on how to write functions using the C calling convention!

Let's examine a very simple function that takes two parameters. We'll examine three versions: one uses registers to pass in the parameters, one uses the C calling convention but doesn't use the proc macro, and the last uses the C calling convention along with the proc and endproc macros.

18.2.2.1 Diff with Register Inputs

GLOBAL _main

SECTION .text

_main:
        mov     eax, 5
        mov     ebx, 3
        call    Diff
        ; Result in eax (should be 2)
        ret

; Purpose: Subtracts ebx from eax.
; Inputs:  eax, number to be subtracted from
;          ebx, amount to subtract
; Outputs: eax, result of subtraction
Diff
        sub     eax, ebx
        ret

18.2.2.2 Diff with C Calling Convention (but without proc)

Suppose for a moment that Diff was a much more complex function with more arguments. Or that it needs to be called from some C source code. Or that it just needs to use the C calling convention because every other function in the program uses the C calling convention. No matter the reason, let's rewrite Diff so that it uses the C calling convention instead of registers to get its parameters. Let's assume for the moment that we don't have the proc or invoke macros, and see what the code looks like.

GLOBAL _main

SECTION .text

_main:
        ; Parameters pushed in reverse order
        push    dword 3
        push    dword 5
        call    _Diff
        add     esp, 8          ; Throw away parameters (still on stack)
        ; Result in eax (should be 2)
        ret

; int Diff(int a, b);
; Purpose: Subtracts b from a.
; Inputs:  a, number to be subtracted from
;          b, amount to subtract
; Outputs: Returns a-b.
_Diff                           ; Prepended underscore to function name
        push    ebp             ; Save caller's stack frame
        mov     ebp, esp        ; Establish new stack frame
        mov     eax, [ebp+8]    ; Get the first parameter
        sub     eax, [ebp+12]   ; Subtract the second parameter
        pop     ebp             ; Restore the base pointer
        ret                     ; Return to caller, with result in eax

18.2.2.3 Diff Using proc

Whew! That's quite a mess of code, and there's a lot for us to keep track of and remember, even for a function that just takes two parameters: the arguments have to be passed in reverse order, 8 needs to be added to ESP after the call, EBP needs to be played with inside the function (pushed, modified, and popped), and the "magic" constants 8 and 12 need to be used to get the parameters off the stack! Let's use the proc, endproc, and invoke macros to clean this code up, and make it easier to read, maintain, and write correctly in the first place!

%include "lib291.inc"   ; Defines proc, endproc, invoke

GLOBAL _main

SECTION .text

_main:
        invoke  _Diff, dword 5, dword 3
        ; Result in eax (should be 2)
        ret

; int Diff(int a, b);
; Purpose: Subtracts b from a.
; Inputs:  a, number to be subtracted from
;          b, amount to subtract
; Outputs: Returns a-b.
proc _Diff      ; "proc" followed by name prepended with underscore

.a      arg     4        ; First parameter, dword (4 bytes)
.b      arg     4        ; Second parameter, dword (4 bytes)

        mov     eax, [ebp+.a]    ; Get the first parameter
        sub     eax, [ebp+.b]    ; Subtract the second parameter
        ret                      ; Return to caller, with result in eax

endproc
_Diff_arglen    equ      8       ; Sum of all parameter sizes

That is a lot easier to read! Comparing it to the non-proc version, it's easy to see exactly what proc, endproc, and invoke do, and to a certain extent how they work. For example, the invoke macro knows how much to add to ESP after the call by looking for _Diff_arglen, which is why it must be present and be equal to the sum of all parameter sizes.

18.2.3 Pointer Parameters

Several of the PModeLib functions take pointer parameters. A pointer paramter is simply a parameter that takes the address of a value rather than the value itself. Let's rewrite Diff one more time, with a few changes:

Wait a second! The output is passed as a pointer!? Yes, and in fact this is a common way for a function to return multiple values, or return a value as well as an error code. As a pointer (or address) of the output is passed, the function knows where to store the result, and thus just stores the result to that address before returning. Let's take a look at the new Diff!

%include "lib291.inc"   ; Defines proc, endproc, invoke

GLOBAL _main

SECTION .data

; As we're passing addresses, the values (and result) have to be in memory.
val1    dw 5
val2    dw 3

SECTION .bss

result  resw 1          ; The result is unknown, so put it in .bss

SECTION .text

_main:
        ; Pass addresses of memory variables.  Even though the values are
        ;  words, their *addresses* are dwords!
        invoke  _Diff, dword result, dword val1, dword val2
        ; Result in result variable (should be 2)
        ; As it's a "void" function, disregard eax value
        ret

; void Diff(short *r, short *a, short *b);
; Purpose: Subtracts b from a.
; Inputs:  a, number to be subtracted from
;          b, amount to subtract
; Outputs: r, result of subtraction (a-b)
proc _Diff

.r      arg     4        ; Note that even though the parameters being
.a      arg     4        ;  pointed to are words (2 bytes), the pointers
.b      arg     4        ;  themselves (the parameters) are dwords!

        push    esi              ; We have to save esi and edi.
        push    edi              ; Yes, this function could be coded without
                                 ;  using them, but just as an example...

        ; Load values of a and b, and do subtraction.
        mov     esi, [ebp+.a]    ; Get "a" *offset*
        mov     cx, [esi]        ; Get "a" *value*
        mov     ebx, [ebp+.b]    ; Get "b" *offset*
        sub     cx, [ebx]        ; Subtract "b" *value* from "a" value

        ; Store result into variable pointed to by r.
        mov     edi, [ebp+.r]    ; Get "r" *offset*
        mov     [edi], cx        ; Store result

        pop     edi      ; Restore saved registers before ret
        pop     esi
        ret              ; Return to caller, eax can be anything

endproc
_Diff_arglen    equ      12      ; Sum of all parameter sizes

Passing pointers is inefficient for such a simple function, but is invaluable for functions that need to return more than one value (eg, return a value into one of the pointed-to parameters as well as return a value in the "normal" fashion in EAX) or that take entire structures as parameters (just the starting offset of the structure can be passed on the stack, rather than every variable in the structure). Pointers/offsets/addresses can be used in very powerful ways.

Tip: For practice using and writing functions using proc and pointer parameters, rewrite the "dspmsg" example program (the one rewritten using PModeLib in Section 18.4) to split the "dspmsg" functionality into a separate function that uses the C calling convention:

void dspmsg(char *string);

and then call the dspmsg function, using the invoke macro, from the main program.