Flutter中FFI学习
- Dart FFI编程
 - 概述
 - NativeType(类型映射)
 - Window安装GCC
 - Dart调用C的函数
 - 数组
 - 字符串
 - 结构体
 
Dart FFI编程
概述
dart:ffi库可以使用Dart语言调用本地C语言API
 ,并读取、写入、分配和删除本地内存。FFI是指外部函数接口(Foregin function interface)。其他类似功能的术语包括本地接口和语言绑定。

 具体关于如何将C语言指针映射为Dart中的指针类型的操作, 可以查看Dart FFI API参考文档
关于Dart语言的FFI使用, 可总结以下6个步骤:
- 导入
ffi包 - 为被调用的C函数创建Dart Native签名(FFI类型前面,Navtive Type)
 - 为被调用的C函数创建Dart函数前面(与第2不对应)
 - 加载动态库
 - 查找C函数,并将C函数指针映射为Dart函数(即将第2、3步创建的签名映射起来)
 - 调用函数
 
NativeType(类型映射)
NativeType是在Dart中表示C语言中的数据结构,它不可在Dart中实例化,只能由Native返回。
Dart FFI与C基础数据类型映射表如下:
 
Window安装GCC
window平台安装GCC
把C/C++文件编译成.dll动态库
gcc -shared -o libexample.dll libexample.c
 
Dart调用C的函数
//C语言函数
int calu(char *expression) {
    
    printf("开始执行方法============");
    for (size_t i = 0; i < 5; i++)
    {
        /* code */
        printf("传入的值是===========%c\n", expression[i]);
    }
   
    return 8;
}
 
验证代码:
import 'dart:ffi';
import 'package:ffi/ffi.dart' as ffi;
///
/// 被Dart调用的C语言原型
/// int calu(char *expression)
///
///
/// int -> int32 (在c语言中int 是占32位)
/// char *  -> Pointer<ffi.Utf8>  在C语言中char * 代表一个指针, Char 实际上int8,但是dart:ffi中没有对应的,
/// 所以我们需要使用到第三方那个库中的API, 该库中char*对应是utf8
/// ffi类型签名(native签名)把c的函数映射成native函数
typedef CaluNavtive = Int32 Function(Pointer<ffi.Utf8>);
/// Dart函数签名
/// 首先dart函数签名,应该使用dart中的签名, 如果是基础数据类型,我们可以直接使用dart中的基础数据类型
/// 但是如果是对象类型, 比如下面函数中我们就不能使用String,如果这里写String,是无法和Native的类型进行转换的
/// 像Dart中特有的非基础数据类型, 那么只能使用Navtive类型, 所以这里直接使用Pointer<ffi.Utf8>
typedef Calc = int Function(Pointer<ffi.Utf8>);
void main(List<String> arguments) {
  // 加载动态库
  var dyn = DynamicLibrary.open('./bin/libtest.dll');
  //查找C函数
  /// external F lookupFunction<T extends Function, F extends Function>(
  Calc calc = dyn.lookupFunction<CaluNavtive, Calc>('calu');
  // 首先我们方法需要的是一个pointer<ffi.ut8>类型的参数, 那么我们需要把dart类型的字符串转成这个类型传递进去
  final exp = "1234567".toNativeUtf8();
  //调用函数
  var result = calc(exp);
  print("result======$result");
}
 
输出结果:
 
数组
Dart与C传递数组, 只能使用堆内存, 也就是动态内存, 当我们需要再FFI中使用动态内存分配时,需要依赖一个官方开发的外部包ffi,注意与Dart内部核心包ffi进行区分
 
 外部包package:ffi主要提供了动态内存分配与字符串的处理,是FFI开发中必不可少的依赖,因此不要忘记在pubspec.yaml中配置依赖
- C语言代码:
 
void test_array(int *arr, int len) {
    for (size_t i = 0; i < len; i++)
    {
        /* code */
        printf("%d\n", arr[i]);
    }
    
}
 
- Dart代码:
 
/// C语言函数
/// void test_array(int *arr, int len)
/// ffi函数类型(Navtive)
typedef testArrayNavtive = Void Function(Pointer<Int32>, Int32);
/// Dart函数
typedef testArray = void Function(Pointer<Int32>, int);
/// Dart层创建一个整形数组, 传递给C语言, 然后C语言循环打印
void test_array(List list) {
  // 需要动态分配一个内存
  Pointer<Int32> pArr = ffi.malloc.allocate(sizeOf<Int32>() * list.length);
  //循环list把内容都加入到数组中
  for (var i = 0; i < list.length; i++) {
    // elementAt是做一个便宜指针操作
    pArr.elementAt(i).value = list[i];
  }
  // 加载动态库
  var dyn = DynamicLibrary.open('./bin/libtest.dll');
  //查找C函数
  /// external F lookupFunction<T extends Function, F extends Function>(
  testArray testarrayTemp =
      dyn.lookupFunction<testArrayNavtive, testArray>('test_array');
  testarrayTemp(pArr, list.length);
  // 释放数组
  ffi.malloc.free(pArr);
}
 
