Most of the information here is obsolete.

Camel.Object is now derived from GObject and uses GObject's property and signal APIs instead of its own homegrown mechanisms. The only relevant sections left are Object Bags and Persistent State.

Camel.Object

Camel is based on an object-oriented design, using an object system implemented in C. Originally it was written using GtkObject, but because this was not thread aware, a new object system was written. This is a fully thread-capable object and event system, but otherwise works similarly to a much simplified GObject.

Classes, Types, and Interfaces

In Camel.Object, classes are types, there is no difference. So, there are no type enumerations, type pointers are just class pointers. This simplifies a lot of common operations.

Interfaces are special classes which can be added to another class. They can also be subclassed like normal types, but can never have separate instances created for them.

Like GObject, class as well as instance data is defined by using C structures embedded at the start of sub-classed C structures.

There is some commented out code in camel-object.c which would allow class and instance data to be defined in a very different way. That is, each class would provide instance and class data sizes, and the type engine would dynamically calculate instance and class data sizes. With some trivial macros to lookup the instance/class data offsets from the base pointers, this would allow objects to completely de-couple from the binary sizes of their parent classes. This would let you two major things happen. Private data would not require a separate pointer, it would simply be part of the base structure for that class, but not visible. Second, it would allow transparent forward and backward binary compatability, even in the case of completely different data structures.

The class implementation is trivial, the problem is changing all of the object implementations to look up their data pointers separately!

Registering Classes

A single function registers new classes. Note that the CamelType return value is infact just a pointer which equates directly to the class data.

 CamelType camel_type_register(CamelType parent, const char * name,
                              size_t instance_size,
                              size_t classfuncs_size,
                              CamelObjectClassInitFunc class_init,
                              CamelObjectClassFinalizeFunc  class_finalize,
                              CamelObjectInitFunc instance_init,
                              CamelObjectFinalizeFunc instance_finalize);

All types need to be given a unique name, and supplied the parent type. The root type, CamelObject is accessed using the simple pointer camel_object_type. This is because this is always initialized once camel is intitalized. By convention, all other classes will have a class_name_get_type function, which just calls camel_type_register for that class.

Class lifecycle

class_init

  • This is called each time this class, or a sub-class is created (registered), to initialise the class data structures. This is where any event hooks or interfaces must be added to the class. This will be called with a global (recursive) type-lock held, so it cannot wait on other threads for any processing, and should run quickly.

class_finalize

  • This is intended to be called whenever a class is no longer used. Currently this is never called since classes are never unloaded.

Object Lifecycle

For each class implementation there is instance init/finalize and class init/finalize callbacks. These will be called in the following circumstances:

init

  • This is called each time an instance of this class or any sub-class, is created. First, a cleared block of memory large enough for the instance will be allocated, and then each init function, from the root class to the type of the object is invoked on that memory, in order. All class initialization functions are always called automatically, they must not be called directly. No locks will be held to invoke the init function.

finalize

  • Once an object has had its final reference count removed, a finalize event will be triggered, and then each finalize method on each class is invoked in the reverse order to the way the initialization method is called. Again, all class finalize methods which are defined are called automatically.

The finalize method (and init method really) must be careful not to call functions which may invoke virtual methods. This is because a virtual method of a sub-class may be invoked, which will try to access data which has been partially freed. Objects with complex lifecycles get around this by adding additional virtual methods like disconnect() which can safely perform complex resource deallocation.

Registering Interfaces

As stated previously, interfaces are special types of instance-free classes. As such, they have a slightly simpler registration function. Otherwise they are created and initialized the same way classes are.

The root class for interfaces is camel_interface_type.

 CamelType camel_interface_register(CamelType parent, const char *name,
                                   size_t classfuncs_size,
                                   CamelObjectClassInitFunc class_init,
                                   CamelObjectClassFinalizeFunc class_finalize);

Interfaces cannot be used on their own, they must be added to a class. Therefore a class init function must call add_interface to add an interface to itself.

 void camel_object_class_add_interface(CamelObjectClass *klass, CamelType itype);

