Thursday, February 14, 2013

Using custom C libraries with Vala

Let's try to use Vala with custom C libraries.

For this example, let's see if we can use Vala to manilulate a gdbm database.
Gdbm is a tiny db used in lots of applications.

In order for a C library to work in Vala, it needs a .vapi file to translate.
In this example, we will use the gdbm library (a small, common database in C)  using Vala bindings.

There are two issues for new users here:
  1. Linking to C libraries in the C compiler
  2. Linking to vapi files in the Vala interpreter
You'll see how to handle both.


1) Download the sources:
  • Create a working directory
  • Install the libgdbm-dev package, to get the c headers.
  • I discovered a vapi file for gdbm created by Andre Masella. Thank you, Andre!
  • Download and install the vapi file. Vapi files belong in /usr/share/vala/vapi/
  • Open a browser window to the Gnu GDBM documentation.
$ mkdir /home/me/vala/gdbm_vala
$ cd /home/me/vala/gdbm_vala
# sudo apt-get install libgdbm-dev
$ wget https://raw.github.com/apmasell/vapis/master/gdbm.vapi
# sudo ln /home/me/vala/gdbm_vala/gdbm.vapi /usr/share/vala/vapi/


2) Create a simple file in C to test the gdbm library:
This simple file (source) is /home/me/vala/gdbm_vala/gdbm_version1.c
It gets the gdbm_version string from the gdbm library.
#include <stdio.h>
#include <gdbm.h>
int main() {
    printf("VERSION (C): %s.\n", gdbm_version);
}
Let's compile it and run it to test the c headers we retrieved in the libgdbm-dev package.
  • The gcc -o flag specifies the output file name

$ gcc -o gdbm_version1 gdbm_version1.c
/tmp/cc31QKu0.o: In function `main':
gdbm_version1.c:(.text+0xa): undefined reference to `gdbm_version'
collect2: error: ld returned 1 exit status

Uh-oh. Fatal error.
The compiler did not understand the 'gdbm_version' variable because gdbm.h is not in it's standard library. I must tell the compiler to link to it using the -l flag.

Try again, linking to the gdbm library.

$ gcc -o gdbm_version1 gdbm_version1.c -l gdbm
$ ./gdbm_version1
VERSION (C): GDBM version 1.8.3. 10/15/2002 (built Jun 11 2012 00:27:26).

There should be no errors or warnings. Success!


3) First program:

Here's the relevant entry in the vapi file for the version number:

[CCode(cheader_filename = "gdbm.h")]
namespace GDBM {
    ...
    [CCode(cname = "gdbm_version")]
    public const string VERSION;
    ...
}

Namespace GDBM + string VERSION, so "string GDBM.VERSION" in Vala should translate to "string gdbm_version" in C. And we just tested that the C version works.

Let's try a simple Vala program that checks the version number:

private static void main () {
    stdout.printf("The gdbm version string: %s\n", GDBM.VERSION);
    }

And compile it:
  • --pkg gdbm tells valac to look for the gdbm vapi file.

