Let's take a classic example to illustrate this new functionality:
Suppose you want to write C code and call out to a library called
calc. calc is written entirely in Common Lisp, full of classes
and objects and methods defining ASTs for a simple symbolic
calculator. It has functions like parse-expr
, simplify
, and
pretty-print-expr
, where everything works on Lisp objects.
(defclass expression ()
()
(:documentation "An abstract expression."))
(defclass int-literal (expression)
((value :reader int-literal-value))
(:documentation "An integer literal."))
(defclass sum-expression (expression)
((left-arg :reader sum-expression-left-arg)
(right-arg :reader sum-expression-right-arg)))
(defun parse (string) ...)
(defun simplify (expr) ...)
(defun expression-to-string (expr) ...)
We'd like to expose these functions so that we can call these functions
from other programming languages using the C ABI. So how does it
work in the simplest case of using calc from a simple C program?
First, we need to define these function pointers in Lisp. The
primitives SBCL now exposes to deal with this is through the macro
sb-alien:define-alien-callable
. You can use this macro in the
following way:
(define-alien-callable calc-parse int ((source c-string) (result (* (* t))))
(handler-case
(progn
;; The following needs to use SBCL internal functions to
;; coerce a Lisp object into a raw pointer value. This is
;; unsafe and will be fixed in the next section.
(setf (deref result) (sap-alien (int-sap (get-lisp-obj-address (parse source))) (* t)))
0)
(t (condition) (declare (ignore condition)) 1)))
(define-alien-callable calc-simplify int ((expr (* t)) (result (* (* t))))
(handler-case
(progn
;; The following needs to use SBCL internal functions to
;; coerce a raw pointer value into a Lisp object and
;; back. This is unsafe and will be fixed in the next
;; section.
(setf (deref result)
(sap-alien (int-sap (get-lisp-obj-address
(simplify (%make-lisp-obj (sap-int (alien-sap expr))))))
(* t)))
0)
(t (condition) (declare (ignore condition)) 1)))
(define-alien-callable calc-expression-to-string int ((expr (* t)) (result (* c-string)))
(handler-case
(progn
;; The following needs to use SBCL internal functions to coerce a raw
;; pointer value into a Lisp object. This is unsafe and
;; will be fixed in the next section.
(setf (deref result) (%make-lisp-obj (sap-int (alien-sap (expression-to-string result)))))
0)
(t (condition) (declare (ignore condition)) 1)))
Notice that for example purposes we just translate any exceptional
Lisp conditions into the C return error code convention. The actual
returned value is set through the second passing argument's
out-pointer in typical C fashion.
Now we've defined C callable function pointers associated with
names. You can get the underlying function pointers with the
function sb-alien:alien-callable-function
, which is useful for
passing these callable functions as callbacks to C. SBCL actually
didn't even have an external interface to callbacks before this,
and CFFI had been using an internal system interface which is now
superseded by this interface.
However, we're not too interested in callbacks right now. We want
to be able to call these functions from C! You can save the Lisp
image containing new callable function pointer definitions like so:
(sb-ext:save-lisp-and-die "calc.core"
:callable-exports '("calc_parse" "calc_simplify" "calc_expression_to_string" ...))
What the :callable-exports
argument describes is the set of C
symbols you want to initialize with the corresponding function
pointers created with define-alien-callable
. This image, when
started, has only two jobs: finish starting up the Lisp system and
initialize these callable exports with the proper function
pointers. It then immediately passes control back to C
Here's an example C file which uses these functions:
#include "libcalc.h"
int main () {
initialize_lisp("libcalc.core");
expr_type expr;
if (calc_parse(source, &expr) != ERR_SUCCESS)
die("unable to parse expression");
char *result;
expr_type simplified_expr;
calc_simplify(expr, &simplified_expr);
if (calc_expression_to_string(simplified_expr, &result) != ERR_SUCCESS)
die("unable to print expression to string");
printf("\n%s\n", result);
return 0;
}
Notice that there is a symbol, initialize_lisp
, that we can use
to initialize the Lisp runtime. Its arguments are the same as the
arguments you can normally pass to the main sbcl executable, so
once the core name is specified and the runtime initializes the
function pointers we are going to use, control is returned to the
program.
Once the Lisp runtime has been initialized, your function pointers
are ready to use, and calling them from C works just like a
callback would, with the same type translation machinery used.
That is very cool! Thank you for sharing. I've starting calling C code from SBCL and I'm looking forward to calling SBCL from C.
ReplyDelete