Interface methods must then use a <code>get_interface</code> function to access the interface class data for a given object. This will just return NULL if the class of this instance does not implement this interface, otherwise it returns a pointer to the interface instance.

 void *camel_object_get_interface(void *vo, CamelType itype);

Currently, although interfaces are implemented and tested, no code uses them. Which is a pity as it could greatly simplify some of the design of camel - particularly for example the offline store/folder mechanism. CamelStore however does define and implement a CamelISubscribe interface as an example, but no store implementations and no client code currently uses it.

Reference Counting

Like GObject, Camel objects are reference counted. Although the reference counting is thread-safe, which makes it a little slower, but assuming a decent thread implementation and the fewer objects involved, this is not an issue.

 void camel_object_ref(void *);
 void camel_object_unref(void *);

In general, when a function takes an object argument, it adds its own reference. This simplifies reference counting calculations, i.e. if you create an object, you must unref it when you no longer need it, even if you pass it to some other function.

Events

Object events are named callbacks that are given a single pointer argument. This greatly simplifies marshalling of event callbacks and makes event emission fairly quick. In addition to this, event emission is properly handled in multithreaded environments.

i.e. if an emission is occuring in say thread one, and simultaneously an event hook is removed in thread two, before it was invoked, it will not be invoked on the callback which was removed in thread two.

Defining an Event

Since events are untyped, defining events is trivial. In a class initialization function, a new event is added to a class using:

 void camel_object_class_add_event(CamelObjectClass *klass, const char *name,
                                   CamelObjectEventPrepFunc prep);

If supplied, the prep function will be called with the event emission data, and it may swallow events depending on some other state, by returning TRUE to block the event. This allows implementations to monitor or control event emission from a central location. For example, it is used in CamelFolder to implement event freezing and filtering of recent messages.

Events are only ever defined in the code, it requires no callback pointer in the class structure.

Triggering an Event

Triggering events is also trivial.

 void camel_object_trigger_event(void *obj, const char *name, void *event_data);

The type (class) of the object will be checked to see if it has an event named name, and if so, any registered callbacks will be invoked with the event_data, in a synchronous manner.

It would be nice if events could be emitted asynchronously, through a processing queue for ordering constraints, however this was never implemented. Currently asynchronicity must be done in client code.

Listening to Events

Client and other internal code can listen to events using a hooking function. Like GObject signals, event listeners are given a user-data field, which is passed as the last argument to the hook event handler.

 typedef void (*CamelObjectEventHookFunc) (CamelObject *, gpointer, gpointer);
 
 CamelObjectHookID camel_object_hook_event(void *obj, const char *name,
                                           CamelObjectEventHookFunc hook, void *data);
 void camel_object_remove_event(void *obj, CamelObjectHookID id);
 void camel_object_unhook_event(void *obj, const char *name, CamelObjectEventHookFunc hook, void *data);

You would normally use hook_event and remove_event, whereas unhook_event is an older function still around for compatibility.

After you have hooked onto the event, you may receive event callbacks on your hook function, at any time, and more importantly from any thread. Once you remove or unhook from an event, you are guaranteed you will no longer receive the event.

Thus, any GUI related event processing must be relayed back to the GUI thread by some external means.

Important note: Another important note is that event hook functions must not emit events on other objects directly. This can lead to deadlocks in certain circumstances depending on the event. So any event handlers which may trigger new events should process them in another thread asynchronously. Another argument for an asynchronous event queue.

Virtual Methods

Virtual methods are implemented by setting up and calling function pointers in the class data.

When a method is defined as a virtual method, you should normally not ever call that function itself directly from within a given C file. It should go through the virtual pointer invocation function for that instance - since it may be possible the type has been sub-classed, and calling the function directly will miss out on any overrides.

Defining virtual methods

For example, to add a do_work virtual method to MyClass:

 struct _MyClass {
        CamelObjectClass parent_class;
 
        void (*do_work)(MyObject *o, int count);
 };

