Question

I'm in the process of wrapping my graphics engine API with Gambit-C and have been successful so far with the FFI. Today I ran into a new problem that I can't easily get past.

I have a structure like this in C:

typedef struct render_list_rec
{
    long render_id;
    render_node* node;
    struct render_list_rec* next; 
} render_list;

In C, I also have a series of functions that get defined by macros to add common list behaviors. Then end up looking something like this:

void render_list_item_add(render_list_item **list, render_list_item* elem);

In C, you can have a render_list_item* that is NULL, but can pass it to the first parameter of this function and it will essentially create the head of the list for you.

My problem is that I cannot get this behavior to work in Gambit-C's FFI. I end up creating something like this:

(c-define-type render-list* (pointer (struct "render_list_rec")))
(c-define-type render-list** (pointer (pointer (struct "render_list_rec"))))
(define render-list-add-item (c-lambda (render-list** long render-node*) render-list* "render_list_add_item"))

When I run this it segfaults. Upon investigation, ___arg1 of the render-list-add-item proceedure is NULL. No matter what I try, I cannot get a valid (pointer (pointer)) in the FFI.

Is there something I'm missing with this?

============================================================

A complete scheme example:

(c-declare #<<c-decl-end
#include <stdio.h>
#include <stdlib.h>

typedef struct test_rec
{
    int i;
} test_rec;

void pointer_test(struct test_rec** in_number)
{
  if (in_number == NULL) {
    fprintf(stdout, "pointer_test is NULL\n");
  }
}

test_rec* new_pointer_test(void)
{
return malloc(sizeof(struct test_rec));
}

c-decl-end
)

(c-define-type test-rec* (pointer (struct "test_rec")))
(define c-pointer-test (c-lambda ((pointer test-rec*)) void "pointer_test"))
(define c-new-pointer-test (c-lambda () test-rec* "new_pointer_test"))
(define test-rec->i-set! (c-lambda (test-rec* int) void "___arg1->i = ___arg2;"))

(display "About to run test with #f ...") (newline)
(define the_false #f)
(c-pointer-test the_false)

(display "About to run test with 1 ...") (newline)
(define number_one (c-new-pointer-test))
(test-rec->i-set! number_one 1)
(c-pointer-test number_one)

Compile with:

gsc -o test -exe  test.scm

Gives output:

About to run test with #f ...
pointer_test is NULL
About to run test with 1 ...
*** ERROR IN ##execute-program -- (Argument 1) Can't convert to C pointer
(c-pointer-test '#<|struct test_rec*| #2 0x28d3fc0>)

============================================================

EDIT:

Felix: Can you give some examples of how you are invoking render-list-add-item

The C code for this looks something like this:

pg_render_list *ui_render_list = NULL;
pg_render_node *ui_node = pg_font_generate_text_string(app_font, L"Lacunarity:", ui_text_material);
pg_render_list_create_item(&ui_render_list, UI_ID_TEXT, ui_node);

Its a list implementation based off of sglib. When these passed a pointer that points to a null pointer,as above, it creates a new list item as the head of the list so that *ui_render_list will point to it.

The scheme code looked something like this (typed from memory):

(define ui-render-list #f)
(letrec ((model-data (pg-model-data-read-binary model-filepath))
          (model-node (pg-render-node-create-fom-model model-data GL_STATIC_DRAW)))
  (pg-render-list-item-add ui-render-list model-data))

The hope was to have similar behavior. It appears from looking at the documentation that having a #f in the C API tranlates to NULL, but I thought the (pointer (pointer)) might catch that. Even passing variables that are bound to something always led to a NULL value. I tested this by creating a function in a (c-declare) that simply printed the address of the pointer:

If you want to see my full wrappers in action, you can look here at this commit

==========================================

The question of how to get (pointer (pointer)) working still stands. But I think that for quicker results, and better interoperability with other languages, I'm going to rewrite my C list macros to define a list structure that will then contain pointers to the list head/tail as seen in "Mastering Algorithms with C". That way pointer to pointers won't be necessary.

Was it helpful?

Solution

Maybe I misunderstand, but when you have

(define ui-render-list #f)

then I would think that the expression:

(pg-render-list-item-add ui-render-list model-data)

would behave like:

"invoke pg-render-list-item-add with the actual arguments #f and whatever model-data denotes.

And then the Gambit-C FFI is translating the Scheme value #f to the C value NULL (i.e. 0) when it crosses the boundary from Scheme into C.

This is very different from:

"invoke pg-render-list-item-add with an address <addr> and whatever model-data denotes" where <addr> meant to be the receiver of the item created by pg-render-list-item-add.

The C statement:

pg_render_list_create_item(&ui_render_list, UI_ID_TEXT, ui_node);

is taking the address of the ui_render_list variable (which, for example, may be allocated on the stack), and passing that address in to pg_render_list_create_item. This is very different from passing in the value NULL as the first argument to pg_render_list_create_item, which would look like this:

pg_render_list_create_item(ui_render_list, UI_ID_TEXT, ui_node);

Note the absence of the ampersand &; it is a crucial distinction here.

I have not taken the time yet to try to write your example myself in Gambit-C, but I imagine one way you could accomplish the effect you desire would be to allocate the receiving memory yourself (by hooking into the malloc function in the FFI), and then once you have allocated the memory, you will then have an address that you can pass as the first parameter to pg-render-list-item.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top