|
NAMEPrima::internals - Prima internal architectureDESCRIPTIONThis documents elucidates the internal structures of the Prima toolkit, its loading considerations, object and class representation and C coding style.BootstrapInitializingFor 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.ConstantsPrima 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 methodsVirtual method tablesPrima 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 routinesMajority 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 ( prima-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 classesPrima defines the following built-in classes: (in hierarchy order)Object Component AbstractMenu AccelTable Menu Popup Clipboard Drawable DeviceBitmap Printer Image Icon File Region 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). ObjectsThis 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.CreationAll 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. DestructionAs 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 instanceAll 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 stagesIt 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:
Coding techniquesAccessing object dataC 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 codecall_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. ExceptionsAs 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 evaluatingSvTRUE( 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 protectionAfter 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. initObject'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 returningAs 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-> type == dbtBitmap) ? 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 objectsThe 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 than once to another object. NotificationsAll 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 has 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 settingPrima::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("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 referenceVariables
Macros and functions
AUTHORDmitry Karasik, <dmitry@karasik.eu.org>.SEE ALSOPrima
Visit the GSP FreeBSD Man Page Interface. |