Key concepts of application development with tinymail
Object oriented development
If you don't care about subjective chit-chat about programming languages, I kindly advise you to skip this section and go straight to the next one of this document.
The C language in which tinymail is developed and which is the default language for application development (but not the only one) has certain limitations.
The programming language C doesn't know about objects nor interfaces.It wasn't very interesting to invent an own object system. Therefore the author of the Tinymail framework chose to use GLib and GObject for this.
GObject provides a GTypeInterface interface type and a GObject base class. They are used a lot within the framework.
In C you don't have syntax sugar for methods, interfaces and classes. Well, actually you have if you would put function pointers in structs and call the functions that get pointed to, methods. For various reasons this becomes quite impractical. Such an API wouldn't be very easy to either use or develop.
In GObject the standard is that each method starts with the underscored type name, that the type name itself is written in CamelCase (I know that a lot C developers prefer the mytype_t style, and I do too) and the 'self' pointer is always the first in the parameters list of each method.
For example this type in a typical OO language:
- My/TheType.cs -
namespace My {
public virtual class TheType {
public virtual void Method (int a) { ... }
}
}
would translate to this in GObject:
- my-the-type.h -
typedef struct _MyTheType MyTheType;
typedef struct _MyTheTypeClass MyTheTypeClass;
#define MY_THE_TYPE_METHOD_GET_CLASS(o) ((MyTheTypeClass *)...)
struct _MyTheType { GObject parent; };
struct _MyTheTypeClass {
GObjectClass parent;
/* virtuals */
void (*method_func) (MyTheType *self, int a);
};
gtype my_the_type_get_type (void);
void my_the_type_method (MyTheType *self, int a);
void
my_the_type_method_default (MyTheType *self, int a) { ... }
void
my_the_type_method (MyTheType *self, int a)
{
MY_THE_TYPE_METHOD_GET_CLASS (self)->method_func (self, a);
}
static void
my_the_type_class_init (MyTheTypeClass *klass)
{
klass->method_func = my_the_type_method_default;
}
There's one really good reason why Tinymail is done in C: The Tinymail ABI and API become very portable. A better word for its portability is perhaps 'scalability': It's not only portable between architectures but also scalable between language bindings when it comes down to making it possible to use the C API in a higher programming language like C#, Java, D, Python or any other such higher language. Another plus is the fact that I don't have to care about C++ ABI problems.
If Tinymail would have been written in one of the popular higher programming languages, it would have been *much* harder to support the exact same code and API in another programming language. Right now, there are language binding code generators for GObject libraries like Tinymail.
Interface oriented
Nearly every Tinymail type has its API defined by a so-called interface. An interface isn't a type that can be instantiated: it expects an implementation. Most of the Tinymail interfaces have one or more such implementations.
You can compare these "interfaces" with the .NET and Java interfaces, or with fully abstract classes in C++. Tinymail's types can and often do implement multiple such interfaces. Tinymail's interfaces can depend on other interfaces (which basically means the same as when you inherit an interface from another interface in for example Java and .NET or when you specify that your type must implement multiple interfaces).
Tinymail components that play the role of a client of such an interface, usually only consume the interface's API. Making them effectively independent from the real implementation and only dependent on the contract and the API of the interface.
What this means is that you can reimplement the types without breaking things. You can also reimplement by inheriting, but that is explained later in this document.
This is why Tinymail's TnyList and TnyIterator don't have constructors. You do have a tny_simple_list_new constructor that will return you "a" instance of a TnyList implementation. In that case it's one that uses a doubly linked list internally (but that shouldn't matter for you, as you will only always be using the TnyList API on the instance).
In a typical OO language, the TnySimpleList type would look like this:
class TnySimpleList implements TnyList
{
GList *list;
public TnySimpleList () {
this.list = g_list_new ();
}
~TnySimpleList () {
g_list_free (this.list);
}
/* Implementation of TnyList's methods */
}
Same for the TnyMsg, the TnyHeader, the TnyMsgView and the TnyFolder (and many others too). You will also usually *only* use the interface's API with the instances. Non interface API of a type basically means that the API is *specific* for *only that* implementation of the interface.
This API doesn't exist but say there would be a tny_simple_list_set_glist (self, list), then that API wouldn't be available in the API of TnyList but in stead would be only available in the API of TnySimpleList. In the typical OO language that would look like this:
class TnySimpleList implements TnyList
{
GList *list;
public TnySimpleList () {
this.list = g_list_new ();
}
~TnySimpleList () {
g_list_free (this.list);
}
/* Implementation of TnyList's methods */
public void prepend (object item) implements TnyList.prepend {
...
}
/* .... */
public void set_glist (GList *list) {
if (this.list)
g_list_free (this.list);
this.list = list;
}
}
This means that the tny_simple_list_set_glist API (which doesn't exist in reality, I repeat) cannot be used on a TnyOtherList type, but that an API like the tny_list_prepend can be used on both the TnySimpleList and the TnyOhterList types.
All non-interface types are virtual
This means that when you are subclassing (inheriting from) the type, that you can override all of the implementations. More information about this can be found here.
Reference counting
Unlike in Gtk+ there's strict consistency within the Tinymail framework when it comes to reference counting. The rule is very simple: everything adds a reference. By that I mean that every time an object is returned, you also need to unreference it. There's also no escaping that.
It also means that every time you put an instance in something, like in a TnyList, that a reference is added too. Every time something that held a reference on something else gets finalized, it will unreference the something else.
This indeed means that if you want to loose the ownership of an instance and hand it over to a list, that you must after adding it to the list (using for example tny_list_prepend or tny_list_append) unreference your own initial reference. It also means that if you get the instance and you are done with it, that you need to unreference what you got.
There's not a single exception in Tinymail with one exception for the optional default Gtk+ components: they follow the standard Gtk+ rules for when you put them in Gtk+ containers and things like that (they 'borrow' the initial reference, just like in standard Gtk+).
The main reason for this is trying to be more thread aware or thread safe: In your thread you *know* that when you got an instance from something, that your reference will be with you until *you* in *your thread* are done with it. If all other code is correct, that reference will not suddenly disappear caused by a worker thread or a background procedure. Which is something that could happen in case the framework wouldn't add that reference before returning the instance to you.
The TnyList as example
The "refcount=n" is about the one instance "my_object". In a real situation more than one such instance will usually be in that TnyList. It's not about the reference counting of "my_list" nor about "iter", only about "my_object".
TnyList *my_list = tny_simple_list_new ();
GObject *my_object = my_object_new (); /* refcount=1 */
TnyIterator *iter;
/* Put it in my_list and loose my_object's initial reference */
tny_list_prepend (my_list, my_object); /* refcount=2 */
g_object_unref (my_object); /* refcount=1 */
iter = tny_list_create_iterator (list);
while (!tny_iterator_is_done (iter))
{
GObject *my_object_in_loop = tny_iterator_get_current (iter);
/* refcount=2 */
/* We need to loose the reference that get_current has added! */
g_object_unref (my_object_in_loop); /* refcount=1 */
tny_iterator_next (iter);
}
/* refcount=1 (still, nothing changed) */
g_object_unref (iter);
/* And after this one, will my_object be finalized */
g_object_unref (my_list); /* refcount=0 */
Another example, the TnyMsgView:
TnyMsg *msg_a = tny_msg_new (....); /* refcount_msg_a=1 */ TnyMsg *msg_b = tny_msg_new (....); /* refcount_msg_b=1 */ TnyMsgView *view = tny_gtk_msg_view_new (.....); tny_msg_view_set_msg (view, msg_a); /* refcount_msg_a=2 */ g_object_unref (G_OBJECT (msg_a)); /* refcount_msg_a=1 */
Now imagine we overwrite the message of the view:
tny_msg_view_set_msg (view, msg_b); /* refcount_msg_a=0 */ /* refcount_msg_b=2 */ g_object_unref (msg_b); /* refcount_msg_b=1 */
And when we'll finalize view:
g_object_unref (view); /* refcount_msg2=0 */
Getting a header example
static void
on_header_view_tree_row_activated (GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer userdata) {
GtkTreeModel *model;
GtkTreeIter iter;
model = gtk_tree_view_get_model(treeview);
if (gtk_tree_model_get_iter(model, &iter, path))
{
TnyHeader *header;
gtk_tree_model_get (model, &iter,
TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
&header, -1);
/* refcount_header=2, gtk_tree_model_get added a reference too */
if (header) {
printf ("%s\n", tny_header_get_subject (header));
g_object_unref (header);
/* refcount_header=1 */
}
}
}
static void
my_routine ()
{
gtk_tree_view_set_model (view, model);
/* When we switch the model, the old model will finalize and
therefore will refcount_header=0 */
}