$ valac --verbose --pkg gdbm gdbm_version2.vala 
Loaded package `/usr/share/vala-0.18/vapi/glib-2.0.vapi'
Loaded package `/usr/share/vala-0.18/vapi/gobject-2.0.vapi'
Loaded package `/usr/share/vala/vapi/gdbm.vapi'
cc -o '/home/ian/vala/gdbm_vala/code/gdbm_version2' '/home/ian/vala/gdbm_vala/code/gdbm_version2.vala.c' -I/usr/include/glib-2.0 -I/usr/lib/i386-linux-gnu/glib-2.0/include  -lgobject-2.0 -lglib-2.0
/tmp/ccnYcsLH.o: In function `_vala_main':
gdbm_version2.vala.c:(.text+0xf): undefined reference to `gdbm_version'
collect2: error: ld returned 1 exit status
error: cc exited with status 256
Compilation failed: 1 error(s), 0 warning(s)

Uh-oh. Something went wrong.

When something goes wrong , the --verbose flag is your friend.
The output shows that the Vala was translated into C without any warnings or errors, but then something was wrong when the compiler tried to prcoess the C.

Wait a second...it's the SAME ERROR we had before in the C program!
We know how to fix that: Tell the compiler to link (-l flag) to the gdbm library.

Check the compiler command, and --sure enough-- link to the gdbm library is missing.

Two lessons here:
  • Vala's link to a pkg does not necessarily mean the compiler is linked to the library. valac --pkg foo may not translate into cc -lfoo
  • Use valac's -Xcc flag to pass links to the compiler.

Let's try compiling again, adding /usr/include/gdbm.h using an Xcc flag, and removing --verbose:
  • --pkg gdbm tells valac to use the gdbm vapi file
  • --Xcc=-gdbm tells the C compiler to link to the C gdbm header

$ valac --pkg gdbm --Xcc=-lgdbm gdbm_version2.vala 
$ ./gdbm_version2
The gdbm version is: GDBM version 1.8.3. 10/15/2002 (built Jun 11 2012 00:27:26)

No errors or warnings. Success!


4) Take a break

We are now past the a bunch of biggest beginner hurdles:
  1. How to figure out common compile errors.
  2. How to tell the compiler to link to libraries.
  3. How to tell valac to use vapi files.

 5) More complicated program

Here is a more complicated program that opens a test database (or create a new db if it doesn't exist), and look for a certain key. If the key is not in the database, it appends the key:value pair to the database.
  • It uses the gdbm.vapi bindings for open(), read/write/create permission, contains(), and save() [which really means append].

// File: /home/me/vala/gdbm_vala/code/gdbm_add
private static int main () {

    // Hard-coded values
    string filename = "/home/me/vala/gdbm_vala/code/sample_db.gdbm";
    string key_string = "spam";
    string value_string = "eggs";

    // Open the database
    // GDBM.OpenFlag.WRCREAT comes from the vapi file. It means read+write+create new db
    GDBM.Database db = GDBM.Database.open (filename, 0, GDBM.OpenFlag.WRCREAT);

    // Convert the string values to bytes. gdbm doesn't save strings.
    uint8[] key_int = key_string.data;
    uint8[] value_int = value_string.data;

    // If the key_int is already in the database, say so and exit.
    if (db.contains (key_int) == true) {
        stdout.printf("In db\n");
        return 0;
        }
    stdout.printf("Not in db\n");

    // If appending the key:value pair to the database is successful, say so and exit.
    if (db.save (key_int, value_int, false) == true) {
        stdout.printf("Good append saved\n");
        return 0;
        }

    // Problems
    stdout.printf("Failed to append\n");
    return 1;
    }

Let's compile it.

$ $ valac --pkg gdbm --Xcc=-lgdbm gdbm_add.vala 
/home/ian/vala/gdbm_vala/code/gdbm_add.vala.c: In function ‘_vala_main’:
/home/ian/vala/gdbm_vala/code/gdbm_add.vala.c:192:2: warning: passing argument 1 of ‘gdbm_open’ discards ‘const’ qualifier from pointer target type [enabled by default]
In file included from /home/ian/vala/gdbm_vala/code/gdbm_add.vala.c:9:0:
/usr/include/gdbm.h:85:18: note: expected ‘char *’ but argument is of type ‘const gchar *’

Oh, my.

Let's look more closely at these error messages. Each error takes two lines
  • The first warning is in the generated C file, line 192, warning: passing argument 1 of ‘gdbm_open’ discards ‘const’ qualifier from pointer target type [enabled by default].

    Line 192 of the C file looks like _tmp4_ = gdbm_open (_tmp3_, 0, GDBM_WRCREAT, 0644, NULL); which looks a lot like the original gdbm_open function again.

    Valac is moving the filename to _tmp3_, and that _tmp_ variable seems to be the wrong type. It's just a warning - the code still compiles. And it seems to be a bug in valac, not a problem with our code.
  • The second warning is in the generated C file, line 9. That's the #include line. The warning is expected ‘char *’ but argument is of type ‘const gchar *’

    This is an interesting warning, and I'm not sure my merely including the header would trigger it...but it's also just a warning, and the compiled binary works.
Finally, let's run the binary a few times. The first time, the database does not exist, so the program should create the database and populate it with one key:value pair. On subsequent runs, the database should already exist, and the program should successfully find the existing key.

$ ./gdbm_add 
Not in db
Good append saved
$ ./gdbm_add 
In db
$ ./gdbm_add 
In db

It works!

We've gotten past the namespace hurdle, the vapi hurdle, and have successfully manipulated a gdbm database using the original C library.

No comments: