10.14 Run-time Type Information (RTTI)
When working with an object variable (as opposed to a pointer to an object), the type of that object is obvious: it's the variable's declared type. Therefore, at both compile-time and run-time the program trivially knows the type of the object. When working with pointers to objects you cannot, in the general case, determine the type of an object a pointer references. However, at run-time it is possible to determine the object's actual type. This section discusses how to detect the underlying object's type and how to use this information.
If you have a pointer to an object and that pointer's type is some base class, at run-time the pointer could point at an object of the base class or any derived type. At compile-time it is not possible to determine the exact type of an object at any instant. To see why, consider the following short example:
ReturnSomeObject(); // Returns a pointer to some class in ESI. mov( esi, ptrToObject );The routine ReturnSomeObject returns a pointer to an object in ESI. This could be the address of some base class object or a derived class object. At compile-time there is no way for the program to know what type of object this function returns. For example, ReturnSomeObject could ask the user what value to return so the exact type could not be determined until the program actually runs and the user makes a selection.
In a perfectly designed program, there probably is no need to know a generic object's actual type. After all, the whole purpose of object-oriented programming and inheritance is to produce general programs that work with lots of different objects without having to make substantial changes to the program. In the real world, however, programs may not have a perfect design and sometimes it's nice to know the exact object type a pointer references. Run-time type information, or RTTI, gives you the capability of determining an object's type at run-time, even if you are referencing that object using a pointer to some base class of that object.
Perhaps the most fundamental RTTI operation you need is the ability to ask if a pointer contains the address of some specific object type. Many object-oriented languages (e.g., Delphi) provide an IS operator that provides this functionality. IS is a boolean operator that returns true if its left operand (a pointer) points at an object whose type matches the left operand (which must be a type identifier). The typical syntax is generally the following:
ObjectPointerOrVar is ClassTypeThis operator would return true if the variable is of the specified class, it returns false otherwise. Here is a typical use of this operator (in the Delphi language)
if( ptrToNumeric is uint ) then begin . . . end;It's actually quite simple to implement this functionality in HLA. As you may recall, each class is given its own virtual method table. Whenever you create an object, you must initialize the pointer to the VMT with the address of that class' VMT. Therefore, the VMT pointer field of all objects of a given class type contain the same pointer value and this pointer value is different from the VMT pointer field of all other classes. We can use this fact to see if an object is some specific type. The following code demonstrates how to implement the Delphi statement above in HLA:
mov( ptrToNumeric, esi ); if( (type uint [esi])._pVMT_ = &uint._VMT_ ) then . . . endif;This IF statement simply compares the object's _pVMT_ field (the pointer to the VMT) against the address of the desired class' VMT. If they are equal, then the ptrToNumeric variable points at an object of type uint.
Within the body of a class method or iterator, there is a slightly easier way to see if the object is a certain class. Remember, upon entry into a method or an iterator, the EDI register contains the address of the virtual method table. Therefore, assuming you haven't modified EDI's value, you can easily test to see if THIS (ESI) is a specific class type using an IF statement like the following:
if( EDI = &uint._VMT_ ) then . . . endif;10.15 Calling Base Class Methods
In the section on constructors you saw that it is possible to call an ancestor class' procedure within the derived class' overridden procedure. To do this, all you needed to do was to invoke the procedure using the call "classname.procedureName( parameters);" On occasion you may want to do this same operation with a class' methods as well as its procedures (that is, have an overridden method call the corresponding base class method in order to do some computation you'd rather not repeat in the derived class' method). Unfortunately, HLA does not let you directly call methods as it does procedures. You will need to use an indirect mechanism to achieve this; specifically, you will have to call the function using the address in the base class' virtual method table. This section describes how to do this.
Whenever your program calls a method it does so indirectly, using the address found in the virtual method table for the method's class. The virtual method table is nothing more than an array of 32-bit pointers with each entry containing the address of one of that class' methods. So to call a method, all you need is the index into this array (or, more properly, the offset into the array) of the address of the method you wish to call. The HLA compile-time function @offset comes to the rescue- it will return the offset into the virtual method table of the method whose name you supply as a parameter. Combined with the CALL instruction, you can easily call any method associated with a class. Here's an example of how you would do this:
type myCls: class . . . method m; . . . endclass; . . . call( myCls._VMT_[ @offset( myCls.m )]);The CALL instruction above calls the method whose address appears at the specified entry in the virtual method table for myCls. The @offset function call returns the offset (i.e., index times four) of the address of myCls.m within the virtual method table. Hence, this code indirectly calls the m method by using the virtual method table entry for m.
There is one major drawback to calling methods using this scheme: you don't get to use the high level syntax for procedure/method calls. Instead, you must use the low-level CALL instruction. In the example above, this isn't much of an issue because the m procedure doesn't have any parameters. If it did have parameters, you would have to manually push those parameters onto the stack yourself (see "Passing Parameters on the Stack" on page 692). Fortunately, you'll rarely need to call ancestor class methods from a derived class, so this won't be much of an issue in real-world programs.
10.16 Putting It All Together
HLA's class declarations provide a powerful tool for creating object-oriented assembly language programs. Although object-oriented programming is not as popular in assembly as in high level languages, part of the reason has been the lack of assemblers that support object-oriented programming in a reasonable fashion and an even greater lack of tutorial information on object-oriented programming in assembly language.
While this chapter cannot go into great detail about the object-oriented programming paradigm (space limitations prevent this), this chapter does explain the object-oriented facilities that HLA provides and supplies several example programs that use those facilities. From here on, it's up to you to utilize these facilities in your programs and gain experience writing object oriented assembly code.
|