Writing a VAPI Manually

This document intends to be a tutorial and reference on how to write a Vala binding to an existing C library. If the library uses GLib, do not follow this document. Instead read Generating a VAPI with GObject Introspection. A library may not follow the GLib coding practices precisely, but it is better to fix the library to work with GObject Introspection than to write a manual binding.

C programmers are a rather liberal bunch; certain procedures are done in a multitude of ways depending on the mood of the programmer, whereas Vala is much more restricted. This guide cannot possibly cover all possible cases of different APIs written by C programmers. It is your job to understand the C API and present it with Vala-friendly semantics.

There is a lot of material in this document and that can make it hard to take in at first. A practical approach to working through the tutorial would be to:

  1. Bind an enum first because enums are easy to test.

    Once your test gives the expected result you know that the build process works. This means working through the "Getting Started" section and the "Enums and Flags" sub-section. Binding an enum also introduces the idea that there isn't a straight mapping from C to Vala

  2. Bind the creation and destruction of a compact class next.

    This means working through the "Using Vala's Automatic Memory Management" section and starting to understand that a struct in C can be bound as either a simple type, a struct or a compact class in Vala. The binding can be tested by looking at the C code produced from a single line in Vala like new MyBoundCompactClass ();

  3. Bind methods of the compact class.

    This is when your binding starts to become useful and it will also give an overview of this document. Once you have an overview the document becomes more of a reference for solving tricky function bindings

The above assumes that the library is written in an object oriented style of C. A C binding, however, is only made up of structs and functions so understanding that in enough detail is the purpose of the approach.

Prerequisites

To write the binding collect the following:

  • a functional copy of the library with headers
  • the documentation for the library, if such a thing exists
  • the source, if possible
  • examples or tutorials that you can use as tests for your binding

If the library is written in C++, you cannot bind it to Vala unless there is a separate C binding of the C++ library (e.g., LLVM).

If you are using vim, you may wish to add the following to your .vimrc:

:noremap <F8> "gyiwO[CCode (cname = "<ESC>"gpa")]<ESC>

which allows you to insert an attribute to make it easier to rename a function by pressing F8 while your cursor is on the symbol.

Getting Started

The VAPI File

Check if the library's development package has installed a pkg-config file ( .pc file extension ). If so, give your VAPI file the same name. For example libfoo.pc should have the VAPI called libfoo.vapi. This allows the details of the library files to be automatically picked up and passed through to the C compiler and linker.

When developing a VAPI a typical command to build the tests against the binding would be:

valac --vapidir . --pkg libfoo program_using_libfoo.vala

The dot after --vapidir tells valac to include the current directory when looking for VAPI files. The --pkg libfoo switch tells valac to look for a VAPI called libfoo.vapi. Note the the .vapi suffix is dropped. If the VAPI also has the same name as a .pc file then valac will find and use the .pc file to extract the relevant library details to pass to the C compiler and linker.

Example VAPI files can be found in the vapi directory of the Vala git repository. Files stating they have been generated by vapigen have been generated through GObject Introspection and are not examples of manually written bindings.

Once you have a working VAPI file, even if it is only a subset of the library's functionality, please consider sharing the file. See Vala Extra VAPIs and Why Distribute Bindings Upstream.

Attribution and License

To distribute the VAPI through one of the main repositories a copyright notice will be required. It may be easier to deal with this formality at the start of writing the binding.

The copyright notice should include an attribution and a copy of the license. The attribution is your name along with your email address. This identifies you as the author of the VAPI and a point of contact in the very unlikely event a third party is identified as using the binding in breach of the license. Free software and open source licenses allow the VAPI file to be copied as long as the terms of the license are met. The license should be the same as the library's license. This ensures compatibility between the binding and the library.

The copyright notice should be between multi-line comments, not documentation comments:

/*
 * Copyright (c) 2016 My Name <my_email@my_address.com>
 * 
 * This library is free software...[or whichever license is used by the library]
 *
 */

Documentation comments have an additional asterisk at the beginning, /**, and would be picked up by valadoc. The use of valadoc is covered later.

The CCode Attribute

Vala generates C code in a certain style, examples are Vala following its own naming conventions and the ordering of automatically generated parameters. The CCode attribute provides fine control of the C code produced by Vala and will be used extensively when binding a C library that uses its own conventions.

The CCode attribute will be used for:

  • including a C header file
  • converting from Vala naming conventions to a library's naming conventions
  • binding a library to Vala's assisted memory management
  • controlling the position of function call arguments, especially Vala generated arguments
  • overcoming various edge cases

These are introduced at the relevant points throughout the tutorial. For a single reference see the Vala Manual Attributes Section.

Create a Root Namespace

Normally all the bindings for a library are placed into a single root namespace. For example libfoo or foolib, would best be placed in a namespace called Foo. This follows the naming convention above. For example an initial VAPI would be:

namespace Foo {
    // bindings
}

The binding can then either be used in a Vala program by prefixing the namespace, e.g.:

void main () {
    Foo.library_function();
}

or bring the VAPI namespace into the scope of the file:

using Foo;

void main () {
    library_function ();
}

Namespaces also provide a convenient way to group functions. Typically, for GLib-based libraries, the x_y_foo patterns can be translated directly into a namespace as x.y.foo. Since most C libraries do not follow these conventions, things are slightly murkier. As general rules of thumb, try the following:

  • Move global variables, functions, constants, enums, flags, and delegate definitions into the class and struct definitions if they are clearly related only to that type. That is, it might make sense to move the enum FooOptions into class Foo as simply Options. Note that structs cannot contain enum, flag, or delegate definitions; only constants and static methods.

  • Use header files and directories as a guide. If the headers are stored as foo-2.0/db/{handle,transaction,row}.h or foo-2.0/db_{handle,transaction,row}.h or if foo-2.0/db.h contains definitions for foo_handle, foo_tx, and foo_row, there's a good chance that creating a namespace Db is a logical grouping.

  • Create namespaces for large groups of related constants. Sometimes, constant collections cannot be converted to enums, in which case, grouping them into a namespace is much easier to manage.

Include the C Header Files

The CCode attribute cheader_filename defines the comma separated list of headers to include in the generated C. For example,

[CCode (cheader_filename = "libfoo/foo.h")]
namespace Foo {
    // bindings
}

Try to apply headers to namespaces or containing types. Applying it to an outer context prevents having to repeat it in the inner context.

A library will often have a single header that includes a number of sub-headers. For an example see the glib/glib.h header. In these cases only the main header file needs to be included.

Symbol Name Translations

Vala has symbol name translation rules from Vala to C. The default rules follow the GLib naming conventions, but for a binding the name translations can be customised with the lower_case_cprefix, cprefix and cname CCode details.

The following example illustrates the default symbol name translation rules. Vala's name translation rules apply to both Vala programs and bindings. Compile the following example program with valac --ccode name_conversion_example.vala then examine how the Vala symbol names have been translated:

void main () {
    Foo.Bar a = new Foo.Bar ();
    a.test ();
    var b = Foo.Bar.UNCHANGING;
}

namespace Foo {
    [Compact]
    class Bar {
        public const int UNCHANGING = 42;
        public void test () {
        }
    }
}

The use of the [Compact] attribute makes the C code simpler and so easier to read, but the name translation rules apply to full Vala classes as well. Here is a table that summarizes the example's translations:

Vala Identifier

C Identifier

Notes

Foo.Bar

FooBar

This is the data type

new Foo.Bar ()

foo_bar_new ()

This is the constructor function

a.test ()

foo_bar_test (a)

This is a function acting on an instance

Foo.Bar.UNCHANGING

FOO_BAR_UNCHANGING

A constant defined with the type

When binding the library the Vala symbol names should follow the following conventions and then lower_case_cprefix, cprefix and cname can be used to ensure the C symbol name matches the library:

Vala Semantics

Vala Convention

Default Translation to C

Modify with CCode Detail

Classes

TitleCase

Constants

UPPER_SNAKE_CASE

Delegates

TitleCase

Enums and Flags

TitleCase

Fields

lower_snake_case

Methods

lower_snake_case

Namespaces

TitleCase

title_case_
TITLE_CASE_
TitleCase

lower_case_cprefix
lower_case_cprefix
cprefix

Properties

lower_snake_case

Structs

TitleCase

Type Variables (Generics)

T (A single uppercase letter). For maps, K, V are preferred for keys and values.

Where appropriate, expand cryptic C names into more understandable Vala ones (e.g., Tx into Transaction). Vala is usually much more compact than C, so we are willing to make different trade-offs and favor readability over being concise a bit more than C programmers generally do. In particular, var saves a lot of writing long type names and import helps make better use of prefixes.

Note the following:

  • the use of cprefix and lower_case_cprefix with a namespace

  • the priority of a class over a namespace when using cprefix and lower_case_cprefix

  • the use of cname with a function and constant

Code Formatting Conventions

  • Tabs for indentation
  • A space before an opening parenthesis and no space afterwards
  • A space either side of equals
  • No space before a comma, but a space after

Documentation and Valadoc.org

Valadoc.org is often the first website a Vala developer visits when seeking how to use a binding. A new VAPI commited to Vala Extra VAPIs can be added to Valadoc.org by adding the VAPI to the list of downloaded packages at Valadoc.org and submitting a pull request to the Valadoc.org repository. See the libcolumbus pull request as an example.

Valadoc.org is frequently re-generated. When Valadoc.org is re-generated VAPIs are pulled in from vala-extra-vapis and documentation generated from them. If no documentation comments are associated with a VAPI then Valadoc.org will only show the symbols in the VAPI.

Add a documentation comment before a symbol in the VAPI. A documentation comment is a C multiline comment with an additional asterisk:

/**
 * Brief description of class Foo
 *
 * Long description of class Foo, which can include an example
 */
