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