Invoking the method can then be done either through a simple macro, or a one line function, depending on your preference. A macro should be faster, but may produce bulkier code. A function allows you to do more tests and assertions, but adds linker and symbol overhead and aids debugging.

 #define my_do_work(o, count) ((MyClass *)((CamelObject *)o)->klass)->do_work(o, count)

 void my_do_work(MyObject *o, int count)
 {
        g_return_if_fail(camel_object_is((CamelObject *)o, my_get_type()));
 
        ((MyClass *)((CamelObject *)o)->klass)->do_work(o, count)
 }

Usually in the given C source file you would just make all that messy casting a simple macro. Also note that using direct C casting is much preferred to using the CAMEL_xxxx_GET_CLASS macro's, as they perform redundant type checking, and make stepping through functions tedious.

Also note that this is ANSI-C or ISO-C or C-89 or newer syntax, for readability you should avoid the older K&R style syntax for function pointer evaluation, which requires a pointer dereference, i.e.

        (* ((MyClass *)((CamelObject *)o)->klass)->do_work) (o, count)

Calling super-classes

Most classes sets up a _parent pointer in their _get_type function. This can then be cast to the appropriate class structure and the functions invoked directly.

e.g. to call the super-class of the current class's meta_set virtual method, assuming parent_class is a static variable in this C source file.

        res = ((CamelObjectClass *)parent_class)->meta_set(o, name, value);

By calling, or not calling super-classes, the standard object-oriented mechanisms of method override and inheritance may be implemented.

Note that some virtual methods are abstract, in that they do not provide methods, or those methods should always be overriden. This depends on the class.

Object Bags

The camel_object_bag interface is an atomic and thread-safe mechanism for storing unreffed accesses to named objects. Bit of a mouth-full. Another way to look at them is a thread-safe cache.

Often, one needs to track a bunch of named objects that have been created, but that you don't want to keep a reference to. e.g. CamelStore keeps track of all open folders, but it doesn't want to keep them open if the client code is no longer accessing them.

It is tempting to just use a hash-table, and a 'finalise' hook, but this will not work. The problem is the 'finalise' hook will be invoked without locking after the object has been finalised. There is thus no way to atomically remove the object from the hashtable before another access to that object will re-ref it; which will hit assertions and crash your program. The problem is the object reference counting and hash table resolution must all sit under the same lock. In addition to this, often you want to create objects which may take some time to create - you don't want to lock the whole table while the object is being created (e.g. accesses to existing objects shouldn't block), which you would need to otherwise to avoid clashes.

So this is where the object bag interfaces come in. In essence they are just a hashtable which has a closer binding to the internals of the reference counting mechanism. But they are a little more than that, in that they provide a rendesvous mechanism to efficiently avoid clashes.

Create and destroy are pretty basic, basically a normal hash table, but with copied keys.

 CamelObjectBag *camel_object_bag_new(GHashFunc hash, GEqualFunc equal, CamelCopyFunc keycopy, GFreeFunc keyfree);
 void camel_object_bag_destroy(CamelObjectBag *bag);

We then have a couple of accessor functions. They both return referenced objects, if they are found. But the get() method will also wait if another thread is currently creating the object in question. The peek() method wont.

 void *camel_object_bag_get(CamelObjectBag *bag, const void *key);
 void *camel_object_bag_peek(CamelObjectBag *bag, const void *key);

We then have the all-important insertion functions. Becuase the interfaces are designed for concurrent access without locks, it is a two-stage process. reserve() will either behave the same as get(); if the object is currently created or being created, it will return a referenced object once it is created. Or if not, it will mark the key as reserved and return NULL.

So, if reserve() returns NULL, then it means your function can create the object, and once done it must either call add() to store the object into the table, or abort() to indicate that the operation failed, and nothing will be stored in the table.

 void *camel_object_bag_reserve(CamelObjectBag *bag, const void *key);
 void camel_object_bag_add(CamelObjectBag *bag, const void *key, void *o);
 void camel_object_bag_abort(CamelObjectBag *bag, const void *key);

Then there are some administrative functions, rekey() which is used to rename objects directly in the table, list() which gets all objects referenced, atomically into an array, which can then be processed at lesuire without requiring locks, and remove() which takes an object out of a bag, by object, not key.

 void camel_object_bag_rekey(CamelObjectBag *bag, void *o, const void *newkey);
 GPtrArray *camel_object_bag_list(CamelObjectBag *bag);
 void camel_object_bag_remove(CamelObjectBag *bag, void *o);

