Writing C extensions for Racket
Written by Dominik Joe Pantůček on 2016-05-26
racketAs our latest project has progressed we encountered a strange obstacle: there is no portable way to determine file inode number in Racket. That would not be much of a problem, but as we tried to dynamically link stat-like functions from libc using ffi, we found that each platform and glibc version has different ABI - including the sizes of various stat structure fields. So we dived into writing extensions in the C programming language.
It turned out writing Racket extensions in C is dead simple. Let us dive into it using a short example. In our case we needed a function that would return the inode number of given file. It should be called like this:
(get-inode-path "filename")
We started with a simple extension that is wrapped as Racket module. As the very first thing you must include the "escheme.h" header:
#include "escheme.h"
Then we write a skeleton of our function:
Scheme_Object *get_path_inode(int argc,
Scheme_Object **argv) {
return scheme_make_integer(0);
}
As you can see, each extension function accepts two C arguments - the number of actual arguments and an array of those. Right now it just returns zero but we will get onto that later on. Now we need to register our function, create the module and pass everything to the Racket runtime. Using the function scheme_initialize we do just that. For more complex modules a proper reload function is needed but in this case it will do the same thing as the initialize function does. As the last bit of building the extension, we properly export the module name as yet another function. Everything looks like this:
Scheme_Object *scheme_initialize(Scheme_Env *env) {
Scheme_Env *mod_env;
Scheme_Object *get_path_inode_func;
mod_env =
scheme_primitive_module(scheme_intern_symbol("path-inode"),
env);
get_path_inode_func =
scheme_make_prim_w_arity(&get_path_inode,"get-path-inode",1,1);
scheme_add_global("get-path-inode",
get_path_inode_func,
mod_env);
scheme_finish_primitive_module(mod_env);
return scheme_void;
}
Scheme_Object *scheme_reload(Scheme_Env *env) {
return scheme_initialize(env);
}
Scheme_Object *scheme_module_name() {
return scheme_intern_symbol("path-inode");
}
And that is it. We have just created an extension module that can be used as any other module:
(require "path-inode")
(displayln (get-path-inode "some-file"))
And yes, it prints the number zero. So the only thing left is the actual call of stat function and return the inode number from resulting structure. We add the relevant code to our C function get_path_inode:
char *path;
struct stat64 buf;
...
if (lstat64(path, &buf) != 0)
return scheme_make_integer(-1);
return scheme_make_integer(buf.st_ino);
So that is the basic part. But how do we convert the string, byte string or path object into the C string lstat64 function expects? That is also really straightforward. All we need is to check for all these possible variants and handle them accordingly:
Scheme_Object *path_utf8;
if (SCHEME_CHAR_STRINGP(argv[0])) {
path_utf8 = scheme_char_string_to_byte_string(argv[0]);
path = SCHEME_BYTE_STR_VAL(path_utf8);
} else if (SCHEME_BYTE_STRINGP(argv[0])) {
path = SCHEME_BYTE_STR_VAL(argv[0]);
} else if (SCHEME_PATHP(argv[0])) {
path = SCHEME_PATH_VAL(argv[0]);
} else {
return scheme_make_integer(-1);
}
And that is all. We probably should raise an exception in case something goes wrong (bad argument type or lstat64 call fails) but we just return -1 for now. It satisfies our needs and does not introduce any unnecessary complexity. We can test it in the Racket's REPL:
$ racket
Welcome to Racket v6.4.
> (require "path-inode")
> (get-path-inode "path-inode.c")
3022724
>
Hope you enjoyed another LISP-related blog and see ya next week as usual.
Cheers!