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:
- A shared library written in vala, including
- shared object (.so), and how to install it
- A command-line tool that uses the library by direct-linking
- 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)
- 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 ", test.hello());
// MyLib sum()
int x = 4, y = 5;
stdout.printf("The sum of %d and %d is %d ", 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 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 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 "); }
}
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 "));
// 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 its 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