Generating a VAPI with GObject Introspection

Vala is designed to allow access to existing C libraries, especially GObject-based libraries, without the need for runtime bindings. Each library to be used requires a Vala API file (.vapi) at compile-time, containing the class and method declarations in Vala syntax.

To generate bindings for Vala using GObject introspection the vapigen tool is used. This is usually included with valac by the distributions, but can also be compiled from source. Get the Vala source from Release.

The binding generation requires several steps:

  • getting a GObject Introspection GIR file
  • using vapigen to generate the VAPI binding from the GIR file

  • tweaking the binding generation with metadata and custom code

See also UpstreamGuide, which will guide you to support Vala bindings from upstream projects (including automake integration).

For non-GLib libraries, see LegacyBindings.

Getting the GIR File (GObject Introspection)

The introspection file defines the objects, structures, constants, enumerations and functions in an abstract markup language. Since GLib and GObject follow a well-defined naming convention for object members, functions, etc., it's possible to generate these independent definitions from the source code. A .gir file is then used to generate the .vapi file that defines the Vala bindings.

A GIR file can be obtained from:

  • a distribution as part of the development files for the library
  • the source code by using g-ir-scanner to generate the GIR

You may come across documentation about generating a GIR from a typelib file, but GIR files generated in this way will have lost relevant information and lead to an awkward binding with Vala.

A GIR file can be generated using GObjectIntrospection method.

Use GObjectIntrospection tools to generate a .gir description file.

Generating the VAPI File

To convert the .gir file into a Vala API file use:

$ vapigen --library poppler-glib poppler-glib/poppler-glib.gir

If you are updating an officially maintained vala binding in the source code tree, you can go in the vapi directory and run:

$ ../vapigen/vapigen --library clutter-gtk-1.0 --vapidir=. --metadatadir=packages/clutter-gtk-1.0/ packages/clutter-gtk-1.0/clutter-gtk-1.0.gir
or just:
$ make clutter-gtk-1.0

Do not forget to include the packages needed by the library. If the library uses GTK+ and GConf, use:

$ vapigen --pkg gtk+-2.0 --pkg gconf-2.0 --library [...]

Otherwise you'll get errors like that, or an incomplete binding:

