1

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
Renbüh
  • 11
  • 1

0 Answers0