These are (mostly) all described in more detail in the api docs/source-code in camel-object.c.

Object State

There are also some virtual functions and utilities in CamelObject for managing state.

The state may or may not be persistent; some state is just information on the running object, other state may be written to disk for permanent storage. It depends on the object instance as to whether it supports persistent state.

Object Properties

Camel object properties are a generic way to pass information from objects to the client code. It is the preferred method of adding accessors to objects - to reduce the overhead of excessive symbol tables, and to reduce linking requirements to hidden objects. Many of the existing Camel methods which are just accessors are being phased out in coming releases. e.g. camel_folder_get_unread_count().

It works through 3 api entry points and virtual methods on camel-object:

        camel_object_setv(void *obj, CamelException *ex, CamelArgV *argv);
        camel_object_getv(void *obj, CamelException *ex, CamelArgGetV *argv);
        camel_object_free(void *obj, guint32 tag, void *data);

There are a couple of varargs helper methods as well, which are the normal get/set methods:

        camel_object_get(void *obj, CamelException *ex, ...);
        camel_object_set(void *ovj, CamelException *ex, ...);

The reason varags/array functions are used instead of individual calls - despite their overhead, is for atomicity.

Another not-particularly-obvious trait of these functions is how the tags should be defined, and how the virtual methods should be implemented.

Tags

A tag is a 32 bit value which identifies the property in question. A simple string could have been used, but numerical tags are simpler to implement; and since the properties are an inherited trait, they are easy to make numerically unique. To automate some of the processing where possible, the tag includes not only a 28 bit unique identifier, but also a 4 bit type field.

Some basic tag values and conventions are defined in camel-arg.h.

 enum camel_arg_t {
        CAMEL_ARG_END = 0,
        CAMEL_ARG_IGNORE = 1,   /* override/ignore an arg in-place */
 
        CAMEL_ARG_FIRST = 1024, /* 1024 args reserved for arg system */
 
        CAMEL_ARG_TYPE = 0xf0000000, /* type field for tags */
        CAMEL_ARG_TAG = 0x0fffffff, /* tag field for args */
 
        CAMEL_ARG_OBJ = 0x00000000, /* object */
        CAMEL_ARG_INT = 0x10000000, /* int */
        CAMEL_ARG_DBL = 0x20000000, /* double */
        CAMEL_ARG_STR = 0x30000000, /* c string */
        CAMEL_ARG_PTR = 0x40000000, /* ptr */
        CAMEL_ARG_BOO = 0x50000000, /* bool */
 };

To prevent the tag system bloating excessively, there are only a few simple types. Some special tags are CAMEL_ARG_END (or normally, simply 0) is used to terminate a tag list, CAMEL_ARG_IGNORE may be used to set a value which should be skipped/ignored (this is used to override superclasses), and the CAMEL_ARG_FIRST is the first value to be used by the first super-object - in this case, CamelObject.

CamelObject Tags

Because the arguments contain both an identifier, and a type, the arguments are defined using two enums/defines.

By convention, the first table defines the numerical id of each property, and begins with CAMEL_type_ARG_. These values are used by the implementation to identify the value being set. These values should only ever be added to at the end, to provide forward and backward compatability transparently:

 enum {
        CAMEL_OBJECT_ARG_DESCRIPTION = CAMEL_ARG_FIRST,
        CAMEL_OBJECT_ARG_METADATA,
        CAMEL_OBJECT_ARG_STATE_FILE,
        CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES,
 };

The second table then defines the application-visible values, which include the type information as well:

 enum {
        CAMEL_OBJECT_DESCRIPTION = CAMEL_OBJECT_ARG_DESCRIPTION | CAMEL_ARG_STR,
        CAMEL_OBJECT_METADATA = CAMEL_OBJECT_ARG_METADATA | CAMEL_ARG_PTR,
        CAMEL_OBJECT_STATE_FILE = CAMEL_OBJECT_ARG_STATE_FILE | CAMEL_ARG_STR,
        CAMEL_OBJECT_PERSISTENT_PROPERTIES = CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES | CAMEL_ARG_PTR,
 };

