Flutter/C/C++混编踩坑

1,610 阅读3分钟

需求背景

我们项目的核心就是围绕设备振动数据采集、数据计算、形成图谱、然后根据图谱分析设备是否存在故障。现在的需求是临时采集,具体使用场景:手机通过蓝牙连接硬件设备获取到数据之后,然后通过计算形成图谱数据。那么问题来了,“计算过程”是哪个端的小伙伴来搞呢,开始的方案是手机获取到设备数据后传递给后台,因为后台已经有一套算法逻辑,但是需求就是需求,来一句我们使用的场景很恶劣,都是在工厂里,手机网络非常不稳定,所以通过传递数据给后台计算就给pass掉了,那么唯一的解决方式就是移动端自己计算了,靠人不靠己呀,兄弟萌,那就搞呗

项目环境

 Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, 1.22.6, on macOS 12.1 21C52 darwin-x64, locale zh-Hans-CN)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[!] Android Studio (version 2020.3)

难点

难点-:计算使用到了fft算法,现有的算法是C++的,dart库你懂的,贼少
解决方案1: C++直接翻译成dart,优点:集成简单,开发容易;缺点:C++需要有基础,违背代码复用原则
解决方案2: Flutter直接调用C++库,优点:逻辑复用,维护方便;缺点:需要Flutter集成Native C/C++不是那么简单
最终方案:采用2,方案2其实也有不同的实现方式,如下:
    (1):直接把集成C++文件到项目中然后通过ffi调用,优点:简单明了维护简单,缺点:算法对外暴露
    (2):将C++代码打包成动态库,然后在调用,优点:算法对外隐蔽,缺点:集成麻烦,安卓需要打包成.so,ios需要打包成.dylib,后期维护麻烦

踩坑之路

集成是按照官方文档docs.flutter.dev/development… 基本有手就行,我直说一下遇到的问题和技巧吧

问题1:

官方demo只提供了一个native_add.cpp的c文件,如果提供C库的同事是两个文件,该怎么引用呢,哈哈,很简单是这个样子滴

 cat > android/CMakeLists.txt << EOF
 cmake_minimum_required(VERSION 3.4.1)  # for example
 add_library(native_add
         # Sets the library as a shared library.
         SHARED

         # Provides a relative path to your source file(s).
         ../ios/Classes/native_add.cpp
         # 如果多个c文件,可以再次列出
         ../ios/Classes/xxx.cpp
         )
EOF

引入完cpp文件之后,接下来根据官方文档,应该dart端创建动态库Step 3: Load the code using the FFI library,代码如下

import 'dart:ffi'; // For FFI
import 'dart:io'; // For Platform.isX

final DynamicLibrary nativeAddLib = Platform.isAndroid
? DynamicLibrary.open('libnative_add.so')
: DynamicLibrary.process();

官方文档提醒安卓加载动态库以CmakeLists.text的设置命名,而iOS是以插件的名字的命名,所以CmakeList.text里设置的库名最好就是插件的名字

问题2:为什么我仿照官方文档,写了个一毛一样的方法,只是加号变减号,跑起来会报错?

具体的报错信息记不清了大概就是 symbol not found,翻译过来就是方法找不到,没想到吧,就是下面这一行搞的鬼,没他真不行,因为官方文档明确指出ffi调用只认识C,不认识C++,所以加上下面这行告诉编译器这个函数是按照C语言编译和链接,注意每个函数都要加上下面一行

extern "C" attribute((visibility("default"))) attribute((used))

示例如下:

cat > ios/Classes/native_add.cpp << EOF
#include <stdint.h>

extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
    return x + y;
}

extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_minus(int32_t x, int32_t y) {
    return x - y;
}

EOF

问题3:C函数是这样的,dart端改怎么写呢

void test(double * input,double * output){
    //简单的处理
    output = input;
}
此方法值得是给一个元素为double型的input数组,得到一个double型的output数组

input 和output这两个指针在dart端怎么表示呢,还好ffi提供了Pointer 这么一个类, NativeType有啥呢,像Int、Double、Float、Bool、Struct等,下面是怎么引用C函数

```
final void nativeTest(Pointer<Double> input, Pointer<Double> output) nativeAdd = nativeAddLib
.lookup<NativeFunction<Void Function(Pointer<Double>, Pointer<Double>)>>('test')
.asFunction();
```

问题4:业务调用时,该怎么声明一个Pointer指针呢?

这个问题困惑了我好久,这个文档dart.cn/guides/libr… 给了一些帮助,其中有个样例是这样写的



  import 'dart:ffi';
  import 'dart:io';

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

  ******
  
  
// calls int subtract(int *a, int b);
// Create a pointer
final p = calloc<Int32>();
// Place a value into the address
p.value = 3;

final subtractPointer =
    dylib.lookup<NativeFunction<SubtractFunc>>('subtract');
final subtract = subtractPointer.asFunction<Subtract>();
print('3 - 5 = ${subtract(p, 5)}');

// Free up allocated memory.
calloc.free(p);

但是发现自己的ffi库中并没有 calloc()函数,好一阵才搞清楚,原来dart sdk 自带的ffi库是没有此函数的,需要引入在yaml中引入ffi插件才行,链接在这pub.flutter-io.cn/packages/ff…

屏幕快照 2022-02-10 下午9.49.04.png

ffi版本更新了,最新的声明指针方法改为 allocated,参数代表开辟多大空间,那么传递一个数组指针最终是这样

List list = [1.00,2.00];
//创建指针
Pointer<Double> input = allocate(count:list.length); 
for(int i =0; i < list.length; i++) {
    input.element(i).value = list[i];
}
//用来存放计算结果
Pointer<Double> output = allocate(count:list.length); 

//开始计算
nativeTest(input,output);

//得到结果
List result = [];
for(int i =0; i < list.length; i++) {
    result[i] = output.element(i).value;
}
print(result);
//打印结果是[1.00, 2.00]

总结

拓展与学习