NAME
Prima::internals - Prima internal architecture
DESCRIPTION
This documents elucidates the internal structures of the Prima toolkit,
its loading considerations, object and class representation and C
coding style.
Bootstrap
Initializing
For a perl script, Prima is no more but an average module that uses
DynaLoader. As ’use Prima’ code gets executed, a bootstrap procedure
boot_Prima() is called. This procedure initializes all internal
structures and built-in Prima classes. It also initializes all system-
dependent structures, calling window_subsystem_init(). After that point
Prima module is ready to use. All wrapping code for built-in
functionality that can be seen from perl is located into two modules -
Prima::Const and Prima::Classes.
Constants
Prima defines lot of constants for different purposes ( e.g. colors,
font styles etc). Prima does not follow perl naming conventions here,
on the reason of simplicity. It is ( arguably ) easier to write
cl::White rather than Prima::cl::White. As perl constants are
functions to be called once ( that means that a constant’s value is not
defined until it used first ), Prima registers these functions during
boot_Prima stage. As soon as perl code tries to get a constant’s value,
an AUTOLOAD function is called, which is binded inside Prima::Const.
Constants are widely used both in C and perl code, and are defined in
apricot.h in that way so perl constant definition comes along with C
one. As an example file event constants set is described here.
apricot.h:
#define FE(const_name) CONSTANT(fe,const_name)
START_TABLE(fe,UV)
#define feRead 1
FE(Read)
#define feWrite 2
FE(Write)
#define feException 4
FE(Exception)
END_TABLE(fe,UV)
#undef FE
Const.pm:
package fe; *AUTOLOAD = \&Prima::Const::AUTOLOAD;
This code creates a structure of UV’s ( unsigned integers ) and a
register_fe_constants() function, which should be called at boot_Prima
stage. This way feRead becomes C analog to fe::Read in perl.
Classes and methods
Virtual method tables
Prima implementation of classes uses virtual method tables, or VMTs, in
order to make the classes inheritable and their methods overrideable.
The VMTs are usual C structs, that contain pointers to functions. Set
of these functions represents a class. This chapter is not about OO
programming, you have to find a good book on it if you are not familiar
with the OO concepts, but in short, because Prima is written in C, not
in C++, it uses its own classes and objects implementation, so all
object syntax is devised from scratch.
Built-in classes already contain all information needed for method
overloading, but when a new class is derived from existing one, new VMT
is have to be created as well. The actual sub-classing is performed
inside build_dynamic_vmt() and build_static_vmt(). gimme_the_vmt()
function creates new VMT instance on the fly and caches the result for
every new class that is derived from Prima class.
C to Perl and Perl to C calling routines
Majority of Prima methods is written in C using XS perl routines, which
represent a natural ( from a perl programmer’s view ) way of C to Perl
communication. perlguts manpage describes these functions and macros.
NB - Do not mix XS calls to xs language ( perlxs manpage) - the latter
is a meta-language for simplification of coding tasks and is not used
in Prima implementation.
It was decided not to code every function with XS calls, but instead
use special wrapper functions ( also called "thunks") for every
function that is called from within perl. Thunks are generated
automatically by gencls tool ( gencls manpage ), and typical Prima
method consists of three functions, two of which are thunks.
First function, say Class_init(char*), would initialize a class ( for
example). It is written fully in C, so in order to be called from perl
code a registration step must be taken for a second function,
Class_init_FROMPERL(), that would look like
newXS( "Prima::Class::init", Class_init_FROMPERL, "Prima::Class");
Class_init_FROMPERL() is a first thunk, that translates the parameters
passed from perl to C and the result back from C function to perl.
This step is almost fully automatized, so one never bothers about
writing XS code, the gencls utility creates the thunks code
automatically.
Many C methods are called from within Prima C code using VMTs, but it
is possible to override these methods from perl code. The actions for
such a situation when a function is called from C but is an overridden
method therefore must be taken. On that occasion the third function
Class_init_REDEFINED() is declared. Its task is a reverse from
Class_init_FROMPERL() - it conveys all C parameters to perl and return
values from a perl function back to C. This thunk is also generated
automatically by gencls tool.
As one can notice, only basic data types can be converted between C and
perl, and at some point automated routines do not help. In such a
situation data conversion code is written manually and is included into
core C files. In the class declaration files these methods are
prepended with ’public’ or ’weird’ modifiers, when methods with no
special data handling needs use ’method’ or ’static’ modifiers.
NB - functions that are not allowed to be seen from perl have ’c_only’
modifier, and therefore do not need thunk wrapping. These functions can
nevertheless be overridden from C.
Built-in classes
Prima defines the following built-in classes: (in hierarchy order)
Object
Component
AbstractMenu
AccelTable
Menu
Popup
Clipboard
Drawable
DeviceBitmap
Printer
Image
Icon
File
Timer
Widget
Application
Window
These classes can be seen from perl with Prima:: prefix. Along with
these, Utils class is defined. Its only difference is that it cannot be
used as a prototype for an object, and used merely as a package that
binds functions. Classes that are not intended to be an object
prototype marked with ’package’ prefix, when others are marked with
’object’ (see prima-gencls manpage).
Objects
This chapter deals only with Prima::Object descendants, pure perl
objects are not of interest here, so the ’object’ term is thereafter
referenced to Prima::Object descendant object. Prima employs blessed
hashes for its objects.
Creation
All built-in object classes and their descendants can be used for
creating objects with perl semantics. Perl objects are created by
calling bless(), but it is not enough to create Prima objects. Every
Prima::Object descendant class therefore is equipped with create()
method, that allocates object instance and calls bless() itself.
Parameters that come with create() call are formed into a hash and
passed to init() method, that is also present on every object. Note the
fact that although perl-coded init() returns the hash, it not seen in C
code. This is a special consideration for the methods that have ’HV *
profile’ as a last parameter in their class declaration. The
corresponding thunk copies the hash content back to perl stack, using
parse_hv() and push_hv() functions.
Objects can be created from perl by using following code example:
$obj = Prima::SampleObject-> create(
name => "Sample",
index => 10,
);
and from C:
Handle obj;
HV * profile = newHV();
pset_c( name, "Sample");
pset_i( index, 10);
obj = Object_create("SampleObject", profile);
sv_free(( SV*) profile);
Convenience pset_XX macros assign a value of XX type to the hash key
given as a first parameter, to a hash variable named profile. "pset_i"
works with integers, "pset_c" - with strings, etc.
Destruction
As well as create() method, every object class has destroy() method.
Object can be destroyed either from perl
$obj-> destroy
or from C
Object_destroy( obj);
An object can be automatically destroyed when its reference count
reaches 0. Note that the auto destruction would never happen if the
object’s reference count is not lowered after its creation. The code
--SvREFCNT( SvRV( PAnyObject(object)-> mate));
is required if the object is to be returned to perl. If that code is
not called, the object still could be destroyed explicitly, but its
reference would still live, resulting in memory leak problem.
For user code it is sufficient to overload done() and/or cleanup()
methods, or just onDestroy notifications. It is highly recommended to
avoid overloading destroy method, since it can be called in re-entrant
fashion. When overloading done(), be prepared that it may be called
inside init(), and deal with the semi-initialized object.
Data instance
All object data after their creation represent an object instance. All
Prima objects are blessed hashes, and the hash key __CMATE__ holds a C
pointer to a memory which is occupied by C data instance, or a "mate".
It keeps all object variables and a pointer to VMT. Every object has
its own copy of data instance, but the VMTs can be shared. In order to
reach to C data instance gimme_the_mate() function is used. As a first
parameter it accepts a scalar (SV*), which is expected to be a
reference to a hash, and returns the C data instance if the scalar is a
Prima object.
Object life stages
It was decided to divide object life stage in several steps. Every
stage is mirrored into PObject(self)-> stage integer variable, which
can be one of csXXX constants. Currently it has six:
csConstructing
Initial stage, is set until create() is finished. Right after
init() is completed, setup() method is called.
csNormal
After create() is finished and before destroy() started. If an
object is csNormal and csConstructing stage, Object_alive() result
would be non-zero.
csDestroying
destroy() started. This stage includes calling of cleanup() and
done() routines.
csFrozen
cleanup() started.
csFinalizing
done() started
csDead
Destroy finished
Coding techniques
Accessing object data
C coding has no specific conventions, except when a code is an object
method. Object syntax for accessing object instance data is also fairly
standard. For example, accessing component’s field called ’name’ can
be done in several ways:
((PComponent) self)-> name; // classic C
PComponent(self)-> name; // using PComponent() macro from apricot.h
var-> name; // using local var() macro
Object code could to be called also in several ways:
(((PComponent) self)-> self)-> get_name( self); // classic C
CComponent(self)-> get_name( self); // using CComponent() macro from apricot.h
my-> get_name( self); // using local my() macro
This calling is preferred, comparing to direct call of
Component_get_name(), primarily because get_name() is a method and can
be overridden from user code.
Calling perl code
call_perl_indirect() function accepts object, its method name and
parameters list with parameter format string. It has several wrappers
for easier use, which are:
call_perl( Handle self, char * method, char * format, ...)
sv_call_perl( SV * object, char * method, char * format, ...)
cv_call_perl( SV * object, SV * code_reference, char * format, ...)
each character of format string represents a parameters type, and
characters can be:
'i' - integer
's' - char *
'n' - float
'H' - Handle
'S' - SV *
'P' - Point
'R' - Rect
The format string can be prepended with ’<’ character, in which case SV
* scalar ( always scalar, even if code returns nothing or array ) value
is returned. The caller is responsible for freeing the return value.
Exceptions
As descriped in perlguts manpage, G_EVAL flag is used in perl_call_sv()
and perl_call_method() to indicate that an eventual exception should
never be propagated automatically. The caller checks if the exception
was taken place by evaluating
SvTRUE( GvSV( errgv))
statement. It is guaranteed to be false if there was no exception
condition. But in some situations, namely, when no perl_call_*
functions are called or error value is already assigned before calling
code, there is a wrapping technique that keeps previous error message
and looks like:
dG_EVAL_ARGS; // define arguments
....
OPEN_G_EVAL; // open brackets
// call code
perl_call_method( ... | G_EVAL); // G_EVAL is necessary
if ( SvTRUE( GvSV( errgv)) {
CLOSE_G_EVAL; // close brackets
croak( SvPV_nolen( GvSV( errgv)));// propagate exception
// no code is executed after croak
}
CLOSE_G_EVAL; // close brackets
...
This technique provides workaround to a "false alarm" situation, if
SvTRUE( GvSV( errgv)) is true before perl_call_method().
Object protection
After the object destroy stage is completed, it is possible that
object’s data instance is gone, and even simple stage check might cause
segmentation fault. To avoid this, bracketing functions called
"protect_object()" and "unprotect_object()" are used. protect_object()
increments reference count to the object instance, thus delaying its
freeing until decrementing unprotect_object() is called.
All C code that references to an object must check for its stage after
every routine that switches to perl code, because the object might be
destroyed inside the call. Typical code example would be like:
function( Handle object) {
int stage;
protect_object( object);
// call some perl code
perl_call_method( object, "test", ...);
stage = PObject(object)-> stage;
unprotect_object( object);
if ( stage == csDead) return;
// proceed with the object
}
Usual C code never checks for object stage before the call, because
gimme_the_mate() function returns NULL if object’s stage is csDead, and
majority of Prima C code is prepended with this call, thus rejecting
invalid references on early stage. If it is desired to get the C mate
for objects that are in csDead stage, use "gimme_the_real_mate()"
function instead.
init
Object’s method init() is responsible for setting all its initial
properties to the object, but all code that is executed inside init
must be aware that the object’s stage is csConstructing. init()
consists of two parts: calling of ancestor’s init() and setting
properties. Examples are many in both C and perl code, but in short it
looks like:
void
Class_init( Handle self, HV * profile)
{
inherited init( self, profile);
my-> set_index( pget_i( index));
my-> set_name( pget_c( name));
}
pget_XX macros call croak() if the profile key is not present into
profile, but the mechanism guarantees that all keys that are listed in
profile_default() are conveyed to init(). For explicit checking of key
presence pexists() macro is used, and pdelete() is used for key
deletion, although is it not recommended to use pdelete() inside
init().
Object creation and returning
As described is previous sections, there are some precautions to be
taken into account when an object is created inside C code. A piece of
real code from DeviceBitmap.c would serve as an example:
static
Handle xdup( Handle self, char * className)
{
Handle h;
Point s;
PDrawable i;
// allocate a parameters hash
HV * profile = newHV();
// set all necessary arguments
pset_H( owner, var-> owner);
pset_i( width, var-> w);
pset_i( height, var-> h);
pset_i( type, var-> monochrome ? imBW : imRGB);
// create object
h = Object_create( className, profile);
// free profile, do not need it anymore
sv_free(( SV *) profile);
i = ( PDrawable) h;
s = i-> self-> get_size( h);
i-> self-> begin_paint( h);
i-> self-> put_image_indirect( h, self, 0, 0, 0, 0, s.x, s.y, s.x, s.y, ropCopyPut);
i-> self-> end_paint( h);
// decrement reference count
--SvREFCNT( SvRV( i-> mate));
return h;
}
Note that all code that would use this xdup(), have to increase and
decrease object’s reference count if some perl functions are to be
executed before returning object to perl, otherwise it might be
destroyed before its time.
Handle x = xdup( self, "Prima::Image");
++SvREFCNT( SvRV( PAnyObject(x)-> mate)); // Code without these
CImage( x)-> type( x, imbpp1);
--SvREFCNT( SvRV( PAnyObject(x)-> mate)); // brackets is unsafe
return x;
Attaching objects
The newly created object returned from C would be destroyed due perl’s
garbage cleaning mechanism right away, unless the object value would be
assigned to a scalar, for example.
Thus
$c = Prima::Object-> create();
and
Prima::Object-> create;
have different results. But for some classes, namely Widget ant its
descendants, and also for Timer, AbstractMenu, Printer and Clipboard
the code above would have same result - the objects would not be
killed. That is because these objects call Component_attach() during
init-stage, automatically increasing their reference count.
Component_attach() and its reverse Component_detach() account list of
objects, attributed to each other. Object can be attached to multiple
objects, but cannot be attached more that once to another object.
Notifications
All Prima::Component descendants are equipped with the mechanism that
allows multiple user callbacks routines to be called on different
events. This mechanism is used heavily in event-driven programming.
Component_notify() is used to call user notifications, and its format
string has same format as accepted by perl_call_indirect(). The only
difference that it always has to be prepended with ’<s’, - this way the
call success flag is set, and first parameter have to be the name of
the notification.
Component_notify( self, "<sH", "Paint", self);
Component_notify( self, "<sPii", "MouseDown", self, point, int, int);
Notifications mechanism accounts the reference list, similar to attach-
detach mechanism, because all notifications can be attributed to
different objects. The membership in this list does not affect the
reference counting.
Multiple property setting
Prima::Object method set() is designed to assign several properties at
one time. Sometimes it is more convenient to write
$c-> set( index => 10, name => "Sample" );
than to invoke several methods one by one. set() performs this calling
itself, but for performance reasons it is possible to overload this
method and code special conditions for multiple assignment. As an
example, Prima::Image type conversion code is exemplified:
void
Image_set( Handle self, HV * profile)
{
...
if ( pexist( type))
{
int newType = pget_i( type);
if ( !itype_supported( newType))
warn("RTC0100: Invalid image type requested (%08x) in Image::set_type",
newType);
else
if ( !opt_InPaint)
my-> reset( self, newType, pexist( palette) ?
pget_sv( palette) : my->get_palette( self));
pdelete( palette);
pdelete( type);
}
...
inherited set ( self, profile);
}
If type conversion is performed along with palette change, some
efficiency is gained by supplying both ’type’ and ’palette’ parameters
at a time. Moreover, because ordering of the fields is not determined
by default ( although that be done by supplying ’__ORDER__’ hash key to
set() }, it can easily be discovered that
$image-> type( $a);
$image-> palette( $b);
and
$image-> palette( $b);
$image-> type( $a);
produce different results. Therefore it might be only solution to code
Class_set() explicitly.
If it is desired to specify exact order how atomic properties have to
be called, __ORDER__ anonymous array have to be added to set()
parameters.
$image-> set(
owner => $xxx,
type => 24,
__ORDER__ => [qw( type owner)],
);
API reference
Variables
primaObjects, PHash
Hash with all prima objects, where keys are their data instances
application, Handle
Pointer to an application. There can be only one Application
instance at a time, or none at all.
Macros and functions
dG_EVAL_ARGS
Defines variable for $@ value storage
OPEN_G_EVAL, CLOSE_G_EVAL
Brackets for exception catching
build_static_vmt
Bool(void * vmt)
Caches pre-built VMT for further use
build_dynamic_vmt
Bool( void * vmt, char * ancestorName, int ancestorVmtSize)
Creates a subclass from vmt and caches result under ancestorName
key
gimme_the_vmt
PVMT( const char *className);
Returns VMT pointer associated with class by name.
gimme_the_mate
Handle( SV * perlObject)
Returns a C pointer to an object, if perlObject is a reference to a
Prima object. returns nilHandle if object’s stage is csDead
gimme_the_real_mate
Handle( SV * perlObject)
Returns a C pointer to an object, if perlObject is a reference to a
Prima object. Same as "gimme_the_mate", but does not check for the
object stage.
alloc1
alloc1(type)
To be used instead (type*)(malloc(sizeof(type))
allocn
allocn(type,n)
To be used instead (type*)(malloc((n)*sizeof(type))
alloc1z
Same as "alloc1" but fills the allocated memory with zeros
allocnz
Same as "allocn" but fills the allocated memory with zeros
prima_mallocz
Same as malloc() but fills the allocated memory with zeros
prima_hash_create
PHash(void)
Creates an empty hash
prima_hash_destroy
void(PHash self, Bool killAll);
Destroys a hash. If killAll is true, assumes that every value in
the hash is a dynamic memory pointer and calls free() on each.
prima_hash_fetch
void*( PHash self, const void *key, int keyLen);
Returns pointer to a value, if found, nil otherwise
prima_hash_delete
void*( PHash self, const void *key, int keyLen, Bool kill);
Deletes hash key and returns associated value. if kill is true,
calls free() on the value and returns nil.
prima_hash_store
void( PHash self, const void *key, int keyLen, void *val);
Stores new value into hash. If the key is already present, old
value is overwritten.
prima_hash_count
int(PHash self)
Returns number of keys in the hash
prima_hash_first_that
void * ( PHash self, void *action, void *params, int *pKeyLen, void **pKey);
Enumerates all hash entries, calling action procedure on each. If
the action procedure returns true, enumeration stops and the last
processed value is returned. Otherwise nil is returned. action have
to be function declared as
Bool action_callback( void * value, int keyLen, void * key, void * params);
params is a pointer to an arbitrary user data
kind_of
Bool( Handle object, void *cls);
Returns true, if the object is an exemplar of class cls or its
descendant
PERL_CALL_METHOD, PERL_CALL_PV
To be used instead of perl_call_method and perl_call_pv, described
in perlguts manpage. These functions aliased to a code with the
workaround of perl bug which emerges when G_EVAL flag is combined
with G_SCALAR.
eval
SV *( char *string)
Simplified perl_eval_pv() call.
sv_query_method
CV * ( SV * object, char *methodName, Bool cacheIt);
Returns perl pointer to a method searched by a scalar and a name If
cacheIt true, caches the hierarchy traverse result for a speedup.
query_method
CV * ( Handle object, char *methodName, Bool cacheIt);
Returns perl pointer to a method searched by an object and a name
If cacheIt true, caches the hierarchy traverse result for a
speedup.
call_perl_indirect
SV * ( Handle self, char *subName, const char *format, Bool cdecl,
Bool coderef, va_list params);
Core function for calling Prima methods. Is used by the following
three functions, but is never called directly. Format is described
in "Calling perl code" section.
call_perl
SV * ( Handle self, char *subName, const char *format, ...);
Calls method of an object pointer by a Handle
sv_call_perl
SV * ( SV * mate, char *subName, const char *format, ...);
Calls method of an object pointed by a SV*
cv_call_perl
SV * ( SV * mate, Sv * coderef, const char *format, ...);
Calls arbitrary perl code with mate as first parameter. Used in
notifications mechanism.
Object_create
Handle( char * className, HV * profile);
Creates an exemplar of className class with parameters in profile.
Never returns nilHandle, throws an exception instead.
create_object
void*( const char *objClass, const char *format, ...);
Convenience wrapper to Object_create. Uses format specification
that is described in "Calling perl code".
create_instance
Handle( const char * className)
Convenience call to "Object_create" with parameters in hash
’profile’.
Object_destroy
void( Handle self);
Destroys object. One of few Prima function that can be called in
re-entrant fashion.
Object_alive
void( Handle self);
Returns non-zero is object is alive, 0 otherwise. In particular,
current implementation returns 1 if object’s stage is csNormal and
2 if it is csConstructing. Has virtually no use in C, only used in
perl code.
protect_object
void( Handle obj);
restricts object pointer from deletion after Object_destroy(). Can
be called several times on an object. Increments Object.
protectCount.
unprotect_object
void( Handle obj);
Frees object pointer after Object. protectCount hits zero. Can be
called several times on an object.
parse_hv
HV *( I32 ax, SV **sp, I32 items, SV **mark, int expected, const char *methodName);
Transfers arguments in perl stack to a newly created HV and returns
it.
push_hv
void ( I32 ax, SV **sp, I32 items, SV **mark, int callerReturns, HV *hv);
Puts all hv contents back to perl stack.
push_hv_for_REDEFINED
SV **( SV **sp, HV *hv);
Puts hv content as arguments to perl code to be called
pop_hv_for_REDEFINED
int ( SV **sp, int count, HV *hv, int shouldBe);
Reads result of executed perl code and stores them into hv.
pexist
Bool(char*key)
Return true if a key is present into hash ’profile’
pdelete
void(char*key)
Deletes a key in hash ’profile’
pget_sv, pget_i, pget_f, pget_c, pget_H, pget_B
TYPE(char*key)
Returns value of ( SV*, int, float, char*, Handle or Bool) that is
associated to a key in hash ’profile’. Calls croak() if the key is
not present.
pset_sv, pset_i, pset_f, pset_c, pset_H
void( char*key, TYPE value)
Assigns a value to a key in hash ’profile’ and increments reference
count to a newly created scalar.
pset_b
void( char*key, void* data, int length)
Assigns binary data to a key in hash ’profile’ and increments
reference count to a newly created scalar.
pset_sv_noinc
void(char* key, SV * sv)
Assigns scalar value to a key in hash ’profile’ without reference
count increment.
duplicate_string
char*( const char *)
Returns copy of a string
list_create
void ( PList self, int size, int delta);
Creates a list instance with a static List structure.
plist_create
PList( int size, int delta);
Created list instance and returns newly allocated List structure.
list_destroy
void( PList self);
Destroys list data.
plist_destroy
void ( PList self);
Destroys list data and frees list instance.
list_add
int( PList self, Handle item);
Adds new item into a list, returns its index or -1 on error.
list_insert_at
int ( PList self, Handle item, int pos);
Inserts new item into a list at a given position, returns its
position or -1 on error.
list_at
Handle ( PList self, int index);
Returns items that is located at given index or nilHandle if the
index is out of range.
list_delete
void( PList self, Handle item);
Removes the item from list.
list_delete_at
void( PList self, int index);
Removes the item located at given index from a list.
list_delete_all
void ( PList self, Bool kill);
Removes all items from the list. If kill is true, calls free() on
every item before.
list_first_that
int( PList self, void * action, void * params);
Enumerates all list entries, calling action procedure on each. If
action returns true, enumeration stops and the index is returned.
Otherwise -1 is returned. action have to be a function declared as
Bool action_callback( Handle item, void * params);
params is a pointer to an arbitrary user data
list_index_of
int( PList self, Handle item);
Returns index of an item, or -1 if the item is not in the list.
AUTHOR
Dmitry Karasik, <dmitry@karasik.eu.org>.
SEE ALSO
Prima