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();
}