TOC PREV NEXT INDEX

Put your logo here!



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 ClassType
 

 

This 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 822). 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 Sample Program

This chapter's sample program will present what is probably the epitome of object-oriented programs: a simple "drawing" program that uses objects to represent shapes to draw on the display. While limited to a demonstration program, this program does demonstrate important object-oriented concepts in assembly language.

This is an unusual drawing program insofar as it draws shapes using ASCII characters. While the shapes it draws are very rough (compared to a graphics-based drawing program), the output of this program could be quite useful for creating rudimentary diagrams to include as comments in your HLA (or other language) programs. This sample program does not provide a "user interface" for drawing images (something you would need to effectively use this program) because the user interface represents a lot of code that won't improve your appreciation of object-oriented programming (not to mention, this book is long enough already). Providing a mouse-based user interface to this program is left as an exercise to the interested reader.

This program consists of three source files: the class definitions in a header file, the implementation of the class' procedures and methods in an HLA source file, and a main program that demonstrates a simple use of the class' objects. The following listings are for these three files.


 

 
type
 

 
	// Generic shape class:
 
	
 
	shape: class
 

 
		const
 

 
			maxX: uns16 := 80;
 
			maxY: uns16 := 25;
 

 
		var
 
			x:				uns16;
 
			y:				uns16;
 
			width:			uns16;
 
			height:			uns16;
 
			fillShape:		boolean;
 

 
		procedure create; returns( "esi" ); external;
 
		
 
		method draw; abstract;
 
		method fill( f:boolean ); external;
 
		method moveTo( x:uns16; y:uns16 );  external;
 
		method resize( width: uns16; height: uns16 );  external;
 

 
	endclass;
 

 

 

 
	// Class for a rectangle shape
 
	//
 
	//	+------+
 
	//	|      |
 
	//  +------+
 
	
 
	rect: class inherits( shape )
 

 
		override procedure create;  external;
 
		override method draw; external;
 

 
	endclass;
 
	
 
	
 
	
 
	// Class for a rounded rectangle shape
 
	//
 
	//	 --------
 
	//	/        \
 
	//  |        |
 
	//  \        /
 
	//	 --------
 
	
 
	roundrect: class inherits( shape )
 
	
 
		override procedure create; external;
 
		override method draw;  external;
 
		
 
	endclass;
 
	
 
	
 
	// Class for a diamond shape
 
	//
 
	//		/\
 
	//	   /  \
 
	//     \  /
 
	//      \/
 
	
 
	diamond: class inherits( shape )
 
	
 
		override procedure create; external;
 
		override method resize;	 external;
 
		override method draw;  external;
 
		
 
	endclass;
 

 

 
Program 10.1	 Shapes.hhf - The Shape Class Header Files
 

 
unit Shapes;
 
#includeonce( "stdlib.hhf" )
 
#includeonce( "shapes.hhf" )
 

 

 
// Emit the virtual method tables for the classes:
 

 
static
 
    vmt( shape );
 
    vmt( rect );
 
    vmt( roundrect );
 
    vmt( diamond );
 

 

 

 
/*********************************************************************/
 

 
// Generic shape methods and procedures
 

 

 

 
// Constructor for the shape class.
 
//
 
//  Note: this should really be an abstract procedure, but since
 
//  HLA doesn't support abstract procedures we'll fake it by
 
//  raising an exception if somebody tries to call this proc.
 

 
procedure shape.create; @nodisplay; @noframe;
 
begin create;
 

 
    // This should really be an abstract procedure,
 
    // but such things don't exist, so we will fake it.
 

 
    raise( ex.ExecutedAbstract );
 

 
end create;
 

 

 
// Generic shape.fill method.
 
// This is an accessor function that sets the "fill" field
 
// to the value of the parameter.
 

 
method shape.fill( f:boolean ); @nodisplay;
 
begin fill;
 

 
    push( eax );
 
    mov( f, al );
 
    mov( al, this.fillShape );
 
    pop( eax );
 

 