[CCode (cname = "foo", ref_function = "foo_retain", unref_function = "foo_release")]
[Compact]
public class Foo {
    // Details of binding
}

The comments can include additional markup. Details are at Valadoc Comment Markup.

The documentation can be generated locally to test how it will appear. First, download and build valadoc:

git clone git://git.gnome.org/valadoc
cd valadoc
./autogen.sh
make
make install

Second, generate the documentation:

cd my_binding_directory
valadoc --directory docs --force --package-name mybinding mybinding.vapi

This will generate the HTML documentation in the docs directory. valadoc expects the docs directory to not exist, but --force overrides this. --package-name mybinding will create a sub-directory called mybinding in docs that contains the generated documentation for mybinding.vapi.

The locally generated documentation will have the same structure as valadoc.org, although the visual styling may differ.

The Version Attribute

Vala symbol's can be annotated with the [Version] attribute. This allows a symbol to be marked as experimental, deprecated and to indicate version information. For example:

namespace Test {
        [Version (experimental = true)]
        public void test_function_1 ();

        [Version (deprecated = true)]
        public void test_function_2 ();

        [Version (deprecated_since = "2.0")]
        public void test_function_3 ();

        [Version (deprecated = true, deprecated_since = "2.0", replacement = "test_function_5", since = "1.0")]
        public void test_function_4 ();

        [Version (since = "1.0")]
        public void test_function_5 ();
}

Using Vala's Automatic Memory Management

When writing Vala code, including code using a C library, memory management is handled by the Vala compiler. There is usually no need to manually claim and free memory. When writing a binding, however, it is an important part of the process to accurately instruct the Vala compiler how to use the C library's memory management calls. This is a one time job and means anyone then using the binding can take advantage of a binding that is much easier to write code for.

Vala's memory allocation and types are a bit more involved than most languages. In Python, everything is a dynamically-typed object and it is allocated out of the ether then gets garbage collected. In C, memory allocation is largely handled by the user and types are simply descriptions of memory considered at compile time. Vala lives somewhere trying to cover all the bases at once. Importantly, the types in Vala imply something about the memory management.

There are 4 memory management schemes in Vala:

Scheme

Memory Manager

Helpers?

Copy Cost

Values

C compiler

No

Cheap

Parented

C compiler

Yes

Expensive

Singly-Owned

Heap allocator

Yes

Expensive

Reference-Counted

Heap allocator

Yes

Cheap

