On the previous post I covered how I can call external programs as new
processes and interact with their inputs or outputs. Here I will use
directly the foreign libraries written in C using Guile’s foreign function
interface
.
In this series of posts I record how to use Guile as a scripting language
and solve various tasks related to email work.
Guile dynamic Foreign Function Interface
This is where you really have to read foreign code and lots of it. You need
to recognize how the library you are developing the bindings for works, and
which parts of it you are going to need.
The next example is the simple case, just link the library using
dynamic-link
and then register a function. You need to give the return
type and the parameter types, any pointer is declared with '*
. The final
line evaluates the function and calls pointer->string
to convert the
returned pointer into something that we can read.
1(use-modules
2 (system foreign))
3
4(define nmlib (dynamic-link "libnotmuch"))
5
6;; const char * notmuch_status_to_string (notmuch_status_t status);
7(define nm-status
8 (pointer->procedure '*
9 (dynamic-func "notmuch_status_to_string" nmlib)
10 (list int)))
11
12(pointer->string (nm-status 5)) ;; => File is not an email
What is left to do is repeat this process for all the functions that you
need exposed in Guile. That is terribly tedious and it doesn’t scale. In
the next post will present a way to do it automatically. In what is left, I
explain more low-level elements I used to expose the notmuch library to
Guile. I do this because, before going automatic on something, I need to
understand how it works manually.
Connecting to the database
The easy example is always too simple to learn something about it, I always
get annoyed on those tutorials that stay on the example and never show some
real use. That is why I document here how I managed to get some things
working.
Getting the rest of the interface is a lot more of work. Fortunately, the
dynamic link is not that different from the python ctypes and notmuch
already comes with a python interface implemented with ctypes. It was not a
simple copy, but something I could orient myself to get things working.
1(use-modules
2 (rnrs bytevectors)
3 (system foreign))
4
5(define nmlib (dynamic-link "libnotmuch"))
6
7;; const char * notmuch_status_to_string (notmuch_status_t status);
8(define nm-status
9 (pointer->procedure '*
10 (dynamic-func "notmuch_status_to_string" nmlib)
11 (list int)))
12
13;; notmuch_status_t notmuch_database_open (const char *path,
14;; notmuch_database_mode_t mode, notmuch_database_t **database);
15(define nm-db-open
16 (pointer->procedure uint32
17 (dynamic-func "notmuch_database_open" nmlib)
18 (list '* uint32 '*)))
19
20;; const char * notmuch_database_get_path (notmuch_database_t *database);
21(define nm-db-path
22 (pointer->procedure '*
23 (dynamic-func "notmuch_database_get_path" nmlib)
24 (list '*)))
25
26(define (make-blob-pointer len)
27 (bytevector->pointer (make-bytevector len)))
28
29;; This db-pointer is of type **, it is the pointer to a pointer, because I
30;; make a pointer to the byte vector, which is of a size to hold a pointer
31;; address. That address is zero, because the byte vector is initialized to
32;; zero. That means, I get a pointer to a null pointer to start with.
33(define db-pointer (make-blob-pointer (sizeof ptrdiff_t)))
34(format #t "DB pointer points to a null pointer: ~s~%"
35 (null-pointer? (dereference-pointer db-pointer)))
36
37
38;; The next codeblock has to be read from the bottom line to the top one to
39;; understand what is going on, as each result is passed to another
40;; function. When I pass db-pointer I'm passing the pointer by referecence
41;; that the function notmuch_database_open requires.
42(format #t "Open database: ~a~%"
43 (pointer->string
44 (nm-status
45 (nm-db-open (string->pointer "/home/titan/.mail/") 0 db-pointer))))
46
47;; To access the datastructure of the database I need to dereference the
48;; pointer. Here I verify that my byte vector has been changed to hold the
49;; memory address of the database
50(format #t "Open DB pointer is null?: ~s~%"
51 (null-pointer? (dereference-pointer db-pointer)))
52
53;; I can recover values from the database, like for example the path
54(format #t "Database path: ~a"
55 (pointer->string (nm-db-path (dereference-pointer db-pointer))))
Executing this small script gives the following result. I see each
stage. First starting with a null pointer, then opening the database which
returns a no error status. I verify that now I don’t have a null pointer
anymore, since now it points to the database address, which I verify by
requesting the registered setup information.
1DB pointer points to a null pointer: #t
2Open database: No error occurred
3Open DB pointer is null?: #f
4Database path: /home/titan/.mail
I should close the database, that is part of managing memory. For the
moment I don’t want to continue describing/implementing this interface,
since there is a way to do it automatically that I describe on the next
post. I just let the script end and close, that frees resources.