Search code examples
rakurakudonativecall

How to use CStruct


I'm trying to return struct from shared library written in C. This is simple code, for testing of returning structure and simple int32, libstruct.c, compiled by gcc -shared -Wl,-soname,libstruct.so.1 -o libstruct.so.1 libstruct.c:

#include <stdint.h>

int32_t newint(int32_t arg) {
    return arg;
}

struct MyStruct {
    int32_t member;
};
struct MyStruct newstruct(int32_t arg) {
    struct MyStruct myStruct;
    myStruct.member = arg;
    return(myStruct);
}

I can use this library with simple C program, usestruct.c, compiled by gcc -o usestruct usestruct.c ./libstruct.so.1:

#include <stdio.h>
#include <stdint.h>

struct MyStruct {
    int32_t member;
};
extern struct MyStruct newstruct(int32_t);
extern int32_t newint(int32_t);

int main() {
    printf("%d\n", newint(42));
    struct MyStruct myStruct;
    myStruct = newstruct(42);
    printf("%d\n", myStruct.member);
    return 0;
}

I can launch it with LD_LIBRARY_PATH=./ ./usestruct, and it works correctly, prints two values. Now, let's to write analogous program in raku, usestruct.raku:

#!/bin/env raku
use NativeCall;

sub newint(int32) returns int32 is native('./libstruct.so.1') { * }
say newint(42);

class MyStruct is repr('CStruct') {
    has int32 $.member;
}
sub newstruct(int32) returns MyStruct is native('./libstruct.so.1') { * }
say newstruct(42).member;

This prints first 42, but then terminates with segmentation fault.

In C this example works, but I'm not expert in C, maybe I forgot something, some compile options? Or is this a bug of rakudo?


Solution

  • NativeCall interface requires that transaction of C structs be made with pointers:

    CStruct objects are passed to native functions by reference and native functions must also return CStruct objects by reference.

    Your C function, however, returns a new struct by value. Then, i guess, this is tried to be interpreted as a memory address as it expects a pointer, and tries to read/write from wild memory areas, hence the segfault.

    You can pointerize your function as:

    struct MyStruct* newstruct(int32_t val) {
        /* dynamically allocating now */
        struct MyStruct *stru = malloc(sizeof *stru);
        stru->member = val;
        return stru;
    }
    

    with #include <stdlib.h> at the very top for malloc. Raku program is essentially the same modulo some aesthetics:

    # prog.raku
    use NativeCall;
    
    my constant LIB = "./libstruct.so";
    
    class MyStruct is repr("CStruct") {
        has int32 $.member;
    }
    
    # C bridge
    sub newint(int32) returns int32 is native(LIB) { * }
    sub newstruct(int32) returns MyStruct is native(LIB) { * }
    
    say newint(42);
    
    my $s := newstruct(84);
    say $s;
    say $s.member;
    

    We build the lib & run the Raku program to get

    $ gcc -Wall -Wextra -pedantic -shared -o libstruct.so -fPIC mod_struct.c
    $ raku prog.raku
    42
    MyStruct.new(member => 84)
    84
    

    (took the liberty to rename C file to "mod_struct.c")

    Seems good. But there's an issue: now that a dynamic allocation was made, responsibility to deliver it back arises. And we need to do it ourselves with a C-bridged freer:

    When a CStruct-based type is used as the return type of a native function, the memory is not managed for you by the GC.

    So

    /* addendum to mod_struct.c */
    void free_struct(struct MyStruct* s) {
        free(s);
    }
    

    Noting that, since the struct itself didn't have dynamic allocations on its members (as it only has an integer), we didn't do further freeing.

    Now the Raku program needs to be aware of this, and use it:

    # prog.raku
    use NativeCall;
    
    my constant LIB = "./libstruct.so";
    
    class MyStruct is repr("CStruct") {
        has int32 $.member;
    }
    
    # C bridge
    sub newint(int32) returns int32 is native(LIB) { * }
    sub newstruct(int32) returns MyStruct is native(LIB) { * }
    sub free_struct(MyStruct) is native(LIB) { * };   # <-- new!
    
    say newint(42);
    
    my $s := newstruct(84);
    say $s;
    say $s.member;
    
    # ... after some time
    free_struct($s);
    say "successfully freed struct";
    

    and the output follows as

    42
    MyStruct.new(member => 84)
    84
    successfully freed struct
    

    Manually keeping track of MyStruct objects to remember freeing them after some time might be cumbersome; that would be writing C! In the Raku level, we already have a class representing the struct; then we can add a DESTROY submethod to it that frees itself whenever garbage collector deems necessary:

    class MyStruct is repr("CStruct") {
        has int32 $.member;
    
        submethod DESTROY {
            free_struct(self);
        }
    }
    

    With this addition, no manual calls to free_struct is needed (in fact, better not because it might lead double freeing which is undefined behaviour on C level).


    P.S. your main C file might be revisioned, e.g., a header file seems in order but that's out of scope or that was only a demonstrative example who knows. In either case, thanks for providing an MRE and welcome to the website.