5.18 Procedure Pointers


5.18 Procedure Pointers

The 80x86 call instruction allows three basic forms: direct calls (via a procedure name), indirect calls through a 32-bit general purpose register, and indirect calls through a double word pointer variable. The call instruction supports the following (low level) syntax:

      call Procname;      // Direct call to procedure "Procname" (or stmt label).      call( Reg32 );      // Indirect call to procedure whose address appears                          // in the Reg32 general purpose 32-bit register.      call( dwordVar );   // Indirect call to the procedure whose address appears                          // in the dwordVar double word variable. 

The first form we've been using throughout this chapter, so there is little need to discuss it further here. The second form, the register indirect call, calls the procedure whose address is held in the specified 32-bit register. The address of a procedure is the byte address of the first instruction to execute within that procedure. Remember, on a Von Neumann architecture machine (like the 80x86), the system stores machine instructions in memory along with other data. The CPU fetches the instruction opcode values from memory prior to executing them. When you execute the register indirect call instruction, the 80x86 first pushes the return address onto the stack and then begins fetching the next opcode byte (instruction) from the address specified by the register's value.

The third form of the call instruction above fetches the address of some procedure's first instruction from a double word variable in memory. Although this instruction suggests that the call uses the displacement only addressing mode, you should realize that any legal memory addressing mode is legal here; e.g., "call( procPtrTable[ebx*4] );" is perfectly legitimate, this statement fetches the double word from the array of double words (procPtrTable) and calls the procedure whose address is the value contained within that double word.

HLA treats procedure names like static objects. Therefore, you can compute the address of a procedure by using the address-of ("&") operator along with the procedure's name or by using the lea instruction. For example, "&Procname" is the address of the very first instruction of the Procname procedure. So all three of the following code sequences wind up calling the Procname procedure:

      call Procname;      .      .      .      mov( &Procname, eax );      call( eax );      .      .      .      lea( eax, Procname );      call( eax ); 

Because the address of a procedure fits in a 32-bit object, you can store such an address into a double word variable; in fact, you can initialize a double word variable with the address of a procedure using code like the following:

      procedure p;      begin p;      end p;      .      .      . static      ptrToP: dword := &p;      .      .      .      call( ptrToP ); // Calls the "p" procedure if ptrToP has not changed. 

Because the use of procedure pointers occurs frequently in assembly language programs, HLA provides a special syntax for declaring procedure pointer variables and for calling procedures indirectly through such pointer variables. To declare a procedure pointer in an HLA program, you can use a variable declaration like the following:

 static      procPtr: procedure; 

Note that this syntax uses the keyword procedure as a data type. It follows the variable name and a colon in one of the variable declaration sections (static, readonly, storage, or var). This sets aside exactly four bytes of storage for the procPtr variable. To call the procedure whose address is held by procPtr, you can use either of the following two forms:

      call( procPtr );      // Low level syntax.      procPtr();            // High level language syntax. 

Note that the high level syntax for an indirect procedure call is identical to the high level syntax for a direct procedure call. HLA can figure out whether to use a direct call or an indirect call by the type of the identifier. If you've specified a variable name, HLA assumes it needs to use an indirect call; if you specify a procedure name, HLA uses a direct call.

Like all pointer objects, you should not attempt to indirectly call a procedure through a pointer variable unless you've initialized that variable with an appropriate address. There are two ways to initialize a procedure pointer variable: static and readonly objects allow an initializer, or you can compute the address of a routine (as a 32-bit value) and store that 32-bit address directly into the procedure pointer at runtime. The following code fragment demonstrates both ways you can initialize a procedure pointer:

 static      ProcPtr: procedure := &p; // Initialize ProcPtr with the address of p.       .       .       .      ProcPtr();                // First invocation calls p.      mov( &q, ProcPtr );       // Reload ProcPtr with the address of q.       .       .       .      ProcPtr();                // This invocation calls the "q" procedure. 

Procedure pointer variable declarations also allow the declaration of parameters. To declare a procedure pointer with parameters, you must use a declaration like the following:

 static      p:procedure( i:int32; c:char ); 

This declaration states that p is a 32-bit pointer that contains the address of a procedure requiring two parameters. If desired, you could also initialize this variable p with the address of some procedure by using a static initializer, e.g.,

 static      p:procedure( i:int32; c:char ) := &SomeProcedure; 

Note that SomeProcedure must be a procedure whose parameter list exactly matches p's parameter list (i.e., two value parameters, the first is an int32 parameter and the second is a char parameter). To indirectly call this procedure, you could use either of the following sequences:

      push( << Value for i >> );      push( << Value for c >> );      call( p ); -or-      p( <<Value for i>>, <<Value for c>> ); 

The high level language syntax has the same features and restrictions as the high level syntax for a direct procedure call. The only difference is the actual call instruction HLA emits at the end of the calling sequence.

Although all of the examples in this section use static variable declarations, don't get the idea that you can only declare simple procedure pointers in the static or other variable declaration sections. You can also declare procedure pointer types in the type section, and you can declare procedure pointers as fields of a record or a union. Assuming you create a type name for a procedure pointer in the type section, you can even create arrays of procedure pointers. The following code fragments demonstrate some of the possibilities:

 type      pptr:           procedure;      prec:           record                      p:pptr;                      // other fields...                endrecord; static      p1:pptr;      p2:pptr[2]      p3:prec;       .       .       .      p1();      p2[ebx*4]();      p3.p(); 

One very important thing to keep in mind when using procedure pointers is that HLA does not (and cannot) enforce strict type checking on the pointer values you assign to a procedure pointer variable. In particular, if the parameter lists do not agree between the declarations of the pointer variable and the procedure whose address you assign to the pointer variable, the program will probably crash if you attempt to call the mismatched procedure indirectly through the pointer using the high level syntax. Like the low level "pure" procedure calls, it is your responsibility to ensure that the proper number and types of parameters are on the stack prior to the call.




The Art of Assembly Language
The Art of Assembly Language
ISBN: 1593272073
EAN: 2147483647
Year: 2005
Pages: 246
Authors: Randall Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net