end fill;
 

 
// Generic shape.moveTo method.
 
// Checks the coordinates passed as a parameter and
 
// then sets the (X,Y) coordinates of the underlying
 
// shape object to these values.
 

 
method shape.moveTo( x:uns16; y:uns16 ); @nodisplay;
 
begin moveTo;
 

 
    push( eax );
 
    push( ebx );
 

 

 
    mov( x, ax );
 
    assert( ax < shape.maxX );
 
    mov( ax, this.x );
 

 
    mov( y, ax );
 
    assert( ax < shape.maxY );
 
    mov( ax, this.y );
 

 
    pop( ebx );
 
    pop( eax );
 

 
end moveTo;
 

 

 
// Generic shape.resize method.
 
// Sets the width and height fields of the underlying object
 
// to the values passed as parameters.
 
//
 
//  Note: Ignores resize request if the size is less than 2x2.
 

 
method shape.resize( width:uns16; height:uns16 ); @nodisplay;
 
begin resize;
 

 
    push( eax );
 
    assert( width <= shape.maxX );
 
    assert( height <= shape.maxY );
 

 
    if( width > 2 ) then
 
    
 
        if( height > 2 ) then
 
    
 
            mov( width, ax );
 
            mov( ax, this.width );
 
            
 
        
 
            mov( height, ax );
 
            mov( ax, this.height );
 
        
 
        endif;
 
                
 
    endif;
 
    pop( eax );
 

 
end resize;
 

 

 
/*******************/
 
/*                 */
 
/* rect's methods: */
 
/*                 */
 
/*******************/
 

 

 
// Constructor for the rectangle class:
 

 
procedure rect.create; @nodisplay; @noframe;
 
begin create;
 

 
    push( eax );
 

 
    // If called as rect.create, then allocate a new object
 
    // on the heap and return the pointer in ESI.
 
    
 
    if( esi = NULL ) then
 

 
        mov( malloc( @size( rect ) ), esi );
 

 
    endif;
 
    
 
    // Initialize the pointer to the VMT:
 
    
 
    mov( &rect._VMT_, this._pVMT_ );
 

 
    // Initialize fields to create a non-filled unit square.
 

 
    sub( eax, eax );
 

 
    mov( ax, this.x );
 
    mov( ax, this.y );
 
    inc( eax );
 
    mov( al, this.fillShape );  // Sets fillShape to true.
 
    inc( eax );
 
    mov( ax, this.height );
 
    mov( ax, this.width );
 

 
    pop( eax );
 
    ret();
 

 
end create;
 

 

 
// Here's the method to draw a text-based square on the display.
 

 
method rect.draw; @nodisplay;
 
static
 
    horz: str.strvar( shape.maxX ); // Holds "+------...--+"
 
    spcs: str.strvar( shape.maxX ); // Holds "      ...    " for fills.
 

 
    
 
