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 selectoradd()for the method to add an object to aList:
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_selfreferences 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
addmethod 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 theadd()selector?
As it stands, anassert()
gets triggered somewhere, the problem is contained, and
our program quits.
Suppose we are working on a class
Xof objects which themselves are not des-
cendants of
List
but which know some
List
object to which they could logically
pass a request toadd(). As it stands, it would be the responsibility of the user ofX
objects, to know (or to find out withrespondsTo()) 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 Calculatorstruct 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 methodforward(): 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 inObject:
% 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 redefiningforward(). 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 theselectorreport which generates all selectors. Message forwarding is
accomplished by declaringforward()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, thecast()in the
classOf
report is turned into a call toisOf()
as part of the
ifmethodreport and an
elseclause is added with theforwardreport to generate the call toforward().
The call toforward()is routed through another selector for argument checking.
It is probably not helpful to get stuck in recursion here, so if the selectorforward()
itself is generated, we stop things with anassert():
% 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 byforward(). The
general approach is to pass the result area toforward()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 theooc
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 whichoocgenerates fornew():
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 likeList. Calling a class method for something
other than a class description is probably a very bad idea; therefore,cast()
is used
to forbid this.
newbelongs intoClass; therefore, no call toisOf()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 toList. However,
forward()is a dynami-
cally linked method, not a class method. Therefore,
forward()
looks in the class
description ofListfor aforwardmethod, i.e., it tries to findListClass_forward():
ListClass Class "ListClass" Class sizeof List
fill
Listcannot happen
no operation
forward for
Listmake
List struct Classctor:
dtor:
delete:
forward:
new:
List•
"List" Object sizeof aListfill
aListempty
aListfree
aListforward for
aListmake
aListadd to
aListtake from
aList struct ListClassctor:
dtor:
delete:
forward:
new:
add:
take:
aList•
...
struct ListThis is perfectly reasonable:
List_forward()is responsible for all messages which
aList
does not understand;
ListClass_forward()
is responsible for all those which
Listcannot handle. Here is theclassOfreport inetc.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