Sunday, March 3, 2013

Creating libraries and linking to libraries in Vala

This is an example of how to create a library in vala, and how to access it using several different methods. This example includes:
  1. A shared library written in vala, including
    • shared object (.so), and how to install it
    • headers (.h), and
    • bindings (.vapi)
  2. A command-line tool that uses the library by direct-linking
  3. A dbus server that uses the library by direct linking
    • makes the library available to other dbus-aware applications
    • self-terminating process after use (not a daemon)
  4. A command-line tool that uses the library via dbus and the dbus-server.

The first half of the example is based on the official vala tutorial library example,
I have added dbus connectivity and a bit more explanation that I found helpful.





1) Create an empty directory. These steps create a lot of files!

    $ mkdir test_library
    $ cd test_library



2) Create the library. Save this file as libtest.vala:

// BEGIN
public class MyLib : Object {

    public string hello() {
      return "Hello World from MyLib";
   }

   public int sum(int x, int y) {
      return x + y;
   }
}
// END



3) Create the .c, .h (C header), and .vapi (vala binding) files:
   -C --ccode   Output C code instead of compiled
   -H --header=file  Output C header file
      --library=name Assign name to library
      --vapi         Assign name to vapi file
   -b --basedir=dir  Use dir as the base source dir (For me, this is unnecessary)

    $ valac -C -H libtest.h --library libtest libtest.vala --basedir ./



4) Compile the library using:

   -shared       Create a .so shared object
   -fPIC         Position Independent Code (PIC) suitable for use in a shared library
   $(pkg-config --cflags gobject-2.0)
                 Output: -I/usr/include/glib-2.0
                         -I/usr/lib/i386-linux-gnu/glib-2.0/include
   -o filename   Output filename

     $ gcc -shared -fPIC -o libtest.so $(pkg-config --cflags --libs gobject-2.0) libtest.c



5) Create the command-line application that uses the library by linking.
   Save this file as hello.vala:

// BEGIN
void main() {
    var test = new MyLib();

    // MyLib hello()
    stdout.printf("%s\n", test.hello());

    // MyLib sum()
    int x = 4, y = 5;
    stdout.printf("The sum of %d and %d is %d\n", x, y, test.sum(x, y));
}
// END




6) Compile the command-line application.
Using vala, there need to be TWO links:
  • The vala link to a .vapi file (in this example, test.vapi)
  • The gcc link to a C library (in this example, -X -ltest to add libtest.so)

   - The vala link to a .vapi file (in this example, test.vapi)
   - The gcc link to a C library (in this example, -X -ltest to add libtest.so)

   -X --Xcc=-I.    Pass the -I. (include current directory) to gcc.
                   GCC will look for shared libraries in the current directory first
   -X --Xcc=-L.    Pass the -L. (add current directory to search path) to gcc.
   -X --Xcc=-ltest Link library "libtest" which we just created in the current directory. 

   If we had the libtest.so in the local directory, we could use:

    $ valac -X -I. -X -L. -X -ltest -o hello hello.vala libtest.vapi



7) Test the command-line application that uses the library:
The library is in the local directory (uninstalled):

     $ LD_LIBRARY_PATH=$PWD ./hello
     Hello World from MyLib
     The sum of 4 and 5 is 9



8) We cannot easily use the local library for dbus.
So install the library to the expected location.
Copy the library to /usr/lib using:

      $ sudo cp libtest.so /usr/lib/



9) Test the (installed) command-line application:

     $ ./hello
     Hello World from MyLib
     The sum of 4 and 5 is 9



10) Create the dbus server that uses the library.
A server listens for requests from other applications. This server acts as a gateway: It translates other application requests for library methods into a library call, and then sends the response back across dbus to the original requestor.

Save this file as dbus_server.vala:

// BEGIN
// Source: https://live.gnome.org/Vala/DBusServerSample#Server

[DBus (name = "org.example.Demo")]   // dbus interface name
public class DemoServer : Object {

    /* Functions that access the library on the Demo interface
     * Note the LACK of \n in the return strings.
     * Vala automatically mangles my_function_name into the
     *   standard Dbus camelcase MyFunctionName
     * So a function is "hello()" here, but "Hello" on Dbus 
     */
    public string sum () { 
        var test = new MyLib();
        int x = 4, y = 5;
        return "The sum of %d and %d is %d".printf( x, y, test.sum(x, y));
        // Note the LACK of \n in the return strings
    }

    public string hello () { 
        var test = new MyLib();
        return "%s".printf( test.hello());
    }

}

[DBus (name = "org.example.DemoError")]
public errordomain DemoError { SOME_ERROR }

/* Dbus functions */
void on_bus_aquired (DBusConnection conn) {
    try {
        string path = "/org/example/demo";
        conn.register_object (path, new DemoServer ());
    } catch (IOError e) { 
    stderr.printf ("Could not register service\n"); }
}

void main () {
    GLib.MainLoop loop           = new GLib.MainLoop ();
    GLib.TimeoutSource time      = new TimeoutSource(3000);   // 3 sec
    GLib.BusType bus             = BusType.SESSION;
    string destination           = "org.example.demo";
    GLib.BusNameOwnerFlags flags = BusNameOwnerFlags.NONE;

    Bus.own_name ( bus, destination, flags,
                  on_bus_aquired,
                  () => {},
                  () => stderr.printf ("Could not aquire name\n"));

    // Use timeout to quit the loop and exit the program
    time.set_callback( () => { loop.quit(); return false; });
    time.attach( loop.get_context() );  // Attach timeout to loop

    loop.run ();                // Listen for dbus connections

}
// END



11) Compile the dbus server:
   Dbus requires the gio package (--pkg gio-2.0)
   -X --Xcc=-I.    Pass the -I. (include current directory) to gcc.
                   GCC will look for shared libraries in the current directory first
   -X --Xcc=-L.    Pass the -L. (add current directory to search path) to gcc.
   -X --Xcc=-ltest Link library "libtest" which we just created in the current directory. 

   Since the library is installed, we can ignore those local directory flags:

      $ valac --pkg gio-2.0 -X -ltest dbus_server.vala libtest.vapi



12) Create a dbus .service file, so dbus knows how to find our new dbus_server.
Save this file as dbus.service. Your third line will vary - use the real path:

// BEGIN

[D-BUS Service]

Name=org.example.demo

Exec=/home/me/vala/library/dbus_server

// END



13) Install the dbus service file:

      $ sudo cp dbus.service /usr/share/dbus-1/services/org.example.demo.service



14) Test the dbus server using an existing command-line application, dbus-send.
This is a command-line application using the library via dbus, using dbus_server for it's intended purpose as a gatetay.

The sender is different each time because dbus-send creates a new process and connection each time it is used.
The destination is different because the server terminates after 3 seconds.

      $ dbus-send --session --type=method_call --print-reply \
        --dest="org.example.demo" /org/example/demo org.example.Demo.Hello
      method return sender=:1.3173 -> dest=:1.3172 reply_serial=2
      string "Hello World from MyLib"

      $ dbus-send --session --type=method_call --print-reply \
        --dest="org.example.demo" /org/example/demo org.example.Demo.Sum
      method return sender=:1.3175 -> dest=:1.3174 reply_serial=2
      string "The sum of 4 and 5 is 9"


15) Clean up:
Remove the dbus service file:
Remove the test library from /usr/lib

     $ sudo rm /usr/share/dbus-1/services/org.example.demo.service
     $ sudo rm /usr/lib/libtest.so