Search code examples
cscriptingschemeguile

Guile Scheme - How to pass C struct value back to Guile for scripting


I'm writing a program/game where I use Guile for scripting on top of a C program. For example I have a command struct type(which is also used for move_to) in C and corresponding move_to wrapper function to to create move_to commands in Guile. In the UI an input line can be entered and is then executed in Guile.

For move_to it look like this in C: Some entity id is passed here as a SCM value, same for the new the position and SCM value is passed for x and the same for y.

    static SCM move_to(SCM scm_id, SCM x, SCM y) {

        unsigned int id = scm_to_int(scm_id);
        float new_x = scm_to_double(x);
        float new_y = scm_to_double(y);

        // Search for entity 
       int index = -1;
       for (int i = 0; i < gs.entities.size(); i++) {

           if (get_id(&(gs.entities[i])) == id) { index = i; break; }
       }

      command c = {new_x,new_y,MOVETO,0};

      // Add move command to entity if one was found
      if (index != -1) { printf("found index\n");add_command(&(gs.entities[index]), c); }

      return SCM_UNSPECIFIED;
    }

In this code the entity is searched in some entity list and if it exist, we create a new move_to_command and add it to the command list of the entity.

In the UI then e.g. (move_to 0 100 100) can be entered and is then executed.

It's straightforward when creating a C function and wrapper when the function just returns an integer or nothing at all. But I'm not sure how to implement a function which returns a (C-/Guile-) struct value to Guile.

Let's say we have this struct type in C:

typedef struct {
  unsigned int id;
  event_type type;
  unsigned int entity_id;
  unsigned int by_entity;
} event;

How would I go to create a Guile struct value from this C struct which is returned when when for example some Guile function - like 'get-next-event'(which I have to write) is called. It would now also be OK if I had to copy the value from the C struct to a new Guile struct over.

Saw that there is the function scm_make_struct(SCM vtable, SCM tail_size, SCM init_list), but I am not sure how to create a vtable value which to pass here as a first argument.

I think older guile versions there was scm_make_vtable_vtable. Also I tried the C-function scm_struct_vtable from the current version.

ADDENDUM:

  • found a function to create a vtable in the file struct.h, which is not documented: SCM_API SCM scm_make_vtable (SCM fields, SCM printer);

Source Code Example:

#include "libguile.h"

static SCM give_me_100(void) {

  return scm_from_int(100);
}

static SCM give_me_a_struct(void) {

  SCM fields = scm_from_locale_string("pwpw");
  SCM vtable = scm_make_vtable(fields,0);

  SCM mystruct = scm_c_make_struct(vtable,0, 10, 20,  SCM_UNDEFINED);                                                                                             //

  return mystruct;

}

static void inner_main(void *closure, int argc, char **argv) {

    /* preparation - make C functions callable from Guile */
    scm_c_define_gsubr("give-me-100", 0, 0, 0, &give_me_100);
    scm_c_define_gsubr("give-me-a-struct", 0, 0, 0, &give_me_a_struct);

    scm_shell(argc, argv);
    /* after exit */
}

int main(int argc, char **argv) {

    scm_boot_guile(argc, argv, inner_main, 0);
    return 0; /* never reached, see inner_main */
}

Which can be compiled with this line for example: gcc -o main -I/usr/include/guile/3.0 -lguile-3.0 main.c

Example output:

GNU Guile 3.0.8
Copyright (C) 1995-2021 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (give-me-100)
$1 = 100
scheme@(guile-user)> (give-me-a-struct)
$2 = zsh: segmentation fault (core dumped)  ./main

Thank you for any help and suggestions, Rael


Solution

  • To use and export C structs to Guile, one could do it like this - the example code is from here: https://github.com/agentlans/guile-foreign-example/blob/master/bessel.c

    #include <math.h>
    #include <libguile.h>
    
    // Returns value of j0 Bessel function. This will be called from Guile Scheme.
    SCM
    j0_wrapper (SCM x)
    {
        return scm_from_double (j0 (scm_to_double (x)));
    }
    
    // A C struct that can hold any Guile Scheme object
    struct foo
    {
        SCM x;
    };
    
    // Represents the foo type in Scheme
    static SCM foo_type;
    
    // Declarations needed for foo structs to be usable in Scheme
    void init_foo_type()
    {
        SCM name = scm_from_utf8_symbol("foo");
        SCM slots = scm_list_1(scm_from_utf8_symbol("x"));
        foo_type = scm_make_foreign_object_type(name, slots, NULL); // NULL finalizer
    }
    
    // Creates a new foo struct containing the given Scheme object
    SCM make_foo(SCM obj)
    {
        struct foo *foo_obj = scm_gc_malloc(sizeof(struct foo), "foo");
        foo_obj->x = obj;
        return scm_make_foreign_object_1(foo_type, foo_obj);
    }
    
    // Returns the Scheme object inside the foo struct
    SCM get_foo(SCM foo_obj)
    {
        scm_assert_foreign_object_type(foo_type, foo_obj);
        return ((struct foo *) scm_foreign_object_ref(foo_obj, 0))->x;
    }
    
    // Initializes the functions exposed in this Guile extension
    void
    init_bessel ()
    {
        scm_c_define_gsubr ("j0", 1, 0, 0, j0_wrapper);
    
        init_foo_type();
        scm_c_define_gsubr("make-foo", 1, 0, 0, make_foo);
        scm_c_define_gsubr("get-foo", 1, 0, 0, get_foo);
    }