0

My c++ function is defined as follows:

std::vector<std::array<std::array<float, 160>, 160>> get_masks(float *img_pixels) {...}

I would request the community to help out by mapping this function to dart so it can be consumed.
I even tried ffigen & Chatgpt but the results were unsatisfactory.

Also is there any good resource from where I can learn this concept?

Kanishka Munshi
  • 530
  • 1
  • 4
  • 11

1 Answers1

1

Solution:

You can use this code as a start, BUT DON'T FORGET TO FREE THE ALLOCATED MEMORY (AND EVEN OPTIMIZE IT MORE TO YOUR SPECIFIC CASE, AS IT MAY NOT BE OPTIMIZED AND IT IS JUST FOR DEMONSTRATION):

  • on the native side:
FFI_PLUGIN_EXPORT float **createDoubleArrayFromVectorOfDoubleArrays(void *vector) {
    std::vector<std::array<std::array<float, 160>, 160>> *vectorOfDoubleArrays = (std::vector<std::array<std::array<float, 160>, 160>> *) vector;
    float **array = new float *[vectorOfDoubleArrays->size()];
    for (int i = 0; i < vectorOfDoubleArrays->size(); i++) {
        array[i] = new float[160 * 160];
        for (int j = 0; j < 160; j++) {
            for (int k = 0; k < 160; k++) {
                array[i][j * 160 + k] = (*vectorOfDoubleArrays)[i][j][k];
            }
        }
    }
    return array;
}
  • on the dart side (after generating the bindings):
List<List<Float64List>> generateRandomArrayOfDoubleArrays(int count){
  Pointer<Void> nativeVector = _bindings.createRandomVectorOfDoubleArrays(count); // create a vector of double arrays in native code and return a pointer to it (casted to void*)
  final Pointer<Pointer<Float>> nativeGeneratedArray = _bindings.createDoubleArrayFromVectorOfDoubleArrays(nativeVector);  // generate a list of list of double arrays from nativeVector, each double array is a Float64List of length 160
  // now we convert them to dart lists by iterating and copying the data
  final List<List<Float64List>> generatedArray = [];
  for (int i = 0; i < count; i++) {
    final List<Float64List> generatedArrayRow = [];
    for (int j = 0; j < 160; j++) {
      final Pointer<Float> nativeGeneratedArrayRow = nativeGeneratedArray.elementAt(i).value.elementAt(j);
      final Float64List generatedArrayRowElement = Float64List.fromList(nativeGeneratedArrayRow.asTypedList(160));
      generatedArrayRow.add(generatedArrayRowElement);
    }
    generatedArray.add(generatedArrayRow);
  }
  return generatedArray;
}

Result: enter image description here

