Is there a canonical/standard way in Idris to bind to C functions that take struct values as arguments?
Here is the most minimum example I could come up with.
It will fail to compile with the following error:
Can't find a value of type FTy FFI_C [] (TestStruct -> IO ())
Which makes sense as there is nothing in the FFI_C descriptor that tells Idris how to marshal arbitrary product types into c structs.
Is there a way to achieve the intend of the example without having to write c wrapper functions?
StructPass.idr
module Main
%include C "testlib.h"
%link C "testlib.o"
data TestStruct = TS Int Int
printTestStruct : TestStruct -> IO ()
printTestStruct ts =
foreign FFI_C "print_test_struct" (TestStruct -> IO ()) ts
main : IO ()
main = printTestStruct (TS 42 27)
testlib.h:
#include <stdint.h>
typedef struct {
uint64_t val1;
uint64_t val2;
} test_struct;
void print_test_struct(test_struct);
testlib.c
#include "testlib.h"
#include <stdio.h>
void print_test_struct(test_struct ts) {
printf("Content of test struct - val 1: %i; val 2: %i\n", ts.val1, ts.val2);
}
Makefile
DEFAULT: StructPass.idr testlib.o
idris StructPass.idr -o struct_pass.bin
testlib.o: testlib.c
gcc -c testlib.c
My current workaround
Based on doofin's question and the CFFI-Module I developed a workaround, but I'm wondering if there is a better way to do it. My workaround is to write wrapper functions in c which take pointers to struct values instead of concrete struct values. In those wrapper functions I simply dereferece those pointers and call the original function with those values. On the Idris side I use the CFFI-Module to allocate and fill the argument struct in memory and then hand a pointer to this memory over to my c wrapper function via a foreign call.
For the minimal example above this would look something like the following:
StructPass.idr
module Main
import CFFI
%include C "testlib.h"
%link C "testlib.o"
data TestStruct = TS Int Int
testStructToMemory : TestStruct -> IO Ptr
testStructToMemory (TS val1 val2) =
do mem <- alloc (STRUCT [I64,I64])
poke I64 (field (STRUCT [I64,I64]) 0 mem) (prim__zextInt_B64 val1)
poke I64 (field (STRUCT [I64,I64]) 1 mem) (prim__zextInt_B64 val2)
pure $ mem
printTestStruct : TestStruct -> IO ()
printTestStruct ts =
foreign FFI_C "w_print_test_struct" (Ptr -> IO ()) !(testStructToMemory ts)
main : IO ()
main = printTestStruct (TS 42 27)
testlib.h
#include <stdint.h>
typedef struct {
uint64_t val1;
uint64_t val2;
} test_struct;
void print_test_struct(test_struct);
void w_print_test_struct(test_struct*);
testlib.c
#include "testlib.h"
#include <stdio.h>
void print_test_struct(test_struct ts) {
printf("Content of test struct - val 1: %i; val 2: %i\n", ts.val1, ts.val2);
}
void w_print_test_struct(test_struct* ts_p) {
print_test_struct(*ts_p);
}
Makefile
DEFAULT: StructPass.idr testlib.o
idris --package contrib StructPass.idr -o struct_pass.bin
testlib.o: testlib.c
gcc -c testlib.c