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.

No comments: