Javascript is required
FFi For Flutter

FFi

MacOS 12.0

CMake:: cmake version 3.21.2

Make: GNU Make 3.81

Dart: Dart SDK version: 2.15.0÷÷

FFI (Foreign Function Interface)表示 外部功能接口,类似 JAVA 的 JNI。项目升级到 Flutter 2.0 之后,我们就可以使用 dart:ffi 库来调用 C 语言编写的代码。  

在 Flutter 2.0 中的 Dart 2.12 已发布,其中包含健全的空安全和 Dart FFI 的稳定版, 并且提供了一套类型绑定生成工具 ffigen,可以自动生成 Dart Wrapper 加快开发效率。

如何将用 C/C++ 编写的代码带入到 Flutter 应用中

image-20220126202601798

关于Dart语言FFI使用 ,大概步骤

  1. 导入ffi包
  2. 为被调用的c函数创建Dart Native签名
  3. 为被调用c函数创建Dart函数
  4. 加载动态库
  5. 查找c函数,并将c函数指针映射dart函数对象
  6. 调用函数

解决的问题

  1. 可以同步调用C API,不像Flutter Channel一开始就是异步
  2. 调用C语言更快,不像之前需要通过Native中转(或者改Flutter引擎代码)

方式

Flutter->原生JNI->C

Flutter->FFI->C

原始FFI

将已有的 c/cpp 通过交叉编译各个平台,windowsdllunix系统的so库,然后通过DynamicLibraryopen函数去打开这个库,来获得其中函数的动态链接。随即按套路编写dart调用ffi的代码即可。

ffgen

每个平台都有一些已经有的so库,如果我们的 c/cpp 语言实现的都是一些所有unix设备或者windows设备底层自带so、dll中的函数,我们不妨将里面每一个函数都转换成dart端可以对应调用的函数,也就是生成一个dart可用的 ".h" 头,当然这个扩展名还是 ".dart" 。

这样一来,我们无需再为了 windows、macos、linux、android、ios 这5个平台分别交叉编译一份动态库,我们只需要写一份通用的dart代码即可。


Mac电脑确保安装llvm

brew install --force-bottle llvm@3.9

/usr/local/opt/llvm # 确保可以找到

添加依赖pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0
  # FFI
  ffi: ^1.1.2
  ffigen: ^4.1.2

添加c文件 ios/Runner/native.cpp ios/Runner/native.h

ffigen:
  name: NativeLibrary
  description: Bindings to `library/hello.h`.
  output: 'lib/c.dart'
  headers:
    entry-points:
      - 'ios/Runner/native.h'

使用ffigen生成dart代码 - 终端执行

➜  ffi dart run ffigen

image-20220126182251187

Dart Use

import 'dart:ffi' as ffi;
import 'dart:io' show Platform, Directory;
import 'package:ffi/ffi.dart';

常用属性与方法介绍

Dart与C语言,Dart FFI提供了很多方法

import 'package:ffi'

DynamicLibrary.open

它可以加载动态链接库

external factory DynamicLibrary.open(String path);
import 'dart:ffi' as ffi;

final dylib = ffi.DynamicLibrary.open("libnative_add.so");

DynamicLibrary.process

import 'dart:io' show Platform;
import 'dart:ffi' as ffi;


late ffi.DynamicLibrary native;

native = Platform.isAndroid
        ? ffi.DynamicLibrary.open("libnative_add.so")
        : ffi.DynamicLibrary.process();

它可以用于在iOS及MacOS中加载应用程序已经自动加载好的动态链接库,也可以解析静态链接到应用的二进制文件符号。需要注意的是,它不能用于windows平台

DynamicLibrary.executable

external factory DynamicLibrary.executable();

它可用于加载静态链接库

NativeType

NativeType是在Dart中表示C语言中的数据结构

Pointer

它是C语言中指针在Dart中的映射

DynamicLibrary->lookup()

external Pointer<T> lookup<T extends NativeType>(String symbolName);

它用于在DynamicLibrary中查找到对应的符号并返回其内存地址。

final dylib = DynamicLibrary.open(libraryPath);
late final _hello_worldPtr =
      dylib.lookup<NativeFunction<Void Function()>>('hello_world');
late final _hello_world = _hello_worldPtr.asFunction<void Function()>();
_hello_world();

Pointer.fromAddress(int ptr)

根据内存地址获取C对象指针

// 创建一个指向NULL的Native指针
final Pointer<Never> nullptr = Pointer.fromAddress(0);

Pointer.fromFunction

根据一个Dart函数,创建一个Native函数指针,一般用于将Dart函数传给C,使C有调用Dart函数的能力

void globalCallback(int src, int result) {
   print("globalCallback src=$src, result=$result");
}
Pointer.fromFunction(globalCallback);

Pointer->address()

获取指针的内存地址

asFunction

将Native指针对象,转换为Dart函数

sizeOf

返回具体类型的内存占用

ffi.sizeOf<ffi.Int64>(); // 8
复制代码

malloc.allocate()

Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment});
复制代码

开辟一块大小byteCount的空间

Pointer<Uint8> bytes = malloc.allocate<Uint8>(ffi.sizeOf<ffi.Uint8>());
复制代码

malloc.free

释放内存

malloc.free(bytes);

Dart FFI与C基础数据类型映射表

Dart 中定义的NativeTypeC语言中的类型说明
Opaqueopaque不暴露其成员类型,一般用于表示C++中的类
Int8int8_t 或 char有符号8位整数
Int16int16_t 或 short有符号16位整数
Int32int32_t 或 int有符号32位整数
Int64int64_t 或 long long有符号64位整数
Uint8uint8_t 或 unsigned char无符号8位整数
Uint16uint16_t 或 unsigned short无符号16位整数
Uint32int32_t 或 unsigned int无符号32位整数
Uint64uint64_t 或 unsigned long long无符号64位整数
IntPtrint*整数类型指针
Floatfloat单精度浮点类型
Doubledouble双精度浮点类型
Voidvoidvoid类型
HandleDart_Handle Dart句柄在C中的表示形式
NativeFunction函数函数类型
Structstruct结构体类型
Unionunion共同体类型
Pointer*指针类型
nullptrNULL空指针
dynamicDart_CObjectDart对象在C中的表现

Dart Call C

如何找到 C++ 中的库

通过 DynamicLibrary.open() dlopen 加载动态链接库,并返回句柄。

ffigen

https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fffigen库来根据C/C++头文件自动生成dart 函数

dart run ffigen

注意

ffigen只能自动生成C风格的头文件,如果你的头文件中包含了C++风格代码如class,需要使用#ifdef __cplusplus #endif包裹起来

/// 两个数相加
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
    return x + y;
}

参考

用到的工具库

文档

https://dart.cn/guides/libraries/c-interop

https://api.dart.cn/stable/2.15.1/dart-ffi/dart-ffi-library.html

FFi

MacOS 12.0

CMake:: cmake version 3.21.2

Make: GNU Make 3.81

Dart: Dart SDK version: 2.15.0÷÷

FFI (Foreign Function Interface)表示 外部功能接口,类似 JAVA 的 JNI。项目升级到 Flutter 2.0 之后,我们就可以使用 dart:ffi 库来调用 C 语言编写的代码。  

在 Flutter 2.0 中的 Dart 2.12 已发布,其中包含健全的空安全和 Dart FFI 的稳定版, 并且提供了一套类型绑定生成工具 ffigen,可以自动生成 Dart Wrapper 加快开发效率。

如何将用 C/C++ 编写的代码带入到 Flutter 应用中

image-20220126202601798

关于Dart语言FFI使用 ,大概步骤

  1. 导入ffi包
  2. 为被调用的c函数创建Dart Native签名
  3. 为被调用c函数创建Dart函数
  4. 加载动态库
  5. 查找c函数,并将c函数指针映射dart函数对象
  6. 调用函数

解决的问题

  1. 可以同步调用C API,不像Flutter Channel一开始就是异步
  2. 调用C语言更快,不像之前需要通过Native中转(或者改Flutter引擎代码)

方式

Flutter->原生JNI->C

Flutter->FFI->C

原始FFI

将已有的 c/cpp 通过交叉编译各个平台,windowsdllunix系统的so库,然后通过DynamicLibraryopen函数去打开这个库,来获得其中函数的动态链接。随即按套路编写dart调用ffi的代码即可。

ffgen

每个平台都有一些已经有的so库,如果我们的 c/cpp 语言实现的都是一些所有unix设备或者windows设备底层自带so、dll中的函数,我们不妨将里面每一个函数都转换成dart端可以对应调用的函数,也就是生成一个dart可用的 ".h" 头,当然这个扩展名还是 ".dart" 。

这样一来,我们无需再为了 windows、macos、linux、android、ios 这5个平台分别交叉编译一份动态库,我们只需要写一份通用的dart代码即可。


Mac电脑确保安装llvm

brew install --force-bottle llvm@3.9

/usr/local/opt/llvm # 确保可以找到

添加依赖pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0
  # FFI
  ffi: ^1.1.2
  ffigen: ^4.1.2

添加c文件 ios/Runner/native.cpp ios/Runner/native.h

ffigen:
  name: NativeLibrary
  description: Bindings to `library/hello.h`.
  output: 'lib/c.dart'
  headers:
    entry-points:
      - 'ios/Runner/native.h'

使用ffigen生成dart代码 - 终端执行

➜  ffi dart run ffigen

image-20220126182251187

Dart Use

import 'dart:ffi' as ffi;
import 'dart:io' show Platform, Directory;
import 'package:ffi/ffi.dart';

常用属性与方法介绍

Dart与C语言,Dart FFI提供了很多方法

import 'package:ffi'

DynamicLibrary.open

它可以加载动态链接库

external factory DynamicLibrary.open(String path);
import 'dart:ffi' as ffi;

final dylib = ffi.DynamicLibrary.open("libnative_add.so");

DynamicLibrary.process

import 'dart:io' show Platform;
import 'dart:ffi' as ffi;


late ffi.DynamicLibrary native;

native = Platform.isAndroid
        ? ffi.DynamicLibrary.open("libnative_add.so")
        : ffi.DynamicLibrary.process();

它可以用于在iOS及MacOS中加载应用程序已经自动加载好的动态链接库,也可以解析静态链接到应用的二进制文件符号。需要注意的是,它不能用于windows平台

DynamicLibrary.executable

external factory DynamicLibrary.executable();

它可用于加载静态链接库

NativeType

NativeType是在Dart中表示C语言中的数据结构

Pointer

它是C语言中指针在Dart中的映射

DynamicLibrary->lookup()

external Pointer<T> lookup<T extends NativeType>(String symbolName);

它用于在DynamicLibrary中查找到对应的符号并返回其内存地址。

final dylib = DynamicLibrary.open(libraryPath);
late final _hello_worldPtr =
      dylib.lookup<NativeFunction<Void Function()>>('hello_world');
late final _hello_world = _hello_worldPtr.asFunction<void Function()>();
_hello_world();

Pointer.fromAddress(int ptr)

根据内存地址获取C对象指针

// 创建一个指向NULL的Native指针
final Pointer<Never> nullptr = Pointer.fromAddress(0);

Pointer.fromFunction

根据一个Dart函数,创建一个Native函数指针,一般用于将Dart函数传给C,使C有调用Dart函数的能力

void globalCallback(int src, int result) {
   print("globalCallback src=$src, result=$result");
}
Pointer.fromFunction(globalCallback);

Pointer->address()

获取指针的内存地址

asFunction

将Native指针对象,转换为Dart函数

sizeOf

返回具体类型的内存占用

ffi.sizeOf<ffi.Int64>(); // 8
复制代码

malloc.allocate()

Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment});
复制代码

开辟一块大小byteCount的空间

Pointer<Uint8> bytes = malloc.allocate<Uint8>(ffi.sizeOf<ffi.Uint8>());
复制代码

malloc.free

释放内存

malloc.free(bytes);

Dart FFI与C基础数据类型映射表

Dart 中定义的NativeTypeC语言中的类型说明
Opaqueopaque不暴露其成员类型,一般用于表示C++中的类
Int8int8_t 或 char有符号8位整数
Int16int16_t 或 short有符号16位整数
Int32int32_t 或 int有符号32位整数
Int64int64_t 或 long long有符号64位整数
Uint8uint8_t 或 unsigned char无符号8位整数
Uint16uint16_t 或 unsigned short无符号16位整数
Uint32int32_t 或 unsigned int无符号32位整数
Uint64uint64_t 或 unsigned long long无符号64位整数
IntPtrint*整数类型指针
Floatfloat单精度浮点类型
Doubledouble双精度浮点类型
Voidvoidvoid类型
HandleDart_Handle Dart句柄在C中的表示形式
NativeFunction函数函数类型
Structstruct结构体类型
Unionunion共同体类型
Pointer*指针类型
nullptrNULL空指针
dynamicDart_CObjectDart对象在C中的表现

Dart Call C

如何找到 C++ 中的库

通过 DynamicLibrary.open() dlopen 加载动态链接库,并返回句柄。

ffigen

https://link.juejin.cn/?target=https%3A%2F%2Fpub.dev%2Fpackages%2Fffigen库来根据C/C++头文件自动生成dart 函数

dart run ffigen

注意

ffigen只能自动生成C风格的头文件,如果你的头文件中包含了C++风格代码如class,需要使用#ifdef __cplusplus #endif包裹起来

/// 两个数相加
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
    return x + y;
}

参考

用到的工具库

文档

https://dart.cn/guides/libraries/c-interop

https://api.dart.cn/stable/2.15.1/dart-ffi/dart-ffi-library.html