Search code examples
flutterdartdart-ffi

Convert a vector of 2D array to dart/flutter for dart-ffi consumption


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?


Solution

  • 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),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }