System description.

The main goal of PRIMA is to provide basic windowing and graphics services in a framework of Perl classes. Naturally, some parts of PRIMA are written in Perl, and other parts are written in C using perlguts(1) API. Since PRIMA was planned to run on multiple platforms, the division of C code to the system-independent and system-dependent parts takes place.

PRIMA tries to make the fact that some of its classes' methods are written in Perl, and others in C as invisible as possible. For instance, when C method init() of a class Prima::Widget invokes method set_text(), there is no easy way to determine whether it is calling the original C set_text() method, or is overridden in the C method of a subclass Prima::Window, or is overridden in the Perl method of a subclass Prima::Button.

The first thing to achieve this fluency in the overriding of virtual methods in either language was to associate Perl objects with a C structure. There is nothing new here, many XS extensions do the same. Originally, a reserved key was used in a hash representing an object to hold a stringified version of a C pointer. Currently, the user type '~' magic is used instead. C-part knows its Perl ``mate'' by storing an SV pointer directly.

The second thing was to pretend that C is an object-oriented language. As a marginal note, other candidates for the low-level implementation language where not considered because of our personal preferences. Every PRIMA class is represented as a C structure that contains data fields and several auxiliary fields. The most important auxiliary field is a pointer to a virtual methods table (VMT for short) structure, which holds method pointers and some run-time typing information. Of course, the declaration of the VMT is specific for every class that has implemented in C non-overridden methods. The gencls script, described below, generates the VMT for classes which have C methods (core classes for short). Thus, the invocation of a method from C normally looks like

   CWidget( self)-> set_text( self, "E~xit");

where CWidget is a convenience macro. Without it, the real call is more like

   ((struct _Widget_vmt*)
      ((struct _Widget*)self)->self)->
         set_text( self, "E~xit");

Most commonly, every C core class method is implemented as three C functions:

At the PRIMA startup all thunks of C methods of core classes are registered by calling newXS, so that C methods become available to Perl. This registration is also partly automated with gencls script.

Things become interesting when we define a pure Perl class inherited directly or indirectly from one of the core classes. From Perl's side, this is not a problem. It is a bit more complicated in C. Using our set_text() example, we need a way to correctly call Prima::Button::set_text() from C dealing with an instance of a button. To cut the long story short, this is achieved as follows (fig. 3).


  
Figure 3: Virtual methods and VMT patching.
\includegraphics[width=\textwidth]{Prima_illu.eps}

Every preinitialized (generated with gencls) VMT structure has a pointer to initialized array (patch table) called ClassnameVmtPatch. When the very first instance of such a pure Perl class is being created, the internal function gimme_the_vmt() is called. The function maintains a hash of known VMTs and is able to determine that it has to create a new VMT. By traversing @ISA it finds a predecessor with an already existing VMT. gimme_the_vmt() then allocates space for a new VMT, copies the predecessor's VMT to it, and scans the patch table, finding methods which were overridden in Perl and replacing corresponding entries in the new VMT with an appropriate Classname_methodname_REDEFINED() address.

The approach described above has its limitations. First, it is not easy to call from C a completely new (not overridden) method of a pure Perl class. Second, it is not possible to define a class which has C methods and is inherited from purely Perl-implemented superclass. It is possible, though, to circumvent the latter by creating a Classname.cls file for the superclass (see gencls description below) and make this class known to the core by registering it.