ffichibi-scheme

Get struct* from out parameter in Chibi Scheme FFI bindings


Can you get a struct * from the out parameter of a C function in Chibi Scheme?

I'm trying to get a struct archive_entry * from this C function:

int archive_read_next_header(
    struct archive *archive,
    struct archive_entry **out_entry);

In C one would do it like this:

struct archive_entry *entry;
archive_read_next_header(archive, &entry);

My Chibi FFI code is:

(define-c-struct archive)

(define-c-struct archive_entry)

(define-c int
          archive-read-next-header
          (archive (result reference archive_entry)))

But it's not generating the right C code to get the archive_entry. I think reference is the wrong thing to use. I also tried pointer but it didn't work either.


Solution

  • I still don't know if it can be done directly.

    But I was able to work around the problem by writing a custom thunk function in C:

    (c-declare "
    struct archive_entry *my_archive_read(struct archive *a, int *out_errcode) {
        struct archive_entry *entry;
        int errcode;
    
        *out_errcode = errcode = archive_read_next_header(a, &entry);
        return (errcode == ARCHIVE_OK) ? entry : NULL;
    }")
    
    (define-c archive_entry my-archive-read (archive (result int)))
    

    So the key is that Scheme doesn't need to deal with any double-indirection (**) in this version. The C code converts double-indirection into single-indirection for Scheme so it all works out.

    Example usage from a Scheme program:

    (let ((archive (my-archive-open filename)))
      (disp "archive" archive)
      (let* ((return-values (my-archive-read archive))
             (entry (car return-values))
             (errcode (cadr return-values)))
        (display entry)
        (newline)))
    

    I copied the technique from the chibi-sqlite3 bindings where they face a similar problem having to get a sqlite3_stmt * from an out parameter:

    (c-declare
     "sqlite3_stmt* sqlite3_prepare_return(sqlite3* db, const char* sql, const int len) {
        sqlite3_stmt* stmt;
        char** err;
        return sqlite3_prepare_v2(db, sql, len, &stmt, NULL) != SQLITE_OK ? NULL : stmt;
      }
    ")
    
    (define-c sqlite3_stmt (sqlite3-prepare "sqlite3_prepare_return") (sqlite3 string (value (string-length arg1) int)))