error: The type name `GLib.tkWidget' could not be found

Fixing VAPI Generation with Metadata

Sometimes it is necessary to fix up the generated VAPI file; for instance, vapigen might not identify out or ref parameters, or identify structures that should generally be put on the stack instead of allocated, and passed by reference to methods.

Instead of updating the VAPI file, and keeping it updated with every upstream API change, vapigen output can be tweaked with a .metadata file. For instance, in poppler-glib the poppler_page_get_size function has two out parameters, width and height; in order to create a valid Vala signature in our VAPI file, we need to add these lines inside the poppler-glib.metadata file:

poppler_page_get_size.width is_out="1"
poppler_page_get_size.height is_out="1"

Which translates to: "the width parameter of poppler_page_get_size is an out parameter" and "the height parameter of poppler_page_get_size is an out parameter".

Documentation for the .metadata file format can be found on Projects/Vala/Manual/GIR metadata format.

Using Metadata Files

In a perfect world, this would be enough. Alas, the world is not perfect, and Vala and GI have different feature sets. In all likelihood, you'll need to provide some more information to help vapigen resolve some minor issues. Metadata files must have the same base name as the GIR, but instead of a "gir" extension they use "metadata". For detailed information on the features and syntax of metadata files, please see the Vala Manual section on GIR metadata format.

To get vapigen to pick up your metadata file, you must provide the name of the directory to look for it in:

vapigen \
    --library foo \
    --pkg bar-1.0 \
    --metadatadir ./metadata/ \
    Foo-1.0.gir

GObject Introspection and Vala support different things. Sometimes one not supporting something the other does is a bug, sometimes it is that one has made certain assumptions about APIs that the other does not, but whatever the reason we usually use metadata to fix it.

Duplicate Symbols

The single most common error seen the first time one tries to generate a VAPI is one about duplicate symbols. Vala has a single scope for methods, virtual methods, signals, and properties. Assuming that the signatures match, vapigen will automatically combine several of these into a single entity--the most extreme example of this is probably a virtual signal, which can combine a signal, virtual method, and method in one item. For example, GIO has the following in GLib.Application:

[HasEmitter]
public virtual signal void activate ();

That said, some conflicts cannot be resolved automatically by vapigen and will require some metadata. The most common conflict is when a method, virtual method, or signal disagrees with another method, virtual method, or signal with the same name regarding arguments or return values. For example, ClutterActor has an event signal, which takes a single argument: a ClutterEvent intance. It also has an event method which takes two arguments: a ClutterEvent instance and a boolean. In this case, we resolve the conflict by renaming the method to "emit_event":

Actor.event#method name="emit_event"

Another common problem is when a symbol of a subclass has the same name as that of a base class but the signatures do not match. Depending on the situation, you can rename or skip one of the symbols (usually in the subclass).

Nested Namespaces

GIR does not support nested namespaces (bug #660879), but Vala does. If you prefer, you can just ignore this Vala feature, but some bindings can be quite a bit cleaner if we make use of it.

A good example of nested namespaces in Vala is moving the hundreds of keysmys in Clutter into a Clutter.Key namespace, allowing us to use Clutter.Key.Right instead of Clutter.KEY_Right. This is accomplished with a single line of metadata:

KEY_* skip=false name="KEY_(.+)" parent="Clutter.Key"

We can also use the same technique to group similar functions together, like for the GContentType family in GIO:

content_type_* parent="GLib.ContentType" name="content_type_(.*)"

Nullability of Return Values

GIR assumes all pointer return values are nullable ("allow-none" in G-I terminology) and does not provide a way to override this assumption (bug #660879). Vala, on the other hand, assumes return values are not nullable unless otherwise otherwise specified, and comparing a non-nullable value to null (e.g., to check for validity) will cause a warning. Luckily, making a value nullable is easy to do from a metadata file, as you can see from this example (for clutter_actor_get_parent):

Actor.get_parent nullable

Variadic Functions

GObject introspection does not currently support variadic methods. It actually generates all the information Vala needs to do so, but it will mark the function as introspectable="0", which is the same that happens when you add a "skip" annotation to the method. Therefore, in order to expose these functions in Vala, we need a simple annotation to un-skip the symbol. For example, this is how clutter_actor_animate is exposed from metadata:

Actor.animate skip=false

Ownership of Struct Fields

GObject introspection does not currently offer a way to specify whether or not fields contain an owned reference. It is therefore impossible for Vala to know whether or not it should ref or copy a value being assigned to this field. Again, this is easy to fix with metadata... using GDBusAnnotationInfo as an example:

DBusAnnotationInfo.*#field unowned=false

Virtual Methods Without Invokers

Some libraries contain virtual methods without emitters, which GObject introspection does not currently offer a way to annotate (bug #730480). Fixing these basically means adding any information that would normally go in annotations to the metadata.

Abstract/Virtual Distinction

Vala distinguishes between abstract and virtual methods (virtual methods do not need to be implemented by an class which implements the interface whereas abstract methods do require an implementation) while GIR does not. In order to mark a method as virtual instead of abstract, you could do something like this (from gtk_source_completion_proposal_equal):

CompletionProposal.equal#virtual_method virtual

Generic Types

GObject Introspection only supports a few different generic types, and that support is hard-coded and cannot currently be extended to other types which should be generic (bug #639908). For example, GDataList is a generic in Vala but is not supported as such by GObject Introspection, so the the following is necessary for soup_form_encode_datalist:

form_encode_datalist.form_data_set type_arguments="string"

GClosure Types

GIR does not provide a way to annotate the type of a callback (bug #636812) contained in a GClosure. Although this is not an error which will cause bindings to not be generated, the result is an API that is extremely difficult to use correctly. For example, you can provide the delegate type of clutter_binding_pool_install_closure from the metadata:

BindingPool.install_closure.closure type="owned BindingActionFunc"

C Headers

GObject Introspection currently generates a single list of header files for each library. Usually, this isn't a problem since libraries tend to install one header file which will include the others. However, some libraries don't install this header file, meaning you end up with a list of many different headers to include. The problem for Vala comes when a header file is added to that list--any code generated using that library will #include the new header, but if the user has an older version of the library installed on their system that will result in an error from the C compiler.

Fixing this requires you to set the C header for each type manually. For example:

PnpIds cheader_filename="libgnome-desktop/gnome-pnp-ids.h"

Inheritance

GObject Introspection currently only handles inheritance for GObject-derived types (bug #560692). To get around it in metadata you can use "base_type":

Buffer base_type="Gst.MiniObject"

Asynchronous Finish Functions

GObject Introspection does not currently offer a way to annotate the relationship between an async function and its corresponding finish function (bug #623635). By default Vala will look for function with the same base name, but a "_finish" suffix, but you can point it to other functions in metadata using "finish_name":

Service.lookupv finish_name="secret_service_lookup_finish"

Macros

Since GObject Introspection is focused primarily on runtime bindings for languages such as Python and JavaScript, it ignores preprocessor macros. Although this decision makes sense for them (you can't dlsym(3) a macro), Vala is capable of utilizing macros. However, since no information on macros is included in the GIR the only way to expose macros is by adding them to a *-custom.vala file.

Fixing VAPI Generation with Custom Vala Code

Remember that thing about the world not being perfect? Well, a metadata file isn't always enough either. Sometimes you'll need the ability to inject custom Vala code into your VAPI. Technically, this file can have any name and there can be more than one per package, but the convention is to use the same file name and directory as the GIR followed by "-custom.vala". For instance, our Foo-1.0.gir might have a corresponding metadata file named Foo-1.0-custom.vala. Once you have your custom Vala file, simply include it in the argument list you pass to vapigen:

vapigen \
    --library foo-1.0 \
    --pkg bar-1.0 \
    --metadatadir ./metadata/ \
    Foo-1.0.gir \
    Foo-1.0-custom.vala

No Generic Methods

Vala supports generic methods, such as g_object_get, while GObject Introspection does not. Unfortunately, metadata alone cannot currently resolve this issue--you will need to skip the method in metadata and recreate it in custom.vala.

A Note on `vala-gen-introspect` and GI files

The traditional approach was to use vala-gen-introspect to generate .gi files. Although vapigen will generate a VAPI from .gi files, as well as .gir files, bindings are moving away from that method.

Some header files may be useless for gobject-introspection and can even trigger errors. In this case, add them to poppler-glib/poppler-glib.excludes and vala-gen-introspect will ignore them.

Projects/Vala/Bindings (last edited 2016-04-13 22:57:54 by AlThomas)