Binary Compatibility in Libraries

For stable and released libraries, it is very important to try to preserve binary compatibility across major revisions of the code. Library developers should try not to alienate users by making frequent binary incompatible changes on production libraries. Of course, libraries that are being developed or are labeled as not done can change as much as they need.

A library typically exports a number of interfaces. These include routine names, the signatures or prototypes of these routines, global variables, structures, structure fields (both the types and the meaning of these), the meaning of enumeration values, and the semantics of the files that are created by the library. To keep binary compatibility means that these interfaces will not be changed. You can add new interfaces without breaking binary compatibility, but you cannot change the existing ones, since old programs will no longer run correctly.

This section includes a number of hints on how you can make your library code binary compatible with old versions of itself.

Keeping binary compatibility means that programs and object files that were compiled against a previous version of the code will continue to work without recompiling if the library is replaced. A detailed description of this can be found in the (libtool)Interfaces info node in the GNU libtool documentation.

Private Information in Structures

In the GNOME system, it is very common task to create a new GtkObject by creating a structure, whose first member is the class this object inherits from, and then a number of instance variables that are added after this first member.

This design scheme usually leads to code that is hard to update and change: if the internal state that the object needs to maintain is extended, programmers need to resort to various hacks to keep the size of the structures the same. That is, it is hard to add new fields to the public structures without breaking binary compatibility, since the structure fields may change and the structures may change size.

We therefore suggest a strategy that developers may adopt when creating new objects. This strategy will ensure binary compatibility and at the same time will improve the readability and maintainability of the code.

The idea is that for any given object, the programmer should create three files: the first one contains the public contract between the library and the user (this is the header file that gets installed in the system); the second one contains the implementation of the object; and the third file contains a structure definition for the internal or private fields that do not need to be in the public structure.

The public API structure would include a private pointer element that would point to the per-instance private data. The implementation routines would dereference this pointer to access private data; the pointer of course points to a structure that is allocated privately.

For example, imagine we are creating a GnomeFrob object. The widget implementation will be split in three files:

Table 1. Files that make up the sample GnomeFrob widget

Filename Contents
gnome-frob.h GnomeFrob public API
gnome-frob.c GnomeFrob object implementation
gnome-frob-private.h GnomeFrob object private implementation data. You might want to use this if you plan on sharing the private data across various C files. If you limit the use of this private information to a single file, you do not need this, defining the structure in the implementation file will achieve the same effect.
>

Example 1. Sample gnome-frob.h public API.

typedef struct _GnomeFrob GnomeFrob;
typedef struct _GnomeFrobPrivate GnomeFrobPrivate;

struct _GnomeFrob {
       GtkObject parent_object;

       int  public_value;
       GnomeFrobPrivate *priv;
} GnomeFrob;

GnomeFrob *gnome_frob_new (void);
void       gnome_frob_frobate (GnomeFrob *frob);
    

Example 2. Sample gnome-frob-private.h

typedef struct {
        gboolean frobbed;
} GnomeFrobPrivate;
    

Example 3. Samplegnome-frob.c implementation.

void gnome_frob_frobate (GnomeFrob *frob)
{
        g_return_if_fail (frob != NULL);
  g_return_if_fail (GNOME_IS_FROB (frob));

  frob->priv->frobbed = TRUE;
}
    

Notice the use of structure prototypes: this enables the compiler to do more checking for you at compile time about the types being used.

This scheme is useful in some situations, particularly the case in which you can afford to store an extra pointer per object instance. If this is not possible the programmer might need to resort to other workarounds for this problem.

The purpose of having an extra private header file for the private structures is to allow for derived classes to use this information. Of course, good programming practice would indicate that interfaces or access methods would be provided precisely to keep the programmer from having to poke into the private structures. You should try to achieve a balance between good practice and pragmatism when creating private structures and public access methods.

Some problems might arise, for example, when you have shipped a version of your code that is being widely used and you want to extend it. There are two possible solutions for this.

The first option is to find a pointer field in the public structure that you can make private, and replace it with a pointer to the private part of the structure. This preserves the size of the structure, since for the purposes of GNOME, pointers can be assumed to be all of the same size. This field can be made to point to the private part of the structure that will be allocated by the function that originally created the public structure. It is important that this field is not called private, as this is a C++ keyword and will create problems when your header file is being used from C++ sources - call it priv instead. Of course, this type of change will only work if the old pointer field was really being used for internal purposes only; if users of the library had had to access that field for any purpose, you will have to find another field or resort to a different solution.

If your original structure was a derivative of GtkObject and there are no pointer fields that you can replace, you can use the GTK+ object data facility instead. This allows you to associate arbitrary data pointers to objects. You can use the gtk_object_set_data() function to attach values or structures to your object and then you can use the gtk_object_get_data() to retrieve back those values. This can be used to attach a private structure to a GTK+ object. This is not as efficient as the previous approach, but it might not even matter for your particular application domain. If you will be accessing this data very frequently, you can set and get it using quarks instead of strings by using the gtk_object_set_data_by_id() and gtk_object_get_data_by_id() functions, respectively.