• No results found

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

List

cannot happen

no operation

forward for

List

make

List struct Class

ctor:

dtor:

delete:

forward:

new:

List

"List" Object sizeof aList

fill

aList

empty

aList

free

aList

forward for

aList

make

aList

add to

aList

take from

aList 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

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

For dynamically linked methods

`linkage

is

%−. In this case we get the class

description as astruct ClassfromclassOf(), but we cast it to the class description

structure which it will be onceisOf()

has verified the type, so that we can select

the appropriate method component.

For a class method,

`linkageevaluates as%+, i.e., we advance to the second

half of the report and simply check withcast()that_selfis at least aClass. This is

the only difference in a selector for a class method with forwarding.