These particular properties proabaly need explaining at some point. But briefly, PERSISTENT_PROPERTIES is used to get a list of property tags which should be stored persistently for the object. This information, together with the object meta-data (another application-level property mechanism) is stored in the file pointed to by STATE_FILE, if that property is set on the object. The Evolution Mail folder properties dialogue is driven by this mechanism and it lets loaded camel backends add to the properties list without requiring direct linkage. PERSISTENT_PROPERTIES is a GSList of CamelProperty structures.

See also camel-folder.h for CamelFolder properties.

Args

Because setting and getting arguments require different pointers, there are two objects which represent each argument. They include a tag describing the type and property name.

One for setting, which is passed the object in question.

 struct _CamelArg {
        guint32 tag;
        union {
                void *ca_object;
                int ca_int;
                double ca_double;
                char *ca_str;
                void *ca_ptr;
        } u;
 };

And one for getting, which is passed a pointer to the object's pointer.

 struct _CamelArgGet {
        guint32 tag;
        union {
                void **ca_object;
                int *ca_int;
                double *ca_double;
                char **ca_str;
                void **ca_ptr;
        } u;
 };

Some helper macros ease access to the union:

 #define ca_object u.ca_object
 #define ca_int u.ca_int
 #define ca_double u.ca_double
 #define ca_str u.ca_str
 #define ca_ptr u.ca_ptr

These are passed to functions as arryas, using the CamelArgV and CamelArgGetV structures, defined in camel-arg.h.

CamelObject.getv()

getv() is a virtual method which is used to access one or more properties atomically. Each implementation should walk the array, processing each in turn:

# Mask out the type and switch on the property value (AND CAMEL_ARG_TAG). # Process any of its own properties, setting the value directly into the pointer's pointer. # Process any properties it is overriding. # Ignore all other values.

For any values it processes, it should set the tag to ignore, using the macro camel_argv_ignore().

It then passes the getv method onto the superclass.

It is up to the implementation as to whether it sets exceptions for bad requests or just ignores them; the latter should provide more graceful fallback. Either way, this mechanism provides a simple over-rideable, generic properties mechanism.

CamelObject.setv()

setv() works similarly to getv() but has less indirection to the values to set. Processing should proceed in the same manner as for the getv() method.

CamelObject.free()

The free method is just given a single value and the property tag. This allows properties to be freed specifically on a per-property basis. For example, some properties may be static strings, others may be allocated; the free function can either noop or free as appropriate. Note that any strings which may change due to multi-threaded access MUST be returned as copies.

Object Meta-Data

Object meta-data is a simple key-value string pair for client supplied information which may be persistent on the object.

 char *camel_object_meta_get(void *vo, const char * name);
 gboolean camel_object_meta_set(void *vo, const char * name, const char *value);

To clear a value, set it to NULL.

In addition, a <tt>meta_changed</tt> event exists which will indicate which meta-data value changed.

Persistent State

Persistent state on an object is loaded/stored using these functions:

 int camel_object_state_read(void *vo);
 int camel_object_state_write(void *vo);

These will write to the file defined by the CAMEL_OBJECT_STATE_FILE property. If this is unset, then they do nothing.

These are usually used internally by implementations after they have been instantiated to pre-load any state and meta-data for that object.

Currently, only CamelFolder implements any persistent state.

Debugging

By default, camel will be compiled in a manner which tracks all instances of all objects internally. This tree may be accessed by walking the various lists in the CamelObjectClass, but a simple debug function will print information on all instances of all objects and all events and all hooked events as well, from a given base class:

 void camel_object_class_dump_tree(CamelType root);

If camel is compiled without instance tracking, then this will only dump the class heirarchy and events. This can be used to track down leaks and the like.

Need to point to CAMEL_DEBUG environmental variable, which should be written elsewhere.

Apps/Evolution/Camel.Object (last edited 2013-08-08 22:50:10 by WilliamJonMcCann)