Run-time Virtual Methods


Application developers often need to perform operations (such as display) that are not provided by ACIS. When the operation depends on the type of entity being operated upon, the developer has two implementation options. If they have source for ACIS, they can add a virtual method to ENTITY and its derived classes. Otherwise, they can provide a function which uses an if-else-if construct to determine the entity type and take appropriate action. Unfortunately, neither of these methods works well when two or more components may optionally be used together. For example, if component A defines a method or function to display entities, there is no way, without having source, for component B to tell component A how to display any new entity types it derives.

The runtime virtual method mechanism solves these problems by allowing components to define new virtual methods with runtime binding, similar to the way attributes allow data to be added to entities.

Design

When a new method is defined, an argument list class is derived from METHOD_ARGS to contain the arguments to be passed to the method in addition to the entity pointer. This provides for compile time type checking. To ensure that the correct argument list class is passed to the method, each argument list class provides an id method which returns a string identifying the class.

A master list is maintained that associates an index value with each combination of method name and argument list identifier requested by an application. This list is maintained as a linked list sorted by method name. Multiple methods with the same name are further sorted by argument list identifier. A METHOD_ID class is used to look up entries in the master list. If the requested combination of method name and argument list identifier is not found, a new entry is added to the list and given the next available index value.

Each class derived from ENTITY has a dynamically allocated array of function pointers associated with it. When a method is registered for the class, this array is expanded if necessary and a pointer to the specified function is stored in the element indicated by the index value in the specified METHOD_ID.

When a method is called, the identifier for the supplied argument list item is first checked against the identifier in the specified METHOD_ID. If they do not match, FALSE is returned.

Otherwise, the function at the element specified by the METHOD_ID in the function table for the class is called with the entity pointer and the specified argument list item. If the function table for the class does not contain the specified element of that element is the NULL pointer, the method is called for the parent of the class. If no ancestor of the class provides the method, FALSE is returned.

Usage

When defining a new runtime virtual method, as with defining any function, the developer must decide on the name for the method and its arguments. The only restriction for a runtime virtual method is that it must return a logical value indicating whether the operation was supported for the supplied entity type. This implies that any values to be returned by the operation must be passed back through the argument list.

The developer then derives a class from METHOD_ARGS to contain the arguments for the method. Since the major purpose of this class is to provide some compile time type checking, not encapsulation, it is recommended that all data members be declared public for simplicity. In addition to a constructor, which may be used to provide default parameters for the method, this class must also provide a virtual id class which returns a character string used to provide additional type safety and allow a form of method overloading. The identifier string should match the class name in order to reduce the possibility of conflict with classes generated by other developers. Also, to reduce dependencies between components, it is recommended that all methods for the argument class be defined inline in the .hxx file.

The following example defines an argument class with all inline methods:

C++ Example

#include methargs.hxx
class DL_item;
class SPAtransf;
class SKETCH_ARGS : public METHOD_ARGS
{
public:
   DL_item *&result;
   int color;
   const SPAtransf *transform;
   SKETCH_ARGS(DL_item *&res, int  col,
   const SPAtransf *trans = NULL)  :
   result(res), color(col), transform(trans)  {}
   virtual const char *id() const { return  sketch_args; } };

Example. methargs.hxx

Implementing Methods

To implement a method for an entity type, the developer first defines a function to perform the desired operation for a particular entity type or types. This function takes two arguments: a pointer to void containing the this pointer for the calling entity, and a const reference to METHOD_ARGS. These arguments can be cast to the appropriate types inside the function. The function returns a logical indicating whether or not the operation was performed. Refer to the following example.

C++ Example

static logical sketch_edge(
    void *entptr,
    METHOD_ARGS const &argref
)
{     EDGE *edge = (EDGE *) entptr;
     SKETCH_ARGS const &args = *(SKETCH_ARGS *) &argref;
     // Use edge, args.color, and args.transform to create a
     // DL_item storing a pointer to the DL_item in args.result
     // The body of this has been omitted for clarity.
     return TRUE;
}

Example. Define the Function

Next, the function is registered using the static add_method method for the appropriate entity types. This can be done using static initializers or an initialization function for the component.

C++ Example

// Get the identifier for a method named sketch using
// SKETCH_ARGS
static METHOD_ID sketch_method_id(sketch, sketch_args);
// Associate sketch function with sketch method for EDGE class
static MethodFunction xxx = EDGE::add_method(sketch_method_id,
sketch_edge);

Example. Registering the Function

Calling Methods

To call a runtime virtual method, the developer calls an entitys call_method method with the appropriate METHOD_ID and METHOD_ARGS. This method returns a logical value indicating whether or not the operation was performed. The caller should check this value before attempting to use any return values from the runtime method. Refer to the following example.

C++ Example

// Get the identifier for a method named sketch using
// SKETCH_ARGS static METHOD_ID sketch_method_id(sketch, sketch_args);
// Execute the sketch method associated with this entity type
ok = entity->call_method(sketch_method_id, SKETCH_ARGS
   (item, color, trans));
// Check for supported operation before using result
if (ok) { . . . }

Example. Calling Methods

[Top]