In previous post I’ve described what ABI is. Now the time for potential problems which you can run into with Vala – unfortunately the list might not complete so please threat it as a guideline rather than definite description.
For the purpose of this post I’ll assume that breaking API is necessarily breaking the ABI. There are exceptions I’ll describe in part III.
Interfaces and classes
One of the easiest way of breaking compatibility is by changing the classes and interfaces. There are several pitfalls one need to avoid and it is good to familiarize with a basics of GObject layout and how Vala generates the code. For example Vala do order the fields more or less in order they appear in source code (there is more to it but I’ll describe it later) which means that changing the order of fields or virtual methods break the ABI (even when API remains the same).
In GObject layout the parent class layout (‘parent object’) is a prefix of a child layout – the first element of a child struct is a member ‘parent’. The practical consequence of it for the ABI is that changing the size of parent object breaks the ABI for the client inheriting the object.
In other words one can break an ABI by adding or removing one of following:
- Public, protected or internal instance fields change the size of an object struct
- Public, protected or internal class fields change the size of a class struct.
- Public, protected or internal virtual methods change the size of class struct.
- Public, protected or internal virtual properties change the size of class struct.
- Public, protected or internal default signal handlers change the size of class struct.
Usually it impose a restriction on what can be done with classes. As a rule of thumb you should not have a public, protected or internal fields and rely on private ones (the further problem with internal fields is that Vala does not handle them very well at this moment). If you need to have access to them wrap them in internal methods and access it through it (with LTO and support for ‘true’ internal methods it should be as fast as direct access inside the binary).
The bigger problem are virtual methods and default signal handlers which cannot be removed by the above trick. However there is a workaround – all pointers now you do care about are function pointers (it is important as technically in C
sizeof(void *) != sizeof(void (*)()) might be true). Therefore to future-proof the ABI you can pad the structure by reserved, internal virtuals (in gee we used creative names like “
reserved0” or “
When you use the reserved virtual you need to delete as many of them as you are going to use – you need a one reserved virtual per one added virtual, one per each virtual setter and one per each virtual getter (so in some cases the virtual property might add more than 1 pointer).
The final issue is that the virtual methods are grouped before the signals which are before properties (by default). So if you cannot add a virtual method you would break the ABI… unless you instruct Vala about the exact layout. The
CCode attribute property
ordering allow you to fine-tune the layout of class so that position of virtuals do not change from version to version.
The interfaces are easier to handle than objects. The client access them only by pointers so they can be extended without worrying about breaking ABI. However the ordering is still an issue as the methods, signals and properties are sorted as with classes.
While in most cases the methods are easier to handle (as long as they are not changed into virtuals – its covered by previous section) there are still a few things to remember.
The first problem is the ownership – when you change an argument or return value from owned value to unowned one (or other way round) the caller needs to add or remove the references as needed. It also adds or remove C parameters in case of the delegate parameters so you might end up with wrong data in wrong places. While Vala usually has sane defaults (owned returned values and unowned arguments) you might want to ensure that they are what you want them to be – or not optimize it at all.
Second, possibly more obvious, problem is a default value for parameters. They work in Vala by supplying them into the code by compiler when the client code is compiled. That means that adding parameter with default value do change the ABI – the exact behaviour will depend on the calling convention but most likely you will get garbage as input. Changing the parameter do not, however, change ABI as long as the code still handles an old value.
In the next episode
In following post I will try to show a few potentially useful workarounds of how to change the API if you really need to while preserving backward compatibility.