Dart中FFI学习

Dart FFI编程

概述

dart:ffi库可以使用Dart语言调用本地C语言API
,并读取、写入、分配和删除本地内存。FFI是指外部函数接口(Foregin function interface)。其他类似功能的术语包括本地接口和语言绑定。

在这里插入图片描述
具体关于如何将C语言指针映射为Dart中的指针类型的操作, 可以查看Dart FFI API参考文档

关于Dart语言的FFI使用, 可总结以下6个步骤:

  1. 导入ffi
  2. 为被调用的C函数创建Dart Native签名(FFI类型前面,Navtive Type
  3. 为被调用的C函数创建Dart函数前面(与第2不对应)
  4. 加载动态库
  5. 查找C函数,并将C函数指针映射为Dart函数(即将第2、3步创建的签名映射起来)
  6. 调用函数

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对象的副本, 是分开的, 修改不会相互影响的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值