Dart FFI keep Struct pointer

Issue

I am trying to call a C library from dart. The C code as a genertor method such as

a_struc  create_struct(void);

and then methods that take in a pointer to struct, such as:

const char *
 get_info(const a_struc * s);

I have created the dart binding using ffigen and I have

class a_struct extends ffi.Struct {
}

a_struct create_struct() {
    return create_struct();
  }

late final create_structPtr =
   _lookup<ffi.NativeFunction<a_struct Function()>>(
       'create_struct');
late final create_struct =
   create_structPtr
       .asFunction<a_struct Function()>();

and

ffi.Pointer<ffi.Int8> get_info(
    ffi.Pointer<a_struct> a_struct,
  ) {
    return get_info(
      a_struct,
    );
  }

late final get_infoPtr = _lookup<
    ffi.NativeFunction<
        ffi.Pointer<ffi.Int8> Function(
            ffi.Pointer<a_struct>)>>('rcl_publisher_get_topic_name');
late final get_info =
    get_infoPtr.asFunction<
        ffi.Pointer<ffi.Int8> Function(ffi.Pointer<a_struct>)>();

My problem is that I don’t know how to call the method get_info from the dart a_struct generated by create_struct. Indeed, get_info expects a ffi.Pointer<a_struct> but I only generate a a_struct without the pointer. Since dart deprecated .addressOf, how can I obtain a ffi.Pointer<a_struct> from create_struct?

Solution

When you return a struct by value from C to Dart, the struct is copied into a relocatable memory block that’s fully managed by Dart.

Dart doesn’t provide an API that exposes the underlying address of these objects, and so if you need to pass that data to C by reference, you’ll need to store it in a non-relocatable native memory region (i.e. heap allocate it or use global C state) and keep track of a pointer to it on the Dart side.

If you can’t (or would prefer not to) modify the C++ library, you could use the Dart FFI package to call malloc<MyStruct>() and then copy the struct into the newly allocated space with ref= setter.

final ffi.Pointer<MyStruct> myStructPtr = malloc<MyStruct>()
  ..ref = createStructFn();

// ... pass myStructPtr to other FFI functions by reference ...

malloc.free(myStructPtr);

Note that the ref= setter in StructPointer landed in Dart on Jan 12, 2022 (quite recently, at the time of writing).

Full example below.

C++:

// Build command: g++ -shared lib.cc -o lib.dll -Wl,--out-implib,lib.a

#include <cstdlib>

extern "C" {
  typedef struct {
    const char* info;
  } MyStruct;

  MyStruct CreateStruct() {
    return {.info = "Hello Dart!"};
  }

  const char* GetInfo(MyStruct* s) {
    return s->info;
  }
}

Dart:

import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';

class MyStruct extends ffi.Struct {
  external ffi.Pointer<Utf8> info;
}

typedef CreateStruct = MyStruct Function();
typedef GetInfo = ffi.Pointer<Utf8> Function(ffi.Pointer<MyStruct>);

void main() {
  // Load FFI procs.
  final lib = ffi.DynamicLibrary.open('lib.dll');
  final createStructFn =
      lib.lookupFunction<CreateStruct, CreateStruct>('CreateStruct');
  final getInfoFn = lib.lookupFunction<GetInfo, GetInfo>('GetInfo');

  // Allocate a native memory region and copy the returned struct into it.
  final myStructPtr = malloc<MyStruct>()..ref = createStructFn();

  // It's a pointer, so we can pass by reference.
  final result = getInfoFn(myStructPtr);
  print(result.toDartString());

  // Cleanup!
  malloc.free(myStructPtr);
}

As an aside: Before the "returning structs by value" features landed, all structs passed from C to Dart had to be passed by reference. That’s the reason addressOf was initially available on all FFI structs — they all were ffi.Pointers. To support Dart-managed storage backends, ffi.Pointer needed to be decoupled from ffi.Struct.
The result is a much more sensible API! For example, you can have a Pointer<Pointer<Uint8>> and it functions exactly as you’d expect.

Answered By – Brandon DeRosier

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.