- 调用:
 
void main(List<String> arguments) {
  // 调用打印数组
  List list = [1, 2, 67, 8, 23, 88, 90, 32];
  test_array(list);
}
 
- 结果:

 
字符串
字符串的本质就是字节数组, 因此传递字符串就是传递数组。
- C语言函数:
 
// 测试字符串的传递
void test_string(char *str, int len) {
    printf("便利dart传递进来的字符串======%s", str);
    for (size_t i = 0; i < len; i++)
    {
        /* code */
        printf("%d\n", str[i]);
    }
}
 
- Dart函数
 
/// ffi函数
typedef testStringNavtive = Void Function(Pointer<Int8>, Int32);
/// Dart函数
typedef testString = void Function(Pointer<Int8>, int);
void test_string(String message) {
  // 如果传递的字符串是常见, 就不用动态分配内存, 但是如果是一个变量,那么我们需要动态分配内存
  Pointer<Int8> charPointer =
      ffi.malloc.allocate(sizeOf<Int8>() * message.length);
  charPointer = message.toNativeUtf8().cast<Int8>();
  // 加载动态库
  var dyn = DynamicLibrary.open('./bin/libtest.dll');
  //查找C函数
  /// external F lookupFunction<T extends Function, F extends Function>(
  testString tesstringTemp =
      dyn.lookupFunction<testStringNavtive, testString>('test_string');
  tesstringTemp(charPointer, message.length);
  ffi.malloc.free(charPointer);
}
 
- 结果:

 
上述Dart中传递一个字符串给C语言(char *), 我们使用了Pointer来传递的, 但是我看了很多其他的资料, 都是Dart中的Pointer<ffi.utf8>就对饮C语言的char *,但是当我使用时,报如下错误:
 
 不知道如何解决,后续待研究
结构体
目前,FFI中,结构体不能直接 作为函数传递,但可以作为返回值。
- C语言中代码:
 
#include <stdio.h>
#include <stdlib.h>
// 定义一个结构体
typedef struct struct_ts
{
    /* data */
    char * name;
    int age;
} Person;
// 创建一个Person的对象
Person create_person(char *name, int age) {
    Person p = {};
    p.name = name;
    p.age = age;
    return p;
}
// 创建一个Person的指针对象
Person * get_person(char *name, int age) {
    Person *p = (Person *)malloc(sizeof(Person));
    p->name = name;
    p->age = age;
    return p;
}
 
- Dart中创建和C语言中对应的结构体代码:
 
class Person extends Struct {
  external Pointer<ffi.Utf8> name;
  @Int32()
  external int age;
}
 
- Dart中的结构体只能用于和C语言中结构体映射,FFI 会自动生成 
setter/getter 方法,用于中访问内存中Native结构体的字段值; - 如果字段的数据类型不是 
NativeType,则需要使用NativeType进行修饰(如:int, double);否则则不需要(如:Pointer类型则不需要); Struct中的所有字段都必须使用external关键词进行修饰;
注意:不能实例化该 Dart 类,仅用于指向 Native 内存(即结构体是由C分配的,Dart只是持有一个引用而已),如果要实例化该类,则应该由 C 语言提供对应的创建/销毁方法,由Dart调用。
- Dart调用代码:
 
/// Person create_person(char *name, int age)
/// Person * get_person(char *name, int age)
typedef createPersonNavtive = Person Function(Pointer<ffi.Utf8>, Int32);
typedef createPerson = Person Function(Pointer<ffi.Utf8>, int);
typedef getPersonNavtive = Pointer<Person> Function(Pointer<ffi.Utf8>, Int32);
typedef getPerson = Pointer<Person> Function(Pointer<ffi.Utf8>, int);
void test_struct() {
// 加载动态库
  var dyn = DynamicLibrary.open('./bin/libstruct_ts.dll');
  //查找C函数
  /// external F lookupFunction<T extends Function, F extends Function>(
  final create_person =
      dyn.lookupFunction<createPersonNavtive, createPerson>('create_person');
  var get_person =
      dyn.lookupFunction<getPersonNavtive, getPerson>('get_person');
  // 创建一个对象
  var person = create_person('张三'.toNativeUtf8(), 28);
  print('${person.name.toDartString()}-----${person.age}');
  //创建一个person的指针对象
  var p = get_person('李四'.toNativeUtf8(), 18);
  print("${p.ref.name.toDartString()} -----${p.ref.age}");
  // 修改指针对象
  p.ref.name = '王五'.toNativeUtf8();
  p.ref.age = 20;
  print("修改指针对象后====${p.ref.name.toDartString()} -----${p.ref.age}");
// 这里需要释放指针对象,这里建议C语言应该还需要提供一个释放的方法,供Dart调用。而不是直接在dart中直接释放
  ffi.malloc.free(p);
}
 
- 结果:

 create_person方法返回了一个Dart的Person对象,这个是传值而非传引用,所以这个perosn对象相当于C语言中person对象的副本, 是分开的, 修改不会相互影响的。


















