Every dynamically linked method is called through its selector, and we saw in chapter 8 that the selector checks if its method can be found for the object. As an example, consider the selector add() for the method to add an object to a List:
struct Object * add (void * _self, const void * element) { struct Object * result;
const struct ListClass * class =
cast(ListClass(), classOf(_self)); assert(class —> add.method);
cast(Object(), element);
result = ((struct Object * (*) ())
class —> add.method)(_self, element); return result;
}
classOf() tries to make sure that _self references an object; the surrounding call to cast() ascertains that the class description of _self belongs to the metaclass ListClass, i.e., that it really contains a pointer to an add method; finally, assert() guards against a null value masquerading as this pointer, i.e., it makes sure that an add method has been implemented somewhere up the inheritance chain.
What happens if add() is applied to an object that has never heard of this method, i.e., what happens if _self flunks the various tests in the add() selector? As it stands, an assert() gets triggered somewhere, the problem is contained, and our program quits.
Suppose we are working on a class X of objects which themselves are not des- cendants of List but which know some List object to which they could logically pass a request to add(). As it stands, it would be the responsibility of the user of X objects, to know (or to find out with respondsTo()) that add() cannot be applied to them and to reroute the call accordingly. However, consider the following, slightly revised selector:
168
___________________________________________________________________________14 Forwarding Messages — A GUI Calculator
struct Object * add (void * _self, const void * element) { struct Object * result;
const struct ListClass * class =
(const void *) classOf(_self); if (isOf(class, ListClass()) && class —> add.method) {
cast(Object(), element);
result = ((struct Object * (*) ())
class —> add.method)(_self, element); } else
forward(_self, & result, (Method) add, "add",
_self, element); return result;
}
Now, _self can reference any object. If its class happens to contain a valid add pointer, the method is called as before. Otherwise, all the information is passed to a new method forward(): the object itself; an area for the expected result; a pointer to and the name of the selector which failed; and the values in the original argu- ment list. forward() is itself a dynamically linked method declared in Object:
% Class Object { ...
%—
void forward (const _self, void * result, \ Method selector, const char * name, ...);
Obviously, the initial definition is a bit helpless:
% Object forward { %casts
fprintf(stderr, "%s at %p does not answer %s\n",
nameOf(classOf(self)), self, name); assert(0);
}
If an Object itself does not understand a method, we are out of luck. However, forward() is dynamically linked: if a class wants to forward messages, it can accomplish this by redefining forward(). As we shall see in the example in section 14.6, this is almost as good as an object belonging to several classes at the same time.
14.2 Implementation
Fortunately, we decided in chapter 7 to enforce our coding standard with a prepro- cessor ooc, and selectors are a part of the coding standard. In section 8.4 we looked at the selector report which generates all selectors. Message forwarding is accomplished by declaring forward() as shown above, by defining a default imple- mentation, and by modifying the selector report in etc.rep so that all generated selectors reroute what they do not understand:*
____________________________________________________________________________________________ * As before, the presentation is simplified so that it does not show the parts which deal with variable argument lists.
`%header { `n `%result `%classOf `%ifmethod `%checks `%call `t } else `n `%forward `%return } `n `n
This is almost the same code as in section 8.4: as we saw above, the cast() in the classOf report is turned into a call to isOf() as part of the ifmethod report and an else clause is added with the forward report to generate the call to forward().
The call to forward() is routed through another selector for argument checking. It is probably not helpful to get stuck in recursion here, so if the selector forward() itself is generated, we stop things with an assert():
% forward // forward the call, but don’t forward forward `{if `method forward
`t `t assert(0); `} `{else
`t `t forward(_self, \
`{if `result void 0, `} `{else & result, `} \ (Method) `method , " `method ", `%args ); `} `n
The additional `{if concerns the fact that a selector eventually has to return the result expected by its caller. This result will have to be produced by forward(). The general approach is to pass the result area to forward() to get it filled somehow. If, however, our selector returns void, we have no result variable. In this case we pass a null pointer.
It looks as if we could write slightly better code by hand: in some cases we could avoid the result variable, assignment, and a separate return statement. However, tuning would complicate the ooc reports unnecessarily because any rea- sonable compiler will generate the same machine code in either case.
classOf is the other report that gets modified significantly. A call to cast() is removed, but the interesting question is what happens if a call to a class method needs to be forwarded. Let us look at the selector which ooc generates for new():
struct Object * new (const void * _self, ...) { struct Object * result;
va_list ap;
const struct Class * class = cast(Class(), _self); va_start(ap, _self);
if (class —> new.method) {
result = ((struct Object * (*) ()) class —> new.method) (_self, & ap);
170
___________________________________________________________________________14 Forwarding Messages — A GUI Calculator
} else
forward((void *) _self, & result, (Method) new, "new", _self, & ap); va_end(ap);
return result; }
new() is called for a class description like List. Calling a class method for something other than a class description is probably a very bad idea; therefore, cast() is used to forbid this. new belongs into Class; therefore, no call to isOf() is needed.
Let’s assume that we forgot to define the initial Object_new(), i.e., that List has not even inherited a new method, and that, therefore, new.method is a null pointer. In this case, forward() is applied to List. However, forward() is a dynami- cally linked method, not a class method. Therefore, forward() looks in the class description of List for a forward method, i.e., it tries to find ListClass_forward():
ListClass Class "ListClass" Class sizeof List fillList cannot happen no operation forward forList
makeList struct Class ctor: dtor: delete: forward: new: List • "List" Object sizeof aList fillaList emptyaList freeaList
forward foraList
makeaList
add toaList
take fromaList
struct ListClass ctor: dtor: delete: forward: new: add: take: aList • ... struct List
This is perfectly reasonable: List_forward() is responsible for all messages which aList does not understand; ListClass_forward() is responsible for all those which List cannot handle. Here is the classOf report in etc.rep:
`{if `linkage %— `{if `meta `metaroot
`t const struct `meta * class = classOf(_self); `n `} `{else
`t const struct `meta * class = ` \
(const void *) classOf(_self); `n `}
`} `{else
`t const struct `meta * class = ` \
cast( `metaroot (), _self); `n `} `n
For dynamically linked methods `linkage is %−. In this case we get the class description as a struct Class from classOf(), but we cast it to the class description structure which it will be once isOf() has verified the type, so that we can select the appropriate method component.
For a class method, `linkage evaluates as %+, i.e., we advance to the second half of the report and simply check with cast() that _self is at least a Class. This is the only difference in a selector for a class method with forwarding.