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:
- Linking to C libraries in the C compiler
- 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:
- How to figure out common compile errors.
- How to tell the compiler to link to libraries.
- 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.