Sample App: Full working example steps:

  • create a dart ffi project by running flutter create --template=plugin_ffi --platforms=android,ios,windows,macos,linux sample_ffi_plugin
  • go to the generated project
  • rename sample_ffi_plugin.c in src/ to sample_ffi_plugin.cpp (and rename it also in CMakeLists.txt
  • replace contents of sample_ffi_plugin.cpp with these:
#include "sample_ffi_plugin.h"

FFI_PLUGIN_EXPORT float **createDoubleArrayFromVectorOfDoubleArrays(void *vector) {
    std::vector<std::array<std::array<float, 160>, 160>> *vectorOfDoubleArrays = (std::vector<std::array<std::array<float, 160>, 160>> *) vector;
    float **array = new float *[vectorOfDoubleArrays->size()];
    for (int i = 0; i < vectorOfDoubleArrays->size(); i++) {
        array[i] = new float[160 * 160];
        for (int j = 0; j < 160; j++) {
            for (int k = 0; k < 160; k++) {
                array[i][j * 160 + k] = (*vectorOfDoubleArrays)[i][j][k];
            }
        }
    }
    return array;
}

FFI_PLUGIN_EXPORT void* createRandomVectorOfDoubleArrays(int count){
    std::vector<std::array<std::array<float, 160>, 160>> *vector = new std::vector<std::array<std::array<float, 160>, 160>>;
    for (int i = 0; i < count; i++) {
        std::array<std::array<float, 160>, 160> array;
        for (int j = 0; j < 160; j++) {
            for (int k = 0; k < 160; k++) {
                array[j][k] = j * 160 + k;
            }
        }
        vector->push_back(array);
    }
    return vector;
}
  • replace contents of sample_ffi_plugin.h with these:
#if _WIN32
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FFI_PLUGIN_EXPORT
#endif


#ifdef __cplusplus
#include <vector>
#include <array>
extern "C" {
#endif

FFI_PLUGIN_EXPORT void* createRandomVectorOfDoubleArrays(int count);

FFI_PLUGIN_EXPORT float** createDoubleArrayFromVectorOfDoubleArrays(void* vector);

#ifdef __cplusplus
}
#endif

  • generate the bindings:
flutter pub run ffigen --config ffigen.yaml
  • replace code in sample_ffi_plugin.dart in /lib/ with this:

import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'dart:typed_data';

import 'sample_ffi_plugin_bindings_generated.dart';


const String _libName = 'sample_ffi_plugin';

/// The dynamic library in which the symbols for [SampleFfiPluginBindings] can be found.
final DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    return DynamicLibrary.open('$_libName.framework/$_libName');
  }
  if (Platform.isAndroid || Platform.isLinux) {
    return DynamicLibrary.open('lib$_libName.so');
  }
  if (Platform.isWindows) {
    return DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

/// The bindings to the native functions in [_dylib].
final SampleFfiPluginBindings _bindings = SampleFfiPluginBindings(_dylib);

extension Float64ListExtension on Float64List {
  /// Creates a `double` array from this list by copying the list's
  /// data, and returns a pointer to it.
  ///
  /// `nullptr` is returned if the list is empty.
  Pointer<Double> toDoubleArrayPointer({required Allocator allocator}) {
    if (isEmpty) {
      return nullptr;
    }
    final Pointer<Double> ptr = allocator(sizeOf<Double>() * length);
    for (int i = 0; i < length; i++) {
      ptr[i] = this[i];
    }
    return ptr;
  }
}

List<List<Float64List>> generateRandomArrayOfDoubleArrays(int count){
  Pointer<Void> nativeVector = _bindings.createRandomVectorOfDoubleArrays(count); // create a vector of double arrays in native code and return a pointer to it (casted to void*)
  final Pointer<Pointer<Float>> nativeGeneratedArray = _bindings.createDoubleArrayFromVectorOfDoubleArrays(nativeVector);  // generate a list of list of double arrays from nativeVector, each double array is a Float64List of length 160
  // now we convert them to dart lists by iterating and copying the data
  final List<List<Float64List>> generatedArray = [];
  for (int i = 0; i < count; i++) {
    final List<Float64List> generatedArrayRow = [];
    for (int j = 0; j < 160; j++) {
      final Pointer<Float> nativeGeneratedArrayRow = nativeGeneratedArray.elementAt(i).value.elementAt(j);
      final Float64List generatedArrayRowElement = Float64List.fromList(nativeGeneratedArrayRow.asTypedList(160));
      generatedArrayRow.add(generatedArrayRowElement);
    }
    generatedArray.add(generatedArrayRow);
  }
  return generatedArray;
}
  • finally go to /example and replace main.dart with this:
import 'package:flutter/material.dart';
import 'dart:async';

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    const textStyle = TextStyle(fontSize: 25);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Native Packages'),
        ),
        body: SingleChildScrollView(
          child: Container(
            padding: const EdgeInsets.all(10),
            alignment: Alignment.center,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text(
                  'First 100 elements of each of first 10 generated arrays:',
                  style: textStyle,
                ),
                for(int i=0;i<10;i++)
                Text(
                    '${sample_ffi_plugin.generateRandomArrayOfDoubleArrays(1)[0][i].take(100)}',
                    style: textStyle),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

HII
  • 3,420
  • 1
  • 14
  • 35
  • First of all thanks for your prompt answer. I wanted to ask you if is it possible that the variable count can be dynamic. ie. in this method generateRandomArrayOfDoubleArrays(int count) can count not be used at all? – Kanishka Munshi Mar 29 '23 at 13:25
  • @KanishkaMunshi yes, you can remove this parameter and hard-code the value you want instead – HII Mar 29 '23 at 14:29
  • Can't hardcode, it is dynamic. It is detecting objects from an image and returns all those objects from cpp to dart. Here the number of objects (i.e. the count) is dynamic depending upon the image. – Kanishka Munshi Mar 29 '23 at 14:55
  • 1
    @KanishkaMunshi yes, you can, in your app, on the cpp side, you can modify `createDoubleArrayFromVectorOfDoubleArrays` to your needs (change the loop bounds, vector size, element assignment). I just put the `count` for demonstration purposes – HII Mar 29 '23 at 15:41
  • Although I had to make plenty of changes according to my use case, this answer was very close to what I did and helped me gain enough insights into making it work. – Kanishka Munshi Apr 03 '23 at 06:59