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?
NativeCall interface requires that transaction of C structs be made with pointers:
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:
with
#include <stdlib.h>
at the very top formalloc
. Raku program is essentially the same modulo some aesthetics:We build the lib & run the Raku program to get
(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:
So
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:
and the output follows as
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: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.