Thursday, February 21, 2013

Logging with Vala

How to send messages to syslog using Vala:

// log.vala

public static void main() {

    // Application Name (called 'domain' in Gspeak)
    string application_name = "MyTestApplication";

    // Log message
    string message = "This is a test message";

    // Log level - set to "Warning"
    GLib.LogLevelFlags glib_level       = GLib.LogLevelFlags.LEVEL_WARNING;
    int                posix_level      = Posix.LOG_WARNING; 

    // Print to stderr, not syslog or another log
    GLib.log( application_name, glib_level, message );

    // Log to syslog using the posix bindings
    // Posix.LOG_PID and Posix.LOG_USER are defined by Posix, not me.
    Posix.openlog(application_name, Posix.LOG_PID, Posix.LOG_USER);
    Posix.syslog(posix_level, message);

    return;
}

Compile using valac --pkg posix log.vala

String Arrays in Vala

String Arrays are simply bunches of strings grouped together

string[] apples = {"red delicious", "granny smith", "macintosh", "gala", "fuji"}

They look like Python lists, but they are not. They often don't work like lists (you cannot slice them), but they work *really* fast. They are useful if you have a data set that is (mostly) immutable...you can append to it, but I haven't found a way to delete strings from the array without copying everything else into a new array.

Here is a demo program showing how to
  • Create a string array
  • Append a string to an array
  • Match a string in an array
  • Determine the index of the matched string
  • Retrieve a string from the array (non-destructively)
  • Replace a string with another withing an array
  • Concatenate the array into a single string (for printing)

// string_array.vala

// If a in b
void if_a_in_b ( string a, string[] b, string b_name )  {
    if (a in b) { 
        stdout.printf("Found %s in %s\n", a, b_name); 
    }
    else {
        stdout.printf("%s not Found in %s\n", a, b_name);
    }
    return;
}


// One way to print an array
void print1 (string[] a, string a_name) {
    stdout.printf("Array %s: ", a_name);
    foreach (string item in a) {
        stdout.printf("%s, ", item);
    }
    stdout.printf("\n");
    return;
}

// Another way to print an array
void print2 (string[] a, string a_name) {
    string a_string = string.joinv(", ", a);
    stdout.printf("Array %s : %s\n", a_name, a_string);
    return;
}

// Index of an item in an array
void index_array (string[] a, string a_name, string match) {
    // Record the index of all matches
    int[] indexes = {};
    int i;
    for (i = 0; i < a.length; i++) {
        if (a[i] == match) {
            indexes += i;
        }
    }
    // Print the results
    if (indexes.length == 0) {
        stdout.printf("Indexing %s: %s not found\n", a_name, match);
    }
    else if (indexes.length == 1) {
        stdout.printf("Indexing %s: %s found at position %d\n", 
        a_name, match, indexes[0]);        
    }
    else if (indexes.length == 2) {
        stdout.printf("Indexing %s: %s found at positions %d and %d\n", 
        a_name, match, indexes[0], indexes[1]);        
    }
    else {
        stdout.printf("Indexing %s: %s found at positions ", 
        a_name, match);
        // Convert ints to a strings
        int j;
        for (j = 0; j < indexes.length; j++) {
            if ( j < (indexes.length - 1)) {
                stdout.printf("%d, ", indexes[j]);
            }
            else {
                stdout.printf("and %d.\n", indexes[j]);
            }
        }
    }
    return;
}



void arrays () {
    // Create two string arrays 
    string[] orange = { "fred", "joe", "allen", "steve" };
    string[] blue   = { "jane", "sam", "ellie", "terri" };

    // Test contents of each array
    if_a_in_b ("fred", orange, "Orange");
    if_a_in_b ("fred", blue, "Blue");

    // Length of an array
    stdout.printf("List length: %i\n", orange.length);

    // Item from an array
    stdout.printf("Orange item #2: %s\n", orange[1]);

    // Replace one string in an array
    blue[2] = "pamela";

    // Determine the index (location) of a string in an array
    index_array(blue, "blue", "pamela");

    // Append one string to an array 
    blue += "stacy";

    // Print an array
    print1(orange, "orange");
    print2(blue, "blue");

    return;
}


// Main
public static void main() {
    arrays ();
    stdout.printf("Hello, World\n");
    return;
    }


Compile with a simple valac string_array.vala



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.

Saturday, February 9, 2013

Starting to learn Vala

I have decided to learn Vala. Instructions are here.




Hello Dbus:

Everyone starts with "Hello World." I did too. Little is gained by repeating it. So here is something different. Here is my first dbus introspection in vala:

// This vala file is named dbus-introspection.vala
[DBus (name = "org.freedesktop.DBus.Introspectable")]   // Dbus interface
interface Introspectable : Object {
    [DBus (name = "Introspect")]                        // Dbus method
    public abstract string introspect() throws IOError;
}

void main () {
    Introspectable conn = null;    // If inside the try, causes fail
    try {
        conn = Bus.get_proxy_sync (BusType.SESSION,    // Dbus session
                   "org.freedesktop.Notifications",    // Dbus destination
                   "/org/freedesktop/Notifications");  // Dbus path
        string reply = conn.introspect();              // the actual event
        stdout.printf ("%s\n", reply);                 // print response
    } catch (IOError response) {                       // print error
        stderr.printf ("IO Error: %s\n", response.message);
    }
}

Compile using $ valac --pkg gio-2.0 dbus-introspect.vala
Run using $ ./ dbus-introspect

The vala file is 19 lines.
The vala-generated C file is 300 lines, and about 15 times the size of the vala file.
The compiled binary is 19.4K, 130% the size of the C file.
But 19.4K is still pretty small, and it does run really fast!

For my own reference:

  • The dbus server information we need to connect. This is a common service that has Introspection:
    bus         = session bus
    destination = org.freedesktop.Notifications
    path        = /org/freedesktop/Notifications
    interface   = org.freedesktop.DBus.Introspectable
    method      = Introspect
    message     = (none)

  • main () { } is where the program starts.
    Equivalent to python's if "__name__" == "__main__":.
    Since it's the main, it returns either a return code (int) or nothing (void), and nothing else. The example above returns nothing.
    To add a return code, add return 0; to the end of the try { } block, and add return 1; to the end of the catch { } block

  • Michael Brown pointed out a very useful tidbit to avoid a lot of confusion:

  • Vala class = DBus interface
  • Vala interface = DBus proxy
  • This is the tricky part.
    Building the dbus connection (proxy) means we call that variable an interface in vala.
    And each dbus interface must be defined as a separate class in vala.
    To that, I will add that each class must be a GLib.Object.

    So a connection must start by opening an instance of the class, then adding the proxy information. Interface first, then proxy. That's backwards from the way dbus-send or python do it.

    For example:
    The dbus interface org.freedesktop.DBus.Introspectable gets defined as a vala class:
    interface Introspectable : Object { }


    Next, let's add the method and the labels to this class:
    [DBus (name = "org.freedesktop.DBus.Introspectable")]   // Dbus interface
    interface Introspectable : Object {
        [DBus (name = "Introspect")]                        // Dbus method
        public abstract string introspect() throws IOError;
    }
    

  • Methods must be public and abstract. We expect it to return a string (introspection returns a string of XML). As far as I can tell, methods should throw IOError in case the dbus message is malformed, the receiving service does not exist or has no such message, etc. The method is part of the interface's Object.


  • Main uses the try { } catch { } blocks to locate those IOErrors.
    Initial creation of the interface must be outside the try block, or the program won't be listening for the dbus response. Adding the bus and proxy information within the try block is not required...if you never make spelling mistakes.