Pointers in C (or what all these *'s mean)

The asterisk, *, is the indirection operator in C. Although, be aware it is also the multiplication operator. The indirection operator means an identifier contains a pointer to a memory location. Usually the data type held in the memory location is also indicated. For example int *identifier means an int is held at the memory location pointed to by identifier. The data type, however, does not have to be specified and instead the "generic" type can be used: void *identifier.

There can be multiple levels of indirection, e.g. char **identifier.

The 'address of' operator is ampersand, &.

The use of the indirection operator and the address of operator is relevant to binding function signatures, which is covered in a later section. For a comprehensive explanation of pointers in C see Everything you need to know about pointers in C.

For now it is enough to understand that the pointer gives no indication of how the memory pointed to is managed. It is not known from seeing a pointer in the C code whether the memory is constant, stack allocated or heap allocated.

Constants, the Stack and the Heap in C

Data in C can be allocated using a mechanism that stops it from being changed during the running of the program. These are known as constants. Data can also be allocated using two other schemes: the stack and the heap. These three schemes need to be understood when writing a binding. The main reason is so heap memory is allocated and deallocated properly by Vala code when using the binding, but also to make sure the binding doesn't apply heap rules to the other two schemes.

To better understand these three schemes it is helpful to analyse how they handle memory in four stages:

  1. Declaration
  2. Allocation
  3. Initialization
  4. Deallocation

Declaration informs the compiler of how much memory will be needed. For example uint8 lets the compiler know at least 8 bits (a byte) is needed, or double will likely require more memory than float. The exact size of each type is platform dependent and will be resolved by the compiler.

Allocation is the process of exclusively reserving an area of memory. Where the memory is allocated from is based on the memory scheme.

Once the memory has been allocated the memory needs to be initialized to the required value. For example int a = 128; will set the memory reserved for an int to the value of 128.

Deallocating the memory means it can be used again by other parts of the program.

Declaration

Allocation

Initialization

Deallocation

Constant

Compile time

Compiler

Compile time

Program exit

Stack

Compile time

Compiler

Run time

Compiler

Heap

Compile time

Coder

Run time

Coder

The C compiler does some memory management. This happens when items are placed on the stack or inside another structure: the compiler creates the space needed to hold these objects. Otherwise, malloc and free are used to allocate space from the heap. If any instance contains references to other instances, helper functions are needed to do the allocate and deallocate those references.

The Concept of "Ownership" in Vala

Binding to C Heap Handlers

One of the unique features of Vala is to have both singly-owned instances and reference-counted instances. Reference-counted instances can be stored in new locations and memory management done by counting the number of references; destruction of the instance is done when there are no more references to that instance. Singly-owned instances have a single authoritative reference and, when that reference is destroyed, the instance is destroyed. Reference-counted objects can thus be “copied” by increasing the reference count while singly-owned instances cannot be copied without duplicating the actual data in them, if that is even possible.

While this is primarily a concern for objects, all instances in Vala must subscribe to one of these memory management schemes. Different types of objects can follow different schemes and some types can subscribe to different schemes depending on subtle differences in declaration.

Vala Type

Scheme

C Type

Memory Management Binding Needed?

Enum and Flag

Value

int

No

Delegate (has_target = false)

Value

Function Pointer

No

Delegate (has_target = true)

Value

Function Pointer and Void Pointer

No

Delegate (has_target = true)

Singly-Owned

Function Pointer and Void Pointer

Yes, use free_function

Simple-Type Struct

Value

Various Basic Types or a Struct

No

Struct

Value

Struct, but passed as a Pointer to a Struct

No

Struct

Parented

Struct, but passed as a Pointer to a Struct

Yes, use destroy_function

Compact Class

Singly-Owned

Pointer to a Struct

Yes, use free_function

Compact Class

Reference-Counted

Pointer to a Struct

Yes, use ref_function and unref_function

Pointer

Value

Pointer to Contents

No

Array

Singly-Owned

Pointer to Element Type (and Integer Length)

Yes, use free_function

Recognizing Vala Semantics in C Code

An important difference between C and Vala is that Vala is more semantically expressive. For instance, in C char* can mean several things. It could be a string, an array, a pointer to a single character, an out parameter returning a character, a pointer to a character that will be modified by the routine. It is also completely unclear if this pointer can be null. Vala expresses these differences syntactically, so writing the binding requires understanding the intent of the original code.

It is easiest to start by looking through the header files and determining all the important types to be bound. For each one, find any allocation functions, copy functions and cleanup functions. From these, the right binding strategy can be inferred.

Constants

This sub-section introduces:

  • the #define pre-processor directive in C

  • the stages the Vala compiler follows

A constant does not vary during the running of a program and must be a simple type or string. As an example, if the C library defines a constant through a #define statement:

#define CUSTOM_PI 3.14159265358979323846

#define is simple text substitution by the pre-processor. So relevant occurrences of CUSTOM_PI are replaced with 3.14159265358979323846 by the C pre-processor before the C code is then compiled. This is why no type information is given. Also, because this is done before compilation it is implicit that the value is constant.

When binding this to Vala the type information and that it is constant are made explicit:

public const double CUSTOM_PI;

An important point to note is the value is not bound, only the identifier. Vala will use the identifier in the generated C code and then the C pre-processor will replace it with the value before compilation.

Enums and Flags

While C has support for enums, C programmers often do not use enums, opting instead for #defines. Both of these structures can be bound to Vala enums.

This first example is a straight mapping between a C enum and a Vala enum. The C:

typedef enum {
    FOO_A,
    FOO_B,
    FOO_C,
} foo_e;

and the Vala binding:

[CCode (cname = "foo_e", cprefix = "FOO_", has_type_id = false)]
public enum Foo {
    A,
    B,
    C
}

Note how cprefix is used in the above example to prepend FOO_ to all the Vala values when the C is generated.

The second example shows how a series of definitions of constants in C can be mapped to a Vala enum:

#define BAR_X 1
#define BAR_Y 2
#define BAR_Z 3

[CCode (cname = "int", cprefix = "BAR_", has_type_id = false)]
public enum Bar {
    X,
    Y,
    Z
}

Check where the enum is used to determine the correct type, though int and unsigned int are the most common.

There is also a common tendency to use combinable bit patterns. These are convertible to Vala flags enums.

#define FOO_READ (1<<0)
#define FOO_WRITE (1<<1)
#define FOO_CREATE (1<<2)

[CCode (cname = "int", cprefix = "FOO_", has_type_id = false)]
[Flags]
public enum Foo {
    READ,
    WRITE,
    CREATE
}

In Vala, enums and flags may have member functions. In particular, strerr-like functions are best converted to member functions.

Enums may also inherit, so if one set of flags is a superset of another, but they are logically separate, this can be done using inheritance.

#define FOO_A 1
#define FOO_B 2
#define FOO_C 3
#define FOO_D 4
/* takes FOO_A or B only */
void do_something(int);
/* takes any FOO_ value */
void do_something_else(int);

[CCode (cname = "int", cprefix = "FOO_", has_type_id = false)]
public enum Foo { A, B }
[CCode (cname = "int", cprefix = "FOO_", has_type_id = false)]
public enum FooExtended : Foo { C, D }

Simple Type Structs

C libraries often define new types for numeric handles, sizes and offsets. To translate these to a VAPI file, just use the SimpleType attribute with a struct and inherit from the same simple type in the C header.

An example:

    typedef uint32_t people_inside;

would be defined in the VAPI file as:

    [SimpleType]
    [CCode (cname = "people_inside", has_type_id = false)]
    public struct PeopleInside : uint32 {
    }

When inheriting from an existing type, all the methods will be carried forward. For sizes and offsets, this is probably desirable; for handles, it is probably not. For example, a UNIX file descriptor is stored in an integer, but adding or multiplying two file handles has no sense. In this case, it is preferable not to inherit from a numeric type and add the attribute IntegerType (rank=X) so the Vala compiler can automatically cast a type into an integer of an appropriate size when needed (e.g., initialising from an integral constant).

An example from XCB:

    typedef uint32_t xcb_atom_t;

would be defined in the VAPI file as:

    [SimpleType]
    [IntegerType (rank = 9)]
    [CCode (cname = "xcb_atom_t", has_type_id = false)]
    public struct AtomT {
    }

The ranks for the common types, as defined in the glib-2.0.vapi and posix.vapi files, are:

Rank

Types in glib-2.0

Other Use

1

gint8
gfloat

2

gchar
gdouble

3

guchar
guint8

Posix.cc_t

4

gshort
gint16

5

gushort
guint16

6

gint
gint32

Posixpid_t

7

guint
guint32
gunichar

Posix.speed_t
Posix.tcflag_t

8

glong
gssize
time_t

Posix.clock_t

9

gulong
gsize

Posix.nfds_t
Posix.key_t
Posix.fsblkcnt_t
Posix.fsfilcnt_t
Posix.off_t
Posix.uid_t
Posix.gid_t
Posix.mode_t
Posix.dev_t
Posix.ino_t
Posix.nlink_t
Posix.blksize_t
Posix.blkcnt_t

10

gint64

11

guint64

Structs

Note that a struct on the C side can contain the Vala equivalent of instance data of an object and so be bound to a compact class in Vala. This is covered in a later section. This section covers binding C structs and C primitives to Vala structs.

A common pattern in C is a parented structure, like this:

typedef struct {
    int a;
    int *b;
} foo_t;
void foo_init(foo_t*);
void foo_free(foo_t*);

The correct binding is:

[CCode (cname = "foo_t", destroy_function = "foo_free", has_type_id = false)]
public struct Foo {
    int a;
    int *b; // We can do better later
    [CCode (cname = "foo_init")]
    public Foo ();
}

The great trap with this is the naming: foo_free does not free the pointer it is passed. There may be no way of knowing for sure other than reading the implementation of foo_free. The full structure for Foo must be given for a struct. For compact classes it may be opaque (i.e., the contents of the struct are not provided), though not necessarily.

The next example illustrates the use of an empty destroy_function and has a default value for the struct:

typedef struct {
    int x;
    int y;
} bar_t;
#define BAR_INITIALIZER {0, 1}

[CCode (cname = "bar_t", destroy_function = "", default_value = "BAR_INITIALIZER", has_type_id = false)]
public struct Bar {
    int x;
    int y;
}

It's important to note that if a struct doesn't have a destroy function specified, Vala will generate one given there are any fields in the struct which look like they need to be deallocated, which may or may not behave correctly depending on the context. An empty destroy_function will keep the generated code correct and prevent Vala from generating a destructor.

Compact Classes

Vala has three types of class: GObject subclasses, GType classes and compact classes. The relevant parts of a non-GLib based C library can be bound to a compact class in Vala.

Singly-Owned Classes

The most common case is the singly-owned compact class, which follows one of these patterns:

typedef struct foo Foo;
/* Create a new Foo handle. */
Foo *foo_make(void);
/* Make a copy of a Foo. */
Foo *foo_dup(Foo*);
/* Free a Foo handle. */
void foo_free(Foo*);

typedef struct bar *Bar;
/* Open a new Bar from a file, NULL if an error occurs. */
Bar bar_open(const char *filename);
/* Dispose of a Bar when finished. */
void bar_close(Bar);

These should both be bound as compact classes. The foo_make and bar_open functions will allocate memory and create a new instance of the type (this is where documentation is helpful). There's an important subtle difference between these two: where the pointer is mentioned. In the case of Foo, the pointer is mentioned in every function, while Bar has it baked into the typedef. Vala will always add a star, so Bar will be actually be bound using struct bar.

The second difference is the constructor: Foo's constructor will not fail, but Bar's might fail. Vala constructors are not permitted to return null. Bar's constructor is best bound as a static method, as these can return null.

[CCode (cname = "Foo", free_function = "foo_free")]
[Compact]
public class Foo {
    [CCode (cname = "foo_make")]
    public Foo ();

    [CCode (cname = "foo_dup")]
    public Foo dup ();
}

[CCode (cname = "struct bar", free_function = "bar_close", has_type_id = false)]
[Compact]
public class Bar {
    [CCode (cname = "bar_open")]
    public static Bar? open (string filename);
}

In case explicit duplication is needed, include a member function of the copy function called dup(), if available.

Reference-Counted Classes

Reference-counted classes usually have a pattern as follows:

typedef struct foo Foo;
Foo *foo_new();
Foo *foo_retain(Foo*);
void foo_release(Foo*);

and should be bound as:

[CCode (cname = "foo", ref_function = "foo_retain", unref_function = "foo_release")]
[Compact]
public class Foo {
    [CCode (cname = "foo_new")]
    public Foo ();
    [CCode (cname = "foo_retain")]
    public void @ref ();
    [CCode (cname = "foo_release")]
    public void unref ();
}

The ref and unref function are provided as a courtesy to the user such that they might manually change the reference counts in difficult situations.

Functions

These are functions that work alone and can be used without needing previous calls to other functions. This is a simple example of the Posix VAPI file for the sync system call:

[CCode (cname = "sync")]
void sync();

The ccode attribute, cname, specifies the C name to use. This avoids valac appending the current namespace to the function name, ensuring that a call to Posix.sync() in vala will map to a call to sync() in C, and not to posix_sync().

Delegates

C permits the definition of function pointers, which are pointers to code matching a certain signature that may be executed. The major problem with this is that it does not pass information from the caller, through the library, to the callback. In other languages, a closure is an encapsulation of code and state. C programmers sometimes emulate this behaviour by passing a void pointer of “user data” or “context” that acts as the state portion of the closure.

Vala supports both of these modes: a delegate may be targeted (i.e., a closure) or targetless (i.e., a function pointer). This is controlled by the has_target value, which defaults to true. The position of the target is assumed to be the last value in the argument list, which is typically where most C programs put it, though they occasionally place it first.

typedef int(*compute_func)(int a, int b);
typedef double(*analyze_func)(int a, int b, void *userdata);

[CCode (cname = "compute_func", has_target = false)]
public delegate int ComputeFunc (int a, int b);
[CCode (cname = "analyze_func")]
public delegate double AnalyzeFunc (int a, int b);

If the position of the context is not the last parameter, set the CCode attribute, delegate_target_pos, as per Changing the Position of Parameters.

It is common for C programmers not to create a typedef for a function pointer, instead opting to include it directly. Create a delegate and do not set the cname. If possible, contribute a patch to the library to create a typedef.

Fundamentals of Binding a C Function

A function signature comprises the parameters of the function and any return value.

Out and Reference Parameters and Return Values

C makes rather heavy use of out parameters as an alternate method to returning. Unfortunately, because of the non-uniform nature of this return system, it is rather confusing.

For all types except structs, when returned, the instance is returned, per usual. Any supplementary information (delegate targets, array lengths) are quietly appended as out parameters. To return another value, a parameter may be declared as out. Vala will assume the function will accept a pointer to this value, which it will populate upon return. A ref parameter is similar, but the parameter must be initialised before the function is called and the function may manipulate its value.

Consider the following:

int div_and_mod(int a, int b, int *mod) {
    *mod = a % b;
    return a / b;
}

public int div_and_mod (int a, int b, out int mod);

This works just as easily for class type parameters:

int open_file_and_fd(const char *filename, FILE **file) {
    FILE *f = fopen(filename, "r");
    if (file)
    *file = f;
    return (f == NULL) ? -1 : fileno(f);
}

public int open_file_and_fd (string filename, out FileStream file);

For arrays and delegates, this means returning both the parameter and its associated parameters:

void do_approximation(int *input_array, int input_length, int **output_array, int *output_length);

public void do_approximation (int[] input, out int[] output);

Note that when you think of an “out array” what you may actually want is just a buffer. If the caller allocates the memory, you want a buffer, not an out array.

Returning structs is rather different. Because struct's memory is allocated by the caller, an out parameter for a struct is indistinguishable from a regular pointer. Moreover, returning a struct actually means including a hidden out parameter.

public struct Foo { … }
public Foo get_foo (int x);
public void get_foo2 (int x, out Foo f);

void get_foo(int x, foo *ret);
void get_foo2(int x, foo *ret);

To return a struct directly, the question mark operator will box it, and make it look heap allocated:

public Foo? get_foo (int x);
public int make_foo (int y, out Foo? f);

foo *get_foo(int x);
int make_foo(int y, foo **f);

The ownership rules below apply.

Ownership

All parameters are, by default, unowned, unless marked with the owned keyword. All return values and ref and out parameters are, by default, owned, unless marked with the unowned keyword. The basic types mentioned above have no ownership since they may be copied at will.

It is often the case that a function will return one of its input values, particularly when filling a buffer. It is crucial that the ownership is correct. If not done correctly, Vala will acquire a second copy of the pointer that it thinks it has to free, and free the same chunk of memory twice, leaking to a bad time spent in Valgrind.

If ownership semantics are not correct, either a memory leak has been written or a double-free has been written. It is frequently the case that one needs to read the source to be absolutely sure that ownership semantics are correct.

Often C programmers will mark return values as const when they are unowned.

See also: Dependently Typed Ownership

Nullability

For most types, appending a question mark allows the type to be null. Generally, C programmers do a lousy job of conveying whether a particular parameter may be null. For any type which is, underneath, a pointer (arrays, compact classes, arrays, and delegates) nullability does not change the C type. That is, if Foo is a class, then Foo foo and Foo? foo have the same C signature. For simple types, enums, and flags, adding nullability lifts the type to a pointer. That is bool b has the C type gboolean b and bool? b has the C type gboolean *b. Parented structs are a special case. When passed as parameters, they are always passed as a pointer, so nullability makes only a semantic difference; when return values, nullability changes the behaviour, as discussed below.

Vala always assume an out parameter can be null. For example:

public delegate void ComputeFunc (int x);
public void get_compute_func (double epsilon, out ComputeFunc func);

ComputeFunc f;
get_compute_func (3.14158, out f);
f (3); // f should never be a null pointer.
get_compute_func (2.72, null); // This is perfectly okay according to Vala.

It's important to note that nullability refers to the type of the parameter, not the parameter handling. Many C libraries do not check that an out parameter is not null before accessing it, resulting in a segmentation fault. There is no syntax in Vala to prevent this.

Static Methods

Enums, flags, simple types, structs and classes can contain functions. When the Vala compiler generates the C function call the data structure will be included as the first argument. To prevent the automatic generation of the argument use the static keyword with the function definition in the VAPI.

Binding static methods is, in fact, simpler than binding member methods, as there is no instance. Care should be taken to organise static methods into logical places: some should be in the containing namespace and some should be in the type definition. In general, methods that produce instances of the type (i.e., things that act like constructors that might fail) belong in the type definition.

Changing the Position of Generated Arguments

The default behaviour of Vala is to keep the position of arguments in a Vala caller the same as the position of parameters in the C function callee. Where an argument is not explicit on the Vala side, for example instance data, Vala assumes it will be in a certain position. Instance data is assumed to be the first parameter of the C function, but this can be changed via the instance_pos CCode attribute to any position. The Vala position system is used for the instance position (instance_pos), array length position (array_length_pos), delegate target position (delegate_target_pos), and can even be used to reorder parameters (pos).

Vala's positioning system is a little confusing at first, so it bears explanation. Suppose we have a Vala function as follows:

public class Foo {
    public delegate int Transform (double a);
    public int[] compute (int x, Transform t);
}

The generated signature for compute will be:

int *foo_compute(Foo self, int x(position = 1), foo_transform t(position = 2), void *t_userdata, int *array_len);

I have marked the parameters that occur verbatim in Vala with their positions. From Vala's perspective, the position of self must be less than 1. Similarly, t_userdata must be greater than 2, and array_len must be greater than t_userdata for this ordering to make sense. Vala allows floating point values to describe this ordering. Once can think of self as having position 0, t's context as having position 2.1, and the returned array length as having position 2.2. This is just one possible set of values. It could also be 0.9, 2.5, 2.8, respectively, and produce the same result.

By default, Vala will set the instance to be 0, any array length to be the position of the array plus 0.1, any delegate's target to be the position of the delegate plus 0.1, any destructor for an owned delegate to be the position of the delegate plus 0.2.

If the order does not suit the C function, it is possible to reorder them using appropriate values, however you must keep the total order clean in your mind.

Default Values and Changing an Argument's Position

Since C does not have default parameters, there are sometimes duplicate C functions to act in this way:

int foo_compute(Foo *f, int base_height);
int foo_compute_ex(Foo *f, int base_height, Table *t, struct opts *opts);

Since Vala does have default parameters, it may be beneficial to only bind the extended version, but only if the default values are unlikely to change. This is generally true in where the documentation reads “set to null to determine automatically”. If unsure, it is best to bind both.

[CCode (cname = "foo_compute_ex")]
public compute (int base_height, Table t = null, opts? opts = null);

Adapting a Signature with a Vala Wrapper

It is possible to adapt an existing function signature by using a wrapper function written in Vala. This can be used to make the signature more Vala friendly.

This is usually done by making the C binding private and making the wrapper call the private method. The wrapper is also written in the VAPI file.

Variadic Arguments (a.k.a. “...”)

C variadic argument system is treacherous and includes lots of potential ways to break. Vala, unfortunately, inherits them. Vala adds a few safeties, but also introduced some new problems.

One safety put in place is that if the method's CCode attribute includes sentinel = "X", then X will always be appended as the last parameter. Since lists are often terminated by a special value, usually null, this can prevent variadic argument overruns.

Additionally, Vala can do type checking on printf-like and scanf-like function by adding the PrintfFunction or ScanfFunction attributes. However, if the format strings have been modified to include special values, these formatting tokens will not work as intended.

Return values that append to the end of a function, such as array length returns, and delegate contexts, will often interact badly with variadic arguments, since the Vala compiler will erroneously place the parameter after the “...” in the definition. When dealing with variadic functions, it is best to specify all positions explicitly.

Functions That Do Not Return

If a function will never return, the attribute NoReturn allows the compiler's analyzer to know that no code executed after that statement will ever be executed. This is rare, but useful for statements which call abort or exit underneath.

Methods that Change the Instance Reference

Sometimes methods return a new pointer to the instance (think realloc). Declare the function in the VAPI returns void and add the attribute ReturnsModifiedPointer.

typedef struct table Table;
Table *table_grow(Table *t, size_t object_count);

[Compact]
[CCode (cname = "Table")]
public class Table {
    [ReturnsModifiedPointer]
    public void grow (size_t object_count);
}

Methods that Destroy the Instance Reference

If a method destroys the instance (that is, frees it) it can be marked with the DestroysInstance attribute. The method must return void. Although in most cases such a method would be bound as the free_function of the compact class.

If a function destroys an instance but provides a useable return value, instead, bind it as a static method which takes an owned variable for the instance:

typedef struct transaction Transaction;
Transaction begin_tx(Database *db);
void transaction_abort(Transaction *tx);
void transaction_commit(Transaction *tx);
bool transaction_try_commit(Transaction *tx);

[Compact]
[CCode (cname = "Transaction", free_function = "transaction_abort")]
public class Transaction {
    public Transaction (Database db);
    [DestroysInstance]
    public void commit ();
    public static bool try_commit (owned Transaction tx);
}

Adding Vala Friendly Semantics

All bound methods should be public unless working around some awkward situation. The Vala compiler does not respect visibility in VAPI files, so defining private methods simply prevents them from appearing the Valadoc, not from being accessible.

Vala has some special method names that allow the method to be used with Vala syntax. Differences between C and Vala can be captured using the CCode attribute.

to_string () Methods

Binding a method as to_string () will allow the method to be used in a Vala string template without needing to write the method identifier in the Vala source code.

Properties

Vala allows compact classes to have properties, which are syntactic sugar for get and set method pairs. Often C objects with opaque implementations will provide a collection of functions to query state about the instance. These can be converted to properties given the following:

  • The get method has the signature T get(I self) and the set method has the signature void set(I self, T val). They need not actually occur in pairs.

  • The get method does not have side effects that are not obvious to the user.

  • The get method is cheap to call.

  • The set method does not have error information being returned.

Unlike most return types, the return of a get method is assumed to be unowned unless explicitly owned.

Consider:

typedef struct foo Foo;
int foo_item_count(Foo f);
int foo_max_items(Foo f);
void foo_set_max_items(Foo f);

public class Foo {
    public int item_count {
        [CCode (cname = "foo_item_count")] get;
    }
    public int max_items {
        [CCode (cname = "foo_max_items")] get;
        [CCode (cname = "foo_set_max_items")] set;
    }
}

All the usual CCode attributes may be applied to get; and set; and owned may be applied to change the default ownership of get;. Note that changing the ownership of the property is the wrong thing to do unless the instance doesn't actually own the value provided to it by set;.

Collections

Vala has several standard method names that are designed to work with Vala syntaxes like foreach.

The get () method is used by Vala to implement the square bracket indexing syntax. For example a list instance with a get method that returns a list item, list.get (index), can also be written list [index].

In the next example the C function signature returns an item in the collection:

blkid_partition
blkid_partlist_get_partition (blkid_partlist ls,
                              int n);

This could be bound in the VAPI as:

[Compact]
[CCode (cname = "blkid_partlist")]
public class ListOfPartitions {
    [CCode (cname = "blkid_partlist_get_partition")]
    public unowned Partition get (int index);
}

Note that [CCode (cname = "blkid_partlist_get_partition")] is used to change the Vala method name get to the name required in C. The binding can then be used in Vala code to get an item in the collection:

var partition = partitions [count];

set is the Vala method to replace an item in the collection. set must return void.

Both the index methods, get and set, can take as many parameters as the C function allowing for multi-dimensional indexes to be bound. With set in Vala the final parameter must be the new value.

By binding a size property in Vala to a function that returns the size of the collection in C then Vala's foreach keyword can be used with the collection. The get index method is also required. The following example continues with the PartitionList example above. The C function signature to get the size of the list is:

int
blkid_partlist_numof_partitions (blkid_partlist ls);

This is bound as:

[Compact]
[CCode (cname = "blkid_partlist")]
public class ListOfPartitions {
    [CCode (cname = "blkid_partlist_get_partition")]
    public unowned Partition get (int index);
    public int size { [CCode (cname = "blkid_partlist_numof_partitions")] get; }
}

Note that the CCode cname translation is inside the body of the property.

The binding can now be used in Vala code with foreach:

foreach (var partition in partitions) { /* do something with the partition */ }

If the collection is unowned then Vala will give an error: duplicating ListOfPartitions instance, use unowned variable or explicitly invoke copy method. See Bug 661876.

For an unowned collection a for loop will still work:

for (int count = 0; count < partitions.size; count++) {
    var partition = partitions [count];
    /* do something with the partition */
}

For container-like instances, Vala provides syntactic sugar to convert certain operations into method calls:

x in aa.contains (x)
a[x, y] → a.get (x, y)
a[x, y] = za.set (x, y, z);
foreach (var x in a) { … } → var x; var i = a.iterator (); while ((x = i.next_value ()) != null) {...}
foreach (var x in a) { … } → var i = a.iterator (); while (i.next ()) { var x = i.get (); … }

If appropriate, providing methods that match these prototypes will allow use of the sugar.

contains must return bool.

Iterators require an intermediate object to be the holder of the iteration state. That class must implement a next_value function that returns the next value or null if iteration is to stop or it may have a next method with signature bool next () that moves to the next element and returns true if there is one and a method T get () to retrieve the current value of the iterator. It is rare for a C program to have the interface needed to do this.

Use your best judgement in deciding whether or not to use these conventions. This is modifying the interface, but it does tend to make the resulting interface easier to use.

Binding a C Function's Parameter and Return Types

Basic Types

The most basic types (int, double, size_t) can simply be translated. There are various version of some types (e.g., uint32_t and u_int32_t are the same, but defined in different headers), but this can all be harmonised when binding.

Structs

The majority of libraries receive structs passed by reference and it is also the default behaviour of Vala to pass structs by reference. So to pass a struct as an argument in a function or method call you just need to specify the type of struct and the variable name. For example the C code:

typedef struct foo {
    int x;
    int y;
};
void compute_foo(foo *f);

would be bound as:

[CCode (cname = "foo", has_type_id = false)]
public struct Foo {
    public int x;
    public int y;
};
void compute_foo(Foo f);

Very rarely a C library function is written to receive a struct passed by value and not reference. You will see the struct keyword used in the C function's parameter. You may also see const struct. To get Vala to pass the struct by value the [SimpleType] annotation needs to be added to the Vala binding of the struct. The following pattern in C:

typedef struct foo {
    int x;
    int y;
};
void compute_foo(struct foo f);

would be bound as:

[CCode (cname = "foo", has_type_id = false)]
[SimpleType]
public struct Foo {
    public int x;
    public int y;
}
void compute_foo(Foo f);

Arrays

Vala arrays are designed to match most of the C array semantics. Since C arrays, generally, have no explicit length, Vala needs special hints to know what to do. There are several cases for the length of an array, discussed below. For a parameter, a CCode attribute attached to that parameter controls the array's binding. For a return value, the CCode attribute of the method controls the array's binding.

Array Length is Passed as an Argument

By default, Vala assumes the first case and does the following transformation:

void foo (double[] array);
double[] foo (float f);

void foo(double *array, int array_length);
double *foo(float f, int *array_length);

If the C code does this, there are still two potential mismatches: the order of parameters and the type of the array length. Often, the array length is a size_t or unsigned int. The array_length_pos can move the position of the array's length parameter, see Changing the Position of Generated Arguments. The array_length_type specifies a string with the C type of the array (e.g., size_t).

Array is Null-Terminated

The array_null_terminated will assume the array is null terminated, like a string is, and set the array length automatically by iterating over the items in the array. Since Vala always allocates padding in arrays with the final element as null, passing a Vala-declared array in does not involve modifying the array in any way.

Array Length is a Constant Expression

The array_length_cexpr can be set to the C expression that populates the array's value. It does not have access to the array, the instance of the object being called, or any other context. It must be a context-free expression.

Array Length is Unknown

If the array length is unknown, setting array_length = false in the CCode attribute will cause Vala to set the array's .length property to -1 and not pass the length when used as a parameter.

Array Length is Known by Some Awkward Means

This is only applicable for arrays being returned. If the array's length can be determined, but non-trivially, a wrapper function can be included that sets the array's .length property to the correct value. See Array Lengths.

Strings and Buffers

In C, strings and buffers are generally treated like arrays, but Vala may require slightly more finesse. In Vala, a string is a null-terminated list of UTF-8 data that is immutable. If the use case is anything but that, an array of uint8 is the prefered way of dealing with that data.

Frequently, functions take a buffer, fill it with a string and then return the buffer or null (e.g., realpath(3)). The buffer should be a uint8[] and the return value an unowned string?, typically.

Again, check thoroughly for the ownership of strings being returned. Frequently, the caller does not free the string, especially if it is marked const.

Function Pointers

Function pointers in C are bound as delegates in Vala. A delegate is a type that declares the function signature a function pointer should have. A function pointer can also have an associated data parameter called a target.

For delegates without targets, they can simply be treated as simple types.

For targeted delegates, the target must be included. Vala, by default, assumes this will be after the function pointer itself, but this can be adjusted with the delegate_target_pos. The position where the target is received is defined in the delegate's definition; not the calling function.

A delegate with a target cannot be trivially duplicated, since the target must also be duplicated. Thus, targeted delegates are treated much like singly-owned classes, which can be reassigned, but not multiply referenced.

If the method is going to retain a reference to the delegate, then it needs a helper function to destroy the delegate after it has finished. This position is after the target, but can be set with the delegate_target_destroy_notify_pos.

If a delegate is being returned, which is rather rare, the target and destroy notifier are assumed to be out parameters.

typedef void (*foo_func)(int x, void *context);

void call_foo(foo_func f, void *context);
void call_foo_later(foo_func f, void *context, void(*free_context)(void*));
foo_func get_foo(void **context);
foo_func make_foo(void **context, void(**free_context)(void*));

[CCode (cname = "foo_func", has_target = true)]
public delegate void FooFunc (int x);

public void call_foo (FooFunc f);
public void call_foo_later (owned FooFunc f);
public unowned FooFunc get_foo ();
public FooFunc make_foo ();

Parameters of Variable Type (Generics)

Vala's generics can be applied to C functions using void pointers as generic value arguments. Memory management and generics tend not to get along well, so it may be beneficial to avoid this situation where possible. In particular, generic structs that own instances of the generic can behave strangely. Also, putting owned structs in a generic collection can break.

Before starting, determine the scope of the type variable: does it apply to the method or class? Generics are paired with a delegate. Bind the delegate as follows:

typedef int (*foo_func)(void *a, void *b, void* context);

[CCode (cname = "foo_func", simple_generics = true)]
public delegate int FooFunc<T> (T a, T b);

Generic Methods

Frequently, a single method is the context for the generic type variable. Simply apply simple_generics to the CCode attribute:

void sort(void **array, int array_length, foo_func compare, void *context);

[CCode (simple_generics = true)]
public void sort<T> (T[] array, FooFunc<T> compare);

Occasionally, this is not a C function, but a function-like macro that takes the type name (e.g., va_arg), in which case, set generic_type_pos to the position of the argument:

#define sort(array, type, compare, context) ...

[CCode (generic_type_pos = 1.1)]
public void sort<T> (T[] array, FooFunc<T> compare);

Generic Classes and Structs

If a data structure is container-like, then it may be possible to bind the structure using generics. However, Vala's assumptions about generic structures are rather rigid, so this may be impossible.

  • Create a type variable over the class.
  • Decorate all methods that use the type variable with simple_generics.
  • Constructors for classes are expected to take the destructor as an argument if simple_generics is supplied. If the constructor takes no arguments, convert all constructors to static methods with simple_generics.

  • Verify all ownership. When Vala emits simple_generics code of an owned variable, it always passes the destructor. Frequently, C programs are written where the destructor is passed once in the constructor. In this case, set the destructor to be null, and insist that all values be unowned.

The User Pointer Case

Often, C libraries will have a pointer for some user data associated with an object that is left entirely in the hands of the user. This is easily bound.

typedef struct foo Foo;
void *foo_get_userptr(Foo*);
void foo_set_userptr(Foo*,void*);

public class Foo<T> {
    public unowned T? user_data {
        [CCode (cname = "foo_get_userptr", simple_generics = true)] get;
        [CCode (cname = "foo_set_userptr", simple_generics = true)] set;
    }
}

The only caveat is this is rather infectious: the simple_generics attribute must be applied to all methods use of Foo in other contexts, including arrays of that object and other classes that contain that type. To avoid this, the alternate binding is:

public class Foo {
   [CCode (simple_generics = true)]
   public void set_user_ptr<T> (T value);
   [CCode (simple_generics = true)]
   public T get_user_ptr<T> ();
}

However, this scheme is less type-safe.

Pointers

If you've worked this far down the list and the thing still seems to need to be a pointer, then a pointer it is, but this is a badge of shame.

Binding a C Struct's Fields

Compact classes, structs, and simple-type structs may have fields. Often, classes will be opaque; that is, there will be no information about the contents of the class. If so, skip this section. When binding fields, first check that there are no getter/setter functions of the same names (see Properties). Often times, the details of the structure are in the header, but not intended for public consumption; avoid binding variables that should not be accessed. Consult the documentation.

Structs

Any simple types (ints, doubles, enums, or simple types found in the same binding) can simply be bound by preceding them with public. This also applies to any parented structs that are not pointed to directly (i.e., they are foo f; not foo *f;)

Pointers to Structs

Any field referenced as a pointer is slightly more complex.

If the type is a parented struct or the field is possibly null, suffix the type with a question mark.

foo_t *myfoo;

public foo? myfoo;

The next question is: is the reference owned? If the value was overwritten, should the destructor be called? If the answer is no, then prefix with unowned. This is often the case for a tree structure that has parent pointers.

foo_t *parent;

public unowned Foo parent;

If unowned is missing, a double-free event will occur when the field is overwritten. If it is included when not needed, there will be a memory leak.

Arrays

Arrays come in two varieties in C: a pointer to allocated memory or included in the structure. Vala follows a similar convention:

int foo[20];
int *bar;

public int foo[20];
public int[] bar;

Note the position of the square brackets in the Vala versions. For fixed-length arrays Vala expects the square brackets (as well as the length) to follow the variable name, whereas for dynamically-sized arrays Vala expects the square brackets to follow the type (and not contain a length).

Again, if the array may be null, suffix the type with a question mark.

Vala arrays have lengths associated with them. Often, C programmers do this too:

int *foo;
size_t foo_count;

which is bound as:

[CCode (array_length_cname = "foo_count", array_length_type = "size_t")]
public int[] foo;

Often, the size will not be included, by the array will be null-terminated:

Foo **foos;

[CCode (array_null_terminated = true)]
public Foo[] foos;

Occasionally, the length is not included, but defined elsewhere, such as a constant:

/* Array length must be FOO_COUNT */
Foo **foos;

[CCode (array_length_cexpr = "FOO_COUNT")]
public Foo[] foos;

Since Vala will only allow a numeric value as an array length, using a array_length_cexpr may be convenient if the array length can vary as new releases of the library occur.

Vala does not really do C-style stacked arrays (a.k.a. ragged multi-dimensional arrays), so binding them as arrays is nigh impossible without extra C code.Since Vala's pointer semantics are the same, they can be treated as pointers though.

Function Pointers

Fields that are function pointers have more complexity depending on ownership and targeting. If a delegate is targetless then it can be treated as a simple type and no ownership considerations are needed.

If a delegate has a target then the C structure must have an holder for the target:

typedef void(*foo_func)(int a, void *userdata);

typedef struct {
    foo_func callback;
    void *callback_context;
} foo;

[CCode (cname = "foo_func")]
public delegate void FooFunc(int a);

public struct Foo {
    [CCode (delegate_target_cname = "callback_context")]
    public unowned FooFunc callback;
}

Check for nullability as per usual.

Ownership is slightly more complex as there must be a field to hold a function that will free the context data. In GLib terminology, this is a destroy notification.

typedef void(*foo_func)(int a, void *userdata);

typedef struct {
    foo_func callback;
    void *callback_context;
    void(*callback_free)(void*);
} foo;

[CCode (cname = "foo_func")]
public delegate void FooFunc(int a);

public struct Foo {
    [CCode (delegate_target_cname = "callback_context", delegate_target_destroy_notify_cname = "callback_free")]
    public FooFunc callback;
}

If the function pointer will be called exactly once and calling it should result in destruction of the context then use scope = "async".

typedef void(*start_job)(int priority, void *context);

void threadpool_queue_job(Pool *p, start_job j, void *context);

[CCode (scope = "async", cname = "start_job")]
public delegate void StartJob (int priority);

public class ThreadPool {
        public void queue_job (StartJob j);
}

Unions

Vala doesn't understand unions, but the names within the union can be held as part of the cname.

typedef struct {
    bool which_one;
    union {
        double d;
        int i;
    } data;
} foo_t;

public struct Foo {
    public bool which_one;
    [CCode (cname = "data.d")]
    public double data_d;
    [CCode (cname = "data.i")]
    public int data_i;
}

Extra Hints

You can bind a method multiple times. In particular, constructors that take a parent object often make sense as both constructors in the child and methods of the parent instance.

Sometimes, cname = "void" for a class can get around bad typedefs, but never for delegates as casting a void pointer to a function pointer is not legal C.

Feel free to add useful methods to the class definition if they make something more Vala-like.

Awkward Situations

There are a number of awkward situations that come up frequently enough to deserve answers.

Array Lengths

Sometimes functions which return an array include lengths, but not the way Vala expects. The two most common are:

int get_array(foo**out_array_p);

struct {
foo *data;
int size;
} array_with_length;
void get_data(array_with_length *output);

which can be bound as:

[CCode (cname = "get_array")]
private int _get_array ([CCode (array_length = false)] out foo[] a);
[CCode (cname = "vala_get_array")]
public foo[] get_array () {
    foo[] temp;
    var len = _get_array (out temp);
    temp.length = len;
    return (owned) temp;
}

[CCode (cname = "array_with_length", destroy_function = "")]
private struct array_with_length {
    [CCode (array_length_name = "size")]
    foo[] data;
}
[CCode (cname = "get_data")]
private void _get_data (out array_with_length a);
[CCode (cname = "vala_get_data")]
public foo[] get_data () {
    array_with_length temp;
    _get_data (out temp);
    return (owned) a.data;
}

Dependently Typed Ownership

A function may take ownership of an object conditionally. This can be true depending on parameters or return value. In the case of parameters, the function can be bound as such:

void somefunc(foo *data, bool free_when_done);

[CCode (cname = "somefunc")]
private _somefunc(Foo data, bool free_when_done);
[CCode (cname = "")]
private _sink_foo (owned Foo foo);
[CCode (cname = "vala_somefunc")]
public somefunc (Foo data) {
    _somefunc(data, false);
}
[CCode (cname = "vala_somefunc_owned")]
public somefunc_owned (owned Foo data) {
    _somefunc (data, true);
    _sink_foo ((owned) foo);
}

This is more awkward when the return code is source of the dependent typing. One option is as follows:

/* foo is freed if return value is 3. */
int awkward(foo*);

[CCode (cname = "")]
private void _sink_foo (owned Foo f);
[CCode (cname = "awkward")]
private int _awkward (Foo f);
[CCode (cname = "vala_awkward")]
public int awkward (ref Foo f) {
    var ret = _awkward (f);
    if (ret == 3)
        _sink_foo ((owned)f);
    return ret;
}

Member Length

When dealing with raw-ish memory access, there is a common pattern to have:

void foo(void *data, size_t size, size_t nmemb);

in these cases, it is generally best to simply fix the type of data to uint8 and use the right size as a default parameter:

public void foo([CCode (array_length_pos = 2.1)] uint8[] data, size_t size = 1);

Owned Array of Unowned Objects

Vala does not have a convenient way to express the an owned array of unowned objects. See bug 571486.

Shared Context Delegates

When multiple delegates are passed, they sometimes share a context pointer:

void foo(void *context, void(*x)(int a, void *context), void(*y)(double a, void *context));

Here, x and y are meant to share context, but Vala's delegates do not have a way of expressing this. However, there is a work around:

[CCode (simple_generics = true, has_target = false)]
public void X<T> (int a, T context);
[CCode (simple_generics = true, has_target = false)]
public void Y<T> (double a, T context);
[CCode (simple_generics = true);
public void foo<T> (T context, X<T> x, Y<T> y);

This does not easily permit passing a lambda, but it does make passing a class or struct rather practical.

Projects/Vala/ManualBindings (last edited 2024-04-05 16:19:04 by ReubenThomas)