| January 2003 Laboratory Notes: Computer Engineering II | ||
|---|---|---|
| Prev | Chapter 18 Introduction to PModeLib | Next |
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.
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
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.
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
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
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.
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:
It subtracts two 16-bit integers, not two 32-bit integers (in C parlance that means they're "short" instead of "int").
The two parameters are passed as pointers.
The output is also passed as a pointer.
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:
and then call the dspmsg function, using the invoke macro, from the main program.