6

I have this C code:

typedef struct {
  double dat[2];
} gsl_complex;

gsl_complex gsl_poly_complex_eval(const double c[], const int len, const gsl_complex z);

The C function returns a whole struct, not just a pointer, so I cannot write the Raku declaration as:

sub gsl_poly_complex_eval(CArray[num64] $c, int32 $len, gsl_complex $z --> gsl_complex)
  is native(LIB) is export { * }

Any suggestion?

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
Fernando Santagata
  • 1,487
  • 1
  • 8
  • 15
  • Did you try treating the return value as a CArray of doubles? Or another buffer of suitable size. Nativecall doesn't really care about the typing on the C-side (I think). It trusts you and simply pours bytes from c into what you tell it to on the raku side. – Holli Nov 27 '19 at 17:30
  • @Holli no, it doesn't work: the CArray is returned by reference, not by value. I tried it nonetheless, but valgrind shows that reading the returned values results into two "Invalid read of size 8". – Fernando Santagata Nov 28 '19 at 09:28

2 Answers2

5

For that you need a CStruct. The P5localtime module contains a more elaborate example.

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • 1
    That's what I did. The difference is that in `/usr/include/time.h` I see that localtime is defined as `extern struct tm *localtime (const time_t *__timer) __THROW;`: it returns a pointer to a struct. In my case gsl_poly_complex_eval returns a struct, not a pointer. – Fernando Santagata Nov 26 '19 at 10:00
  • Aha, hmmm... Perhaps https://docs.raku.org/language/nativecall#Buffers_and_blobs could be of help then. Or write a C helper function that *would* return a pointer? – Elizabeth Mattijsen Nov 26 '19 at 10:06
  • 1
    I tried to return a Blob, but it caused moarvm to crash; I guess the VM doesn't know how many bytes needs to read. A C helper function should store the returned struct to let its Raku counterpart to read that memory area; I fear that would make it thread-unsafe. – Fernando Santagata Nov 26 '19 at 10:19
  • Then maybe it's time for a Rakudo / MoarVM issue. – Elizabeth Mattijsen Nov 26 '19 at 10:27
  • @FernandoSantagata I'm not able to help but am curious if you resolved this problem, filed a Rakudo or MoarVM issue, made progress some other way, and/or are still waiting for a way forward. If you could provide an update comment that would feel to me like means just defying loose ends, which I'd appreciate. – raiph Dec 18 '19 at 13:51
  • 1
    @raiph I filed a Rakudo issue. I have thought about a solution to *this* particular problem (I need to write a little C function which returns a 16-byte long double, composing the two double values by bit-shifting), but I haven't had the time to test that solution so far, because I'm working on an interface to the huge libgsl library (and Raku already has a Complex data type). – Fernando Santagata Dec 18 '19 at 16:04
2

The problem

Some C APIs work with structs using a three-phase approach, passing around structs by reference, like this:

struct mystruct *init_mystruct(arguments, ...);
double compute(struct mystruct *);
void clean_mystruct(struct mystruct *);

This way the implementation hides the data structure, but this comes with a price: the final users have to keep track of their pointers and remember to clean up after themselves, or the program will leak memory.
Another approach is the one that the library I was interfacing used: return the data on the stack, so it can be assigned to an auto variable and automatically discarded when it goes out of scope.
In this case the API is modeled as a two-phase operation:

struct mystruct init_mystruct(arguments, ...);
double compute(struct mystruct);

The structure is passed on the stack, by value and there's no need to clean up afterwards.
But Raku's NativeCall interface is only able to use C structs passing them by reference, hence the problem.

The solution

This is not a clean solution, because it steps back into the first approach described, the three-phase one, but it's the only one I have been able to devise so far.
Here I consider two C functions from the library's API: the first creates a complex number as a struct, the second adds up two numbers.
First I wrote a tiny C code interface, the file complex.c:

#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <stdlib.h>

gsl_complex *alloc_gsl_complex(void)
{
  gsl_complex *c = malloc(sizeof(gsl_complex));
  return c;
}

void free_gsl_complex(gsl_complex *c)
{
  free(c);
}

void mgsl_complex_rect(double x, double y, gsl_complex *res)
{
  gsl_complex ret = gsl_complex_rect(x, y);
  *res = ret;
}

void mgsl_complex_add(gsl_complex *a, gsl_complex *b, gsl_complex *res)
{
  *res = gsl_complex_add(*a, *b);
}

I compiled it this way:
gcc -c -fPIC complex.c
gcc -shared -o libcomplex.so complex.o -lgsl
Note the final -lgsl used to link the libgsl C library I am interfacing to.
Then I wrote the Raku low-level interface:

#!/usr/bin/env raku

use NativeCall;

constant LIB  = ('/mydir/libcomplex.so');

class gsl_complex is repr('CStruct') {
  HAS num64 @.dat[2] is CArray;
}

sub mgsl_complex_rect(num64 $x, num64 $y, gsl_complex $c) is native(LIB) { * }
sub mgsl_complex_add(gsl_complex $a, gsl_complex $b, gsl_complex $res) is native(LIB) { * }
sub alloc_gsl_complex(--> gsl_complex) is native(LIB) { * }
sub free_gsl_complex(gsl_complex $c) is native(LIB) { * }

my gsl_complex $c1 = alloc_gsl_complex;
mgsl_complex_rect(1e0, 2e0, $c1);
say "{$c1.dat[0], $c1.dat[1]}";          # output: 1 2
my gsl_complex $c2 = alloc_gsl_complex;
mgsl_complex_rect(1e0, 2e0, $c2);
say "{$c2.dat[0], $c2.dat[1]}";          # output: 1 2
my gsl_complex $res = alloc_gsl_complex;
mgsl_complex_add($c1, $c2, $res);
say "{$res.dat[0], $res.dat[1]}";        # output: 2 4
free_gsl_complex($c1);
free_gsl_complex($c2);
free_gsl_complex($res);

Note that I had to free explicitly the three data structures I created, spoiling the original C API careful design.

Fernando Santagata
  • 1,487
  • 1
  • 8
  • 15
  • 1
    Tagging @raiph since you asked. – Fernando Santagata Dec 22 '19 at 12:46
  • I think the tagging only works for those considered by SO to be directly involved in a particular post -- a Q, or a particular A. For this A that means only you. So while SO had let me know about your comment on Liz's answer saying you'd filed an issue (so was able to upvote that to express appreciation) I hadn't seen this answer until now when I just bumped into it. So, much love for posting this answer in addition to filing the issue. And happy new year. :) – raiph Jan 03 '21 at 01:51