Re: Calling Lisp from C (SBCL as a shared object)
Christopher Wicklein <[email protected]> Sat, 18 Jan 2025 15:21:17 -0600
| Newsgroups | gmane.lisp.steel-bank.general |
|---|---|
| Message-ID | <CA+dNuZkA=iG106LgUR=WXV=FdNOpihH33AMjPqr=REtiRYKCag@mail.gmail.com> |
J.,
I also had a bit of trouble getting a C program to call a function exported
by Lisp. I ultimately wrote this example for myself (based on references
cited in README.md) of a Lisp function which is called from C to sum two
signed integers and return the result by pointer (the sum function's return
value is used to indicate success or failure.). I primarily work on macOS
with SBCL 2.5.0, but I've tested this example on Debian 12.9 with SBCL
2.5.0 built from source (sh make.sh ; make -C src/runtime libsbcl.so), and
it seems to work fine on that system, too.
me@debian:~/common-lisp-shared-library$ *export LD_LIBRARY_PATH=.*
me@debian:~/common-lisp-shared-library$ *make*
gcc -c -o example.o example.c
gcc -c -o libsum.o libsum.c
/home/me/sbcl-2.5.0/run-sbcl.sh --noinform --script sum.lisp
cp -a /home/me/sbcl-2.5.0/src/runtime/libsbcl.so .
gcc -shared -o libsum.so libsum.o -L. -lsbcl
gcc -o example example.o -L. -lsum
me@debian:~/common-lisp-shared-library$ *./example*
The sum of 2 and 3 is 5.
me@debian:~/common-lisp-shared-library$
me@debian:~/common-lisp-shared-library$ *for x in *; do printf
"################\n# %s\n################\n\n" "$x"; cat "$x"; printf "\n";
done*
################
# example.c
################
#include <stdio.h>
#include "libsum.h"
int
main(int argc, char **argv) {
int a = 2, b = 3, result;
if (init_example() != 0) {
fprintf(stderr, "Failed to initialize Lisp!\n");
return 1;
}
/* The C compiler enforces the declaration of sum in libsum.h, but
that declaration must be correct, i.e. comport with the callable
produced by Lisp. In writing this example, I had trouble with
SBCL's signed apparently not being the same as int and the
variables a and/or b being corrupted on the stack. */
if (sum(a, b, &result) != 0) {
fprintf(stderr, "The sum function failed!\n");
return 1;
}
printf("The sum of %d and %d is %d.\n", a, b, result);
return 0;
}
################
# libsum.c
################
#include "libsum.h"
/* Declare the function exported by SBCL (built as a shared library)
to initialize the Lisp runtime. */
extern int initialize_lisp(int argc, char **argv);
/* Defined here, this function pointer is initialized by calling
initialize_lisp. */
int (*sum)(int, int, int *);
/* Initialize the Lisp runtime by calling initialize_lisp which takes
the same arguments as the SBCL executable (argc, argv, and
optionally, envp.) The first element of argv (i.e. argv[0]) is an
empty string. The argument --core is provided to specify a
non-default core to use, and --noinform is provided to suppress
printing a banner or other informational messages at start up. */
int
init_example() {
static int once = 0;
char *init_args[] = {"", "--core", "libsum.core", "--noinform"};
if (once) {
return 1;
}
if (initialize_lisp(4, init_args) != 0) {
return -1;
}
once = 1;
return 0;
}
################
# libsum.h
################
#ifndef _libsum_h
#define _libsum_h
/* Declare a pointer to our function which adds signed integers. */
extern int (*sum)(int, int, int *);
/* Declare our function which calls initialize_lisp. */
extern int init_example();
#endif
################
# Makefile
################
.PHONY : all
all : example
SBCL_HOME := $(HOME)/sbcl-2.5.0
%.o : %.c
gcc -c -o $@ $<
libsbcl.so : $(SBCL_HOME)/src/runtime/libsbcl.so
cp -a $(SBCL_HOME)/src/runtime/libsbcl.so .
libsum.core : sum.lisp
$(SBCL_HOME)/run-sbcl.sh --noinform --script sum.lisp
libsum.o : libsum.c
libsum.so : libsum.o libsum.core libsbcl.so
gcc -shared -o libsum.so libsum.o -L. -lsbcl
example.o : example.c
example : example.o libsum.so
gcc -o example example.o -L. -lsum
.PHONY : clean
clean :
rm -f *.o *.core *.so example *~
################
# README.md
################
# common-lisp-shared-library
This is a trivial example of a shared library written in Common Lisp (SBCL.)
References include [this blog post](
https://mstmetent.blogspot.com/2022/04/using-lisp-libraries-from-other.html),
[40ants/sbcl-shared-library-examples](
https://github.com/40ants/sbcl-shared-library-examples), the delivery tool
[quil-lang/sbcl-librarian](https://github.com/quil-lang/sbcl-librarian),
and the SBCL user manual.
A consideration missing from this example is coordinating the effects of
garbage collection by SBCL on objects which cross the foreign function
boundary.
################
# sum.lisp
################
(define-alien-callable sum int ((a int) (b int) (result (* int)))
"Sum the (integer) arguments A and B, store the result in
*RESULT, and return 0 on success, 1 on any condition."
(handler-case
(progn
(setf (deref result) (+ a b))
0)
(t (condition) (declare (ignore condition)) 1)))
;; Save the Lisp core. A C program can initialize the Lisp runtime
;; and the symbol sum by calling initialize_lisp which is exported by
;; libsbcl.so.
(save-lisp-and-die "libsum.core" :callable-exports '(sum))
me@debian:~/common-lisp-shared-library$
On Sat, Jan 18, 2025 at 7:28 AM J. Gareth Williams <[email protected]>
wrote:
> Hi Robert,
>
> Appreciate the reply and suggestion! -- but I did try that -- per the
> example in the Cookbook -- and I had pretty much the same result. Same
> with the example at https://github.com/svetlyak40wt/sbcl-lib.
>
> As soon as I have a non-NIL argument for :callable-exports in the
> SAVE-LISP-AND-DIE call, it fails to initialize. I've tried with different
> call signatures and return values to no avail.
>
> There was a message in sbcl-bugs this September that raised the same issue:
>
> https://sourceforge.net/p/sbcl/mailman/message/58822085/
>
> Can anyone confirm they're using this successfully on recent SBCL on
> Linux? Ideally recent Debian?
>
> Thanks again, Robert.
>
> Gareth
>
>
> On Sat, Jan 18, 2025, 4:34 a.m. Robert Smith <[email protected]> wrote:
>
>> Try SBCL-LIBRARIAN:
>>
>> https://github.com/quil-lang/sbcl-librarian
>>
>> https://lispcookbook.github.io/cl-cookbook/dynamic-libraries.html
>>
>> Robert
>>
>>
>>
>> On Fri, Jan 17, 2025 at 21:02 J. Gareth Williams <[email protected]>
>> wrote:
>>
>>> Hi,
>>>
>>> I have been struggling to get even a trivial example of
>>> DEFINE-ALIEN-CALLABLE to work, and was wondering if anyone could take a
>>> look at what I might be doing wrong.
>>>
>>> Full source:
>>> git clone
>>> https://gitlab.com/garethw14/simple-sbcl-alien-callable.git
>>>
>>> It compiles okay and links, but on initialize_lisp(), I land in the
>>> debugger:
>>>
>>> ---
>>> In main, about to initialize_lisp()
>>>
>>> debugger invoked on a SB-KERNEL::UNDEFINED-ALIEN-VARIABLE-ERROR in thread
>>> #<THREAD "main thread" RUNNING {10013A8003}>:
>>>
>>> * Attempt to access an undefined alien variable.*
>>> Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
>>>
>>> restarts (invokable by number or by possibly-abbreviated name):
>>> 0: [ABORT] Exit from the current thread.
>>>
>>> ("foreign function: call_into_lisp_")
>>> 0]
>>> ---
>>>
>>> I've tried some other examples I've found around the web, but they seem
>>> to yield the same result.
>>>
>>> Tested on Debian 12.9, SBCL versions 2.5.0 and 2.3.2
>>>
>>> Would appreciate any insight!
>>>
>>> gareth
>>>
>>>
>>>
>>> _______________________________________________
>>> Sbcl-help mailing list
>>> [email protected]
>>> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>>>
>> _______________________________________________
> Sbcl-help mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>
--
Christopher Wicklein <[email protected]>
_______________________________________________
Sbcl-help mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sbcl-help