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.
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.
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.
Software archeologist – Recovering Physicist – Dancer
As scientist I studied the very small quantum world. As a hacker I distill code. Software is eating the world, and less code means less errors, less problems, more world to enjoy. Now I build on Cardano for a world where I'm back into control.