begin draw;
 

 
    push( eax );
 
    push( ebx );
 
    push( ecx );
 
    push( edx );
 
    
 
    // Initialize the horz and spcs strings to speed up
 
    // drawing our rectangle.
 
    
 
    movzx( this.width, ebx );
 
    str.setstr( '-', horz, ebx );
 
    mov( horz, eax );
 
    mov( '+', (type char [eax]));
 
    mov( '+', (type char [eax+ebx-1]));
 
    
 
    // If the fillShape field contains true, then we
 
    // need to fill in the characters inside the rectangle.
 
    // If this is false, we don't want to overwrite the
 
    // text in the center of the rectangle.  The following
 
    // code initializes spcs to all spaces or the empty string
 
    // to accomplish this.
 
    
 
    if( this.fillShape ) then
 
                            
 
        sub( 2, ebx );
 
        str.setstr( ' ', spcs, ebx );
 
        
 
    else
 
    
 
        str.cpy( "", spcs );
 
        
 
    endif;
 
    
 
    // Okay, position the cursor and draw
 
    // our rectangle.
 
    
 
    console.gotoxy( this.y, this.x );
 
    stdout.puts( horz );    // Draws top horz line.
 
    
 
    // For each row except the top and bottom rows, 
 
    // draw "|" characters on the left and right
 
    // hand sides and the fill characters (if fillShape
 
    // is true) inbetween them.
 
    
 
    mov( this.y, cx );
 
    mov( cx, bx );
 
    add( this.height, bx );
 
    inc( cx );
 
    dec( bx );
 
    while( cx < bx) do
 
    
 
        console.gotoxy( cx, this.x );
 
        stdout.putc( '|' );
 
        stdout.puts( spcs );
 
        mov( this.x, dx );
 
        add( this.width, dx );
 
        dec( dx );
 
        console.gotoxy( cx, dx );
 
        stdout.putc( '|' );
 
        inc( cx );
 
                
 
    endwhile;
 
    
 
    // Draw the bottom horz bar:
 
    
 
    console.gotoxy( cx, this.x );
 
    stdout.puts( horz );
 

 
    pop( edx );
 
    pop( ecx );
 
    pop( ebx );
 
    pop( eax );
 

 
end draw;
 

 

 

 

 

 
/************************/
 
/*                      */
 
/* roundrect's methods: */
 
/*                      */
 
/************************/
 

 

 

 

 
// This is the constructor for the roundrect class.
 
// See the comments in rect.create for details
 
// (since this is just a clone of that code with
 
//  minor changes here and there).
 

 
procedure roundrect.create; @nodisplay; @noframe;
 
begin create;
 

 
    push( eax );
 
    if( esi = NULL ) then
 

 
        mov( malloc( @size( rect ) ), esi );
 

 
    endif;
 
    mov( &roundrect._VMT_, this._pVMT_ );
 

 
    // Initialize fields to create a non-filled unit square.
 

 
    sub( eax, eax );
 

 
    mov( ax, this.x );
 
    mov( ax, this.y );
 
    inc( eax );
 
    mov( al, this.fillShape );  // Sets fillShape to true.
 
    inc( eax );
 
    mov( ax, this.height );
 
    mov( ax, this.width );
 

 
    pop( eax );
 
    ret();
 

 
end create;
 

 

 

 
// Here is the draw method for the roundrect object.
 
// Note: if the object is less than 5x4 in size,
 
// this code calls rect.draw to draw a rectangle
 
// since roundrects smaller than 5x4 don't look good.
 
//
 
//  Typical roundrect:
 
//
 
//       --------
 
//      /        \
 
//      |        |
 
//      \        /
 
//       --------
 

 
method roundrect.draw; @nodisplay;
 
static
 
    horz:       str.strvar( shape.maxX );
 
    spcs:       str.strvar( shape.maxX );
 

 
