A metaobject is an object that controls the behavior of another object. If an object has an attached metaobject and a message is sent to the object, this message is packed in an object and passed as parameter to a method called methodCall of the metaobject. Then the metaobject can call the original object method or do anything it wants to.
Metaobjects in Green are called shells and are a little bit different from the previous description. A shell is a pseudo-object with methods and instance variables that can be attached to an object. Any message sent to the object is first searched in the shell and then in the object. As an example, we will use shell class Border and class Window.
class Window
proc init( ... )
begin
...
end
public:
proc draw()
...
...
end
shell class
Border(Window)
proc init()
begin
end
public:
proc draw()
begin
self.drawBorder();
{ call object method }
super.draw();
end
private:
proc drawBorder()
...
end
As indicated by the syntax
"shell
class Border(Window)", Border shells can be attached to objects of subtypes of Window, which
includes Window itself. A shell of Border
can be attached to a window object through the syntax
Meta.attachShell(window, Border.new());
Since Border defines
a method draw, if a message "draw()"
is sent to an object with a Border shell the method draw of Border will be called.
Study the following example.
window =
Window.new("My Window", 30, 20, 120, 300);
window.draw();
// call Window::draw()
Meta.attachShell(
window, Border.new() );
window.draw();
// call Borfer::draw
A shell can be removed from an object by
Meta.removeShell(window)
The full functionality of metaobjects is got by declaring a method interceptAll in a shell class:
shell class
Border(Window)
public:
proc interceptAll( mi : ObjectMethodInfo;
vetArg : array(Any)[] )
begin
mi.invoke(vetArg);
end
end
Class ObjectMethodInfo belongs to the introspective reflection library and is used to describe a method of a specific object. Method invoke of this class calls the method with the parameters given by the array vetArg.
After a Border shell is attached to an object, any message sent to the object will be packed in parameters mi and vetArg and method interceptAll of Border will be called. For example, in the code
window = Window.new(...);
window.draw();
Meta.attachShell(window, Border.new());
window.draw();
the last statement will call Border::interceptAll and parameter mi will
describe method Window::draw of window (the object). vetArg will have zero elements since draw has no parameter. The statement
mi.invoke(vetArg);
will call Window::draw
using object window as the message receiver.
Shells may be attached to class objects as well. Although class objects are classless objects, they do have types. A shell class defining one or more new methods may be attached to a class object to control the creation of objects of that class.
There are some more examples with shells that show the C code generated by a
compiler of a subset of Green.
Shells are much easier to understand than metaobjects of other languages such as Open C++ or Meta Java. Shells are much more efficient too. If method interceptAll is not used, there is no overhead: a message send to an object with a shell is as fast as a normal message send. If interceptAll is used, there is an overhead small when compared with the use of metaobjects. To compare, suppose method interceptAll of a shell class just forwards the message to the object as in the example. Then if a shell of this class is attached to an object, a message send to it will be between 2.7 and 4.6 slower than a normal message send. We considered that the object method corresponding to the message sent has an empty body and no parameters or return value. If metaobjects of other languages were used, the overhead would be between 10 and 30 times. See more in the ECOOP'98 article.