0

It seems that a function returning a double data type, from libffi, is not correctly casted when the function returns its value, here is the code I used:

#include <stdio.h>
#include <stdlib.h>
#include <ffi.h>
#include <math.h>  // The make call the function `cos` by the FFI.

int main()
{
  ffi_cif cif;
  ffi_type *args[1];
  void *values[1];
  ffi_arg rc;

  args[0] = &ffi_type_double;

  void *ptr = malloc(sizeof(double));
  *((double *)ptr) = 3.0;
  values[0] = ptr;

  if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_double, args) == FFI_OK)
      ffi_call(&cif, cos, &rc, values);

  printf("%f\n", (double)rc);

  return 0;
}

The result is as follows: 13830464316077631000.000000.

The ffi_arg type is an alias for unsigned long long. The documentation states that the argument of type ffi_arg passed to the ffi_call function, is used to store data if it is smaller than sizeof(ffi_arg), otherwise it is a pointer to the data in memory:

rvalue must point to storage that is sizeof(ffi_arg) or larger for non-floating point types. For smaller-sized return value types, the ffi_arg or ffi_sarg integral type must be used to hold the return value.

(https://manpages.debian.org/testing/libffi-dev/ffi_call.3.en.html)

So I tried it with a pointer dereference:

void* rc;
// The code to call the function and initialize the CIF...
double my_value = *(double *)((void *)rc);

Which crashed the program.

How am I supposed to access the value stored using the rc variable?

EDIT 1

The command line used to compile the program:

gcc source.c -lffi -o source

There are no errors or warnings at compile time.

EDIT 2

After adding the `-Wall' build option, I get:

warning: passing argument 2 of 'ffi_call' from incompatible pointer type [-Wincompatible-pointer-types]
ffi_call(&cif, cos, &rc, values);
               ^~~

This warning seems to be ignored in the libffi example. The example given works very well for me (with this warning).

Foxy
  • 980
  • 8
  • 17

2 Answers2

3

The problem is simple. The ffi_arg is not the type you're supposed to put your return value to. Instead, the 3rd argument is defined as a pointer to void, that should point to an object that is suitably large to contain the return value, and of proper alignment, and of proper type, or interpreted as such, thus:

double rv;
...
ffi_call(&cif, cos, &rv, values);

or

void *rv = malloc(sizeof (double));
...
ffi_call(&cif, cos, rv, values);
double value = *(double *)rv;

As for the warning, that happens because the libffi code is not strictly portable. A function pointer cannot be automatically converted to a void *. You can silence the warning with an explicit cast:

ffi_call(&cif, (void *)cos, rv, values);

At least it does not make it any more wrong than it already is.

1

EDIT - According to the documentation

FFI - rvalue must point to storage that is sizeof(ffi_arg) or larger for non-floating point types. For smaller-sized return value types, the ffi_arg or ffi_sarg integral type must be used to hold the return value.

The function definition for ffi_call is

void ffi_call(ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue);

Instead of passing a ffi_arg to the function you need to pass a void* which is allocated space of a double.

void* rc = malloc(sizeof(double));
ffi_call(&cif, cos, rc, values);
printf("%f\n", *(double *)rc);

This will lead to a correct answer without you making any assumption about the function being called.

naivecoder
  • 106
  • 4
  • This is a typing error for the file inclusion that I inserted by mistake when I copied my code, I corrected it in my question, however, as you point out, the error remains. – Foxy May 03 '20 at 16:50
  • I use the version `9.3.0` of the MSYS2 gcc compiler on MinGW. – Foxy May 03 '20 at 17:01
  • `ffi_arg` being a type alias for `unsigned long long`, a dynamic allocation does not seem appropriate. However, this does not affect the result. Moreover libffi allocates and assigns space and value itself. – Foxy May 03 '20 at 17:09
  • Can you point me to the documentation which says that ffi_arg is an unsigned long long, because I can't seem to find that, it seems to me that it should only be used for integer types as indicated by "rvalue must point to storage that is sizeof(ffi_arg) or larger for non-floating point types. For smaller-sized return value types, the ffi_arg or ffi_sarg integral type must be used to hold the return value." – naivecoder May 03 '20 at 17:15
  • I misinterpreted your question, since you don't want to assume anything about the return value of the function you can't do ```double rc;``` but what you can do is ```void* rc = malloc(sizeof(double)); ``` ```ffi_call(&cif, cos, rc, values);``` ```printf("%f\n", *(double *)rc);``` according to the function signature and documentation you need to pass a pointer to a storage which can hold the value of the return double. I got the answer to be -0.989992 with these changes. – naivecoder May 03 '20 at 17:21
  • 1
    Oh, that's a good point! Your comment was posted at the same time as another answer, which gives this solution (and I can't mark a comment as "issue resolved", but consider it so). Thanks to you, and welcome to SO! – Foxy May 03 '20 at 17:34