begin draw;
 

 
    push( eax );
 
    push( ebx );
 
    push( ecx );
 
    push( edx );
 
    
 
    if
 
    ( #{
 
        cmp( this.width, 5 );
 
        jb true;
 
        cmp( this.height, 4 );
 
        jae false;
 
    }#) then
 
    
 
        // If it's too small to draw an effective
 
        // roundrect, then draw it as a rectangle.
 
    
 
        call( rect._VMT_[ @offset( rect.draw ) ] );
 
    
 
    else
 

 
        // Okay, it's big enough, draw it as a rounded
 
        // rectangle object.  Begin by initializing the
 
        // horz string with a set of dashes with spaces
 
        // at either end.
 
            
 
        movzx( this.width, ebx );
 
        sub( 4, ebx );
 
        str.setstr( '-', horz, ebx );
 
        if( this.fillShape ) then
 
                                
 
            add( 2, ebx );
 
            str.setstr( ' ', spcs, ebx );
 
            
 
        else
 
        
 
            str.cpy( "", spcs );
 
            
 
        endif;
 
        
 
        // Okay, draw the top line.
 
        
 
        mov( this.x, ax );
 
        add( 2, ax );
 
        console.gotoxy( this.y, ax );
 
        stdout.puts( horz );
 
        
 
        // Now draw the second line and the
 
        // as "/" and "\" with optional spaces
 
        // inbetween (if fillShape is true).
 
        
 
        mov( this.y, cx );
 
        inc( cx );
 
        console.gotoxy( cx, ax );
 
        stdout.puts( spcs );
 
        
 
        console.gotoxy( cx, this.x );
 
        stdout.puts( " /" );
 
        
 
        add( this.width, ax );
 
        sub( 4, ax );                   // Sub 4 because we added two above.
 
        console.gotoxy( cx, ax );
 
        stdout.puts( "\ " );
 
        
 
        
 
        // Okay, now draw the bottom line:
 
        
 
        mov( this.x, ax );
 
        add( 2, ax );
 
        mov( this.y, cx );
 
        add( this.height, cx );
 
        dec( cx );
 
        console.gotoxy( cx, ax );
 
        stdout.puts( horz );
 
        
 
        // And draw the second from the bottom
 
        // line as "\" and "/" with optional
 
        // spaces inbetween (depending on fillShape)
 
        
 
        dec( cx );
 
        console.gotoxy( cx, this.x );
 
        stdout.puts( spcs );
 
        
 
        console.gotoxy( cx, this.x );
 
        stdout.puts( " \" );
 
        
 
        mov( this.x, ax );
 
        add( this.width, ax );
 
        sub( 2, ax );                   // Sub 4 because we added two above.
 
        console.gotoxy( cx, ax );
 
        stdout.puts( "/ " );
 
        
 
        // Finally, draw all the lines inbetween the
 
        // top two and bottom two lines.
 
        
 
        mov( this.y, cx );
 
        mov( this.height, bx );
 
        add( cx, bx );
 
        add( 2, cx );
 
        sub( 2, bx );
 
        mov( this.x, ax);
 
        add( this.width, ax );
 
        dec( ax );
 
            
 
        while( cx < bx) do
 
        
 
            console.gotoxy( cx, this.x );
 
            stdout.putc( '|' );
 
            stdout.puts( spcs );
 
            console.gotoxy( cx, ax );
 
            stdout.putc( '|' );
 
            inc( cx );
 
                    
 
        endwhile;
 

 
    endif;
 

 
    pop( edx );
 
    pop( ecx );
 
    pop( ebx );
 
    pop( eax );
 

 
end draw;
 

 

 

 
/*********************/
 
/*                   */
 
/* Diamond's methods */
 
/*                   */
 
/*********************/
 

 

 
// Constructor for a diamond shape.
 
// See pertinent comments for the rect constructor
 
// for more details.
 

 
procedure diamond.create; @nodisplay; @noframe;
 
begin create;
 

 
    push( eax );
 
    if( esi = NULL ) then
 

 
        mov( malloc( @size( rect ) ), esi );
 

 
    endif;
 
    mov( &diamond._VMT_, this._pVMT_ );
 

 
    // Initialize fields to create a 2x2 diamond.
 

 
    sub( eax, eax );
 

 
    mov( ax, this.x );
 
    mov( ax, this.y );
 
    inc( eax );
 
    mov( al, this.fillShape );  // Sets fillShape to true.
 
    inc( eax );                 // Minimum diamond size is 2x2.
 
    mov( ax, this.height );
 
    mov( ax, this.width );
 

 
    pop( eax );
 
    ret();
 

 
end create;
 

 

 
// We have to overload the resize method for diamonds
 
// (unlike the other objects) because diamond shapes
 
// have to be symmetrical.  That is, the width and
 
// the height have to be the same.  This code enforces
 
// this restriction by setting both parameters to the
 
// minimum of the width/height parameters and then it
 
// calls shape.resize to do the dirty work.
 

 
method diamond.resize( width:uns16; height:uns16 ); @nodisplay;
 
begin resize;
 

 
    // Diamonds are symmetrical shapes, so the width and
 
    // height must be the same.  Force that here:
 
    
 
    push( eax );
 
    mov( width, ax );
 
    if( ax > height ) then
 
        
 
        mov( height, ax );
 
        
 
    endif;
 
    
 
    // Call the shape.resize method to do the actual work:
 
    
 
    push( eax );    // Pass the minimum value as the width.
 
    push( eax );    // Also pass the minimum value as the height.
 
    call( shape._VMT_[ @offset( shape.resize ) ] );
 
    
 
    pop( eax );
 
    
 
end resize;
 

 

 
// Here's the code to draw the diamond.
 

 
method diamond.draw; @nodisplay;
 
var
 
    startY: uns16;
 
    endY:   uns16;
 
    startX: uns16;
 
    endX:   uns16;
 

 
begin draw;
 

 
    push( eax );
 
    push( ebx );
 
    push( ecx );
 
    push( edx );
 
    
 
    if
 
    (#{
 
        cmp( this.width, 2 );
 
        jb true;
 
        cmp( this.height, 2 );
 
        jae false;
 
    }#) then
 
    
 
        // Special cases for small diamonds.
 
        // Resizing prevents most of these from ever appearing.
 
        // However, if someone pokes around directly in the
 
        // width and height fields this code will save us:
 
        
 
            cmp( this.width, 1 );
 
            ja D2x1;
 
            cmp( this.height, 1 );
 
            ja D1x2;
 
        
 
            // At this point we must have a 1x1 diamond
 

 
            console.gotoxy( this.y, this.x );
 
            stdout.putc( '+' );
 
            jmp SmallDiamondDone;
 

 
        D2x1:               
 

 
            // Okay, we have a 2x1 (WxH) diamond here:
 

 
            console.gotoxy( this.y, this.x );
 
            stdout.puts( "<>" );
 
            jmp SmallDiamondDone;
 

 
        D1x2:
 

 
            // We have a 1x2 (WxH) diamond here:
 

 
            mov( this.y, ax );
 
            console.gotoxy( ax, this.x );
 
            stdout.putc( '^' );                          
 
            inc( ax );                                   
 
            console.gotoxy( ax, this.x );
 
            stdout.putc( 'V' );
 

 
        SmallDiamondDone:
 
                
 
                                                   
 
    else
 

 
        // Okay, we're drawing a reasonable sized diamond.
 
        // There is still a minor problem.  The best looking
 
        // diamonds always have a width and height that is an
 
        // even integer.  We need to do something special if
 
        // the height or width is odd.
 
        //
 
        //   Odd         Odd
 
        //  Height      Width
 
        //                .   <- That's a period
 
        //   /\          / \
 
        //  <  >         \ /
 
        //   \/           '   <- That's an apostrophe
 
        //
 
        //          Both
 
        //            .
 
        //           / \    
 
        //          <   >
 
        //           \ /                                   
 
        //            '
 
        //
 
        // Step one: determine if we have an odd width.  If so,
 
        // output the period and quote at the appropriate points.
 
 
 
        mov( this.width, ax );
 
        mov( this.y, cx );
 
        test( 1, al );
 
        if( @nz ) then
 

 
            shr( 1, ax );
 
            add( this.x, ax );
 
            console.gotoxy( cx, ax );
 
            stdout.putc( '.' );
 
            inc( cx );
 
            mov( cx, startY );
 

 
            add( this.height, cx );
 
            sub( 2, cx );
 
            console.gotoxy( cx, ax );
 
            stdout.putc( '''' );
 
            dec( cx );
 
            mov( cx, endY );
 
            
 
        else
 
        
 
            mov( this.y, ax );
 
            mov( ax, startY );
 
            add( this.height, ax );
 
            dec( ax );
 
            mov( ax, endY );
 

 
        endif;
 
        
 
        // Step two: determine if we have an odd height.  If so,
 
        // output the less than and greater than symbols at the
 
        // appropriate spots (in the center of the diamond).
 
        
 
        mov( this.height, ax );
 
        mov( this.x, cx );
 
        test( 1, al );
 
        if( @nz ) then
 
        
 
            shr( 1, ax );
 
            add( this.y, ax );
 
            console.gotoxy( ax, cx );
 
            stdout.putc( '<' );
 
            inc( cx );
 
            mov( cx, startX );
 
            
 
            // Write spaces across the center if fillShape is true.
 
            
 
            if( this.fillShape ) then
 
            
 
                lea( ebx, [ecx+1] );
 
                mov( this.x, dx );
 
                add( this.width, dx );
 
                dec( dx );
 
                while( bx < dx ) do
 
                
 
                    stdout.putc( ' ' );
 
                    inc( bx );
 
                    
 
                endwhile;
 
                
 
            endif;
 
            
 
            add( this.width, cx );
 
            sub( 2, cx );
 
            console.gotoxy( ax, cx );
 
            stdout.putc( '>' );
 
            dec( cx );
 
            mov( cx, endX );
 
            
 
        else
 
        
 
            mov( this.x, ax );
 
            mov( ax, startX );
 
            add( this.width, ax );
 
            dec( ax );
 
            mov( ax, endX );
 
            
 
        endif;
 
        
 
        // Step three: fill in the sides of the diamond
 
        //
 
        //      /\           '
 
        //     /  \   O     / \    (or something inbetween these two).
 
        //     \  /    R   <   >
 
        //      \/          \ /
 
        //                   '
 
        // We've already drawn the points if there was an odd height
 
        // or width, now we've just got to fill in the sides with
 
        // "/" and "\" characters.
 
        //
 
        // Compute the middle two (or three) lines beginning with
 
        // the "/" (decY) and "\" (incY) symbols:
 
        //
 
        // decY = (( startY + endY - 1 ) and $FFFE )/2
 
        // incY = ( startY + endY )/2 + 1
 
        
 
        mov( startY, ax );
 
        add( endY, ax );
 
        mov( ax, bx );
 
        dec( ax );
 
        and( $FFFE, ax ); // Force value to be even.
 
        shr( 1, ax );
 
        
 
        shr( 1, bx );
 
        inc( bx );
 
        
 
        
 
        // Fill in pairs of rows as long as we don't hit the bottom/top
 
        // of the diamond:
 
        
 
        while( (type int16 ax) >= (type int16 startY) ) do
 
        
 
            // Draw the sides on the upper half of the diamond:
 
            
 
            mov( startX, cx );
 
            mov( endX, dx );
 
            console.gotoxy( ax, cx );
 
            stdout.putc( '/' );
 
            if( this.fillShape ) then
 
            
 
                inc( cx );
 
                while( cx < dx ) do
 
                
 
                    stdout.putc( ' ' );
 
                    inc( cx );
 
                    
 
                endwhile;
 
                
 
            endif;
 
            console.gotoxy( ax, dx );
 
            stdout.putc( '\' );
 
        
 
            // Draw the sides on the lower half of the diamond:
 
            
 
            mov( startX, cx );
 
            mov( endX, dx );
 
            console.gotoxy( bx, cx );
 
            stdout.putc( '\' );
 
            if( this.fillShape ) then
 
            
 
                inc( cx );
 
                while( cx < dx ) do
 
                
 
                    stdout.putc( ' ' );
 
                    inc( cx );
 
                    
 
                endwhile;
 
                
 
            endif;
 
            console.gotoxy( bx, dx );
 
            stdout.putc( '/' );
 
            
 
            inc( bx );
 
            dec( ax );
 
            inc( startX );
 
            dec( endX );
 
                
 
        endwhile;
 

 
    endif;
 

 

 
    pop( edx );
 
    pop( ecx );
 
    pop( ebx );
 
    pop( eax );
 

 
end draw;
 

 

 

 

 

 

 
end Shapes;
 
            
 

 
            
 
Program 10.2	 Shapes.hla - The Implementation of the Shape Class
 

 
// This is a simple demonstration program
 
// that shows how to use the shape objects
 
// in the shape, rect, roundrect, and diamond classes.
 

 
program ASCIIDraw;
 
#include( "stdlib.hhf" )
 
#includeonce( "shapes.hhf" )
 

 
type
 
    pShape: pointer to shape;
 
    
 

 
// Allocate storage for various shapes:
 

 
static
 
    aRect1: pointer to rect;
 
    aRect2: pointer to rect;
 
    aRect3: pointer to rect;
 
    
 
    aRrect1: pointer to roundrect;
 
    aRrect2: pointer to roundrect;
 
    aRrect3: pointer to roundrect;
 

 
    aDiamond1: pointer to diamond;
 
    aDiamond2: pointer to diamond;
 
    aDiamond3: pointer to diamond;
 

 
    // We'll create a list of generic objects
 
    // in the following array in order to demonstrate
 
    // virual method calls and polymorphism.
 
        
 
    DrawList: pShape[9];
 
    
 
begin ASCIIDraw;
 

 
    console.cls();
 
    
 
    // Initialize various rectangle, roundrect, and diamond objects.
 
    // This code also stores pointers to each of these objects in
 
    // the DrawList array.
 
    
 
    mov( rect.create(), aRect1 ); mov( esi, DrawList[0*4] );
 
    mov( rect.create(), aRect2 ); mov( esi, DrawList[1*4] );
 
    mov( rect.create(), aRect3 ); mov( esi, DrawList[2*4] );
 
    
 
    mov( roundrect.create(), aRrect1 ); mov( esi, DrawList[3*4] );
 
    mov( roundrect.create(), aRrect2 ); mov( esi, DrawList[4*4] );
 
    mov( roundrect.create(), aRrect3 ); mov( esi, DrawList[5*4] );
 
    
 
    mov( diamond.create(), aDiamond1 ); mov( esi, DrawList[6*4] );
 
    mov( diamond.create(), aDiamond2 ); mov( esi, DrawList[7*4] );
 
    mov( diamond.create(), aDiamond3 ); mov( esi, DrawList[8*4] );
 
    
 

 
    // Size and position each of these objects:
 
    
 
    aRect1.resize( 10, 10 );
 
    aRect1.moveTo( 10, 10 );
 
    
 
    aRect2.resize( 10, 10 );
 
    aRect2.moveTo( 15, 15 );
 
    
 
    aRect3.resize( 10, 10 );
 
    aRect3.moveTo( 20, 20 );
 
    aRect3.fill( false );
 

 

 
    aRrect1.resize( 10, 10 );
 
    aRrect1.moveTo( 40, 10 );
 
    
 
    aRrect2.resize( 10, 10 );
 
    aRrect2.moveTo( 45, 15 );
 
    
 
    aRrect3.resize( 10, 10 );
 
    aRrect3.moveTo( 50, 20 );
 
    aRrect3.fill( false );
 
    
 

 
    aDiamond1.resize( 9, 9 );
 
    aDiamond1.moveTo( 28, 0 );
 
    
 
    aDiamond2.resize( 9, 9 );
 
    aDiamond2.moveTo( 28, 3 );
 
    
 
    aDiamond3.resize( 9, 9 );
 
    aDiamond3.moveTo( 28, 6 );
 
    aDiamond3.fill( false );
 
    
 
    
 
    // Note for the real fun, draw all of the objects
 
    // on the screen using the following simple loop.
 
    
 
    for( mov( 0, ebx ); ebx < 9; inc( ebx )) do
 
    
 
        DrawList.draw[ ebx*4 ]();
 
        
 
    endfor;
 

 
end ASCIIDraw;
 
            
 

 
            
 
Program 10.3	 ShapeMain.hla - The Main Program That Demonstrates Using Shape Objects
 

10.17 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.


Web Site Hits Since
Jan 1, 2000

TOC PREV NEXT INDEX