Home > database >  Flutter C bindings with dart::ffi
Flutter C bindings with dart::ffi

Time:03-30

I am trying to write a wrapper package for a C code that does some data analyses in our App. I found this great post that shows the major steps needed to do that. I am more a C/C coder so I am a bit stuck and I don't get what I am doing wrong?

This is the analyses.hpp

#include <memory>
#include <vector>

struct int_array
{
    int* array;
    int len;
};

class cpp_analyses
{
public:
        cpp_analyses();
        ~cpp_analyses() = default;

        int_array get_header_index(int_array idx_list_input);
};

The corresponding analyses.cpp

#include "analyses.hpp"

cpp_analyses::cpp_analyses()
{

}

int_array cpp_analyses::get_header_index(int_array idx_list_input)
{
        int_array out;

        out.array = new int[2];
        out.len = 2;

        out.array[0] = 1;
        out.array[1] = 2;

        return out;
}

This is the analyses-adapter.hpp

#include "analyses.hpp"

#define EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used))

EXPORT void* initialize_analyses();
EXPORT int_array  analyses_get_index(void *ptr, struct int_array in_idx);

The corresponding source file analyses_adapter.cpp

#include "analyses-adapter.hpp"

void* initialize_analyses()
{
        return new cpp_analyses;
}

int_array  analyses_get_index(void *ptr, struct int_array in_idx)
{
        auto typed_ptr = static_cast<cpp_analyses*>(ptr);
        return typed_ptr->get_header_index(in_idx);
}

On the dart side:

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

class IntArray extends Struct {
  external Pointer<Int32> data;

  @Int32()
  external int length;
}

typedef _example_init_analyses = Pointer<Void> Function();
typedef _analyses_get_saccades_index = IntArray Function(Pointer<Void> obj, IntArray input);

class Analyses {
        static late DynamicLibrary nativeApiLib;
        external Pointer<Void> _nativeInstance;
        external _analyses_index analyses_index;

        Analyses() {
                nativeApiLib =(DynamicLibrary.open('libanalyses.so')); // android and linux
                _example_init_analyses _initialize_analyses = nativeApiLib.lookup<NativeFunction<_example_init_analyses>>('initialize_analyses').asFunction();
                _nativeInstance = _initialize_analyses();

                analyses_index = nativeApiLib.lookup<NativeFunction<_analyses_index>>('analyses_get_index').asFunction();
        }

        IntArray get_saccades_index(IntArray input)
        {
                return analyses_index(_nativeInstance, input);
        }
}

Finally in main.dart

import 'package:flutter/material.dart';
import 'package:analyses/analyses.dart';

void main() {
        Analyses OBJ = new Analyses();
        IntArray TMP = IntArray();

        TMP.length = 2;

        IntArray TMP_second = OBJ.get_index(TMP);
}

The whole code builds without issue. However, when I try to run the code, it is complaining about the fact that

#0 NoSuchMethodError._throwNew (dart:core-patch/errors_patch.dart:222:5)

#1 Analyses._nativeInstance= (package:analyses/ncapp_analyses.dart)

I don't see where I am doing something wrong. Any help would be greatly appreciated! Thanks

EDIT As requested, please see the full stacktrace

Tried calling: _nativeInstance=
#0      NoSuchMethodError._throwNew (dart:core-patch/errors_patch.dart:222:5)
#1      Analyses._nativeInstance= (package:analyses/analyses.dart)
#2      new Analyses (package:analyses/analyses.dart:22:17)
#3      main (package:analyses/main.dart:5:33)
#4      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:130:25)
#5      _rootRun (dart:async/zone.dart:1426:13)
#6      _CustomZone.run (dart:async/zone.dart:1328:19)
#7      _runZoned (dart:async/zone.dart:1861:10)
#8      runZonedGuarded (dart:async/zone.dart:1849:12)
#9      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:126:5)
#10     _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#11     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

If I remove the external in front of Pointer _nativeInstance;, I cannot compile:

ERROR: lib/ncapp_analyses.dart:16:23: Error: Field '_nativeInstance' should be initialized because its type 'Pointer<Void>' doesn't allow null.
ERROR:  - 'Pointer' is from 'dart:ffi'.
ERROR:  - 'Void' is from 'dart:ffi'.
ERROR:         Pointer<Void> _nativeInstance;
ERROR:                       ^^^^^^^^^^^^^^^

CodePudding user response:

First, you need to separate your Dart class the represents the shared object from the Dart class that represents your C class.

The first will contain the shared library reference and the looked up functions; the second will just contain the Pointer<Void> to the instance of your C class - there will be one per instance (and will be the thing that you free when you are done with that particular C class instance).

For example:

import 'dart:ffi';

class SoLibrary {
  factory SoLibrary() {
    // make it a singleton
    _instance ??= SoLibrary._();
    return _instance!;
  }

  SoLibrary._() {
    // todo - check for Platform type
    _nativeApiLib = DynamicLibrary.open('libanalyses.so');
  }

  static SoLibrary? _instance;

  late DynamicLibrary _nativeApiLib;
  late final Pointer<Void> Function() _newAnalyses = _nativeApiLib
      .lookupFunction<Pointer<Void> Function(), Pointer<Void> Function()>(
          'initialize_analyses');
}

class Analyses {
  Pointer<Void> _nativeInstance;

  Analyses() : _nativeInstance = SoLibrary()._newAnalyses();
}
  • Related