Dart FFI 使用教程

一、简介

Dart FFI是可以在Dart Native平台上运行的Dart移动、命令行和服务器应用上通过Dart FFI来调用C代码的一个技术。

简单来说,就是Dart与C互相调用的一种机制。Dart FFI是Dart2.12.0版本后(同时包含在 Flutter 2.0 和以后的版本里),才作为稳定版本发布。

说到底,Dart语言也是因为Flutter使用了它才火起来的,所以Dart FFI技术在Flutter应用中更能发挥它更强大的作用。

参考链接:C interop using dart:ffi | Dart

1.1 解决的问题

  1. 可以同步调用C API,不像Flutter Channel一开始就是异步
  2. 调用C语言更快,不像之前需要通过Native中转(或者改Flutter引擎代码)
  3. 还可以封装替换Flutter Channel达到更快和支持同步的目地

二、常用属性与方法

为了连通Dart与C语言,Dart FFI提供了很多方法,下面介绍一下主要的方法。

2.1 DynamicLibrary(库管理)

2.1.1 open

它可以加载动态链接库

external factory DynamicLibrary.open(String path);

此方法用于加载库文件,如上面我编译C后生成的libsample.dylib文件,我们需要使用此方法来将其加载到DartVM中。需要注意的是,多次调用此方法加载库文件也只会将库文件加载到DartVM中一次。

代码示例

import 'dart:ffi' as ffi;
import 'package:path/path.dart' as path;
var libraryPath = path.join(
        Directory.current.path, 'library', 'build', 'libsample.dylib');
final dylib = ffi.DynamicLibrary.open(libraryPath);
2.1.2 executable

它可用于加载静态链接库

external factory DynamicLibrary.executable();
2.1.3 process
external factory DynamicLibrary.process();

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

2.1.4 lookup

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

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

操作示例

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();

2.2 NativeType(类型映射)

NativeType是在Dart中表示C语言中的数据结构,它不可在Dart中实例化,只能由Native返回。

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

2.3 Pointer(指针)

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

2.3.1 fromAddress

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

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

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

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

获取指针的内存地址

2.3.4 asFunction

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

2.3.5 sizeOf

返回具体类型的内存占用

ffi.sizeOf<ffi.Int64>(); // 8

2.4 malloc(内存管理)

2.4.1 allocate

开辟一块大小byteCount的空间

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

代码示例

Pointer<Uint8> bytes = malloc.allocate<Uint8>(ffi.sizeOf<ffi.Uint8>());
2.4.2 free

释放内存

malloc.free(bytes);

三. 参考示例

3.1 C中实现Dart函数回调

面我们通过示例来了解一下C如何调用Dart函数。

原理: C本身是没有提供调用Dart函数的方法的,但是我们可以在程序启动后通过Dart将函数当做参数传入C中,C中缓存起来Dart的函数指针,就可以在需要的时候实现C调用Dart。

1. 定义dart函数

我们先在Dart上定义一个函数。需要注意的是Dart函数需要是顶级函数或者静态函数才能被调用,否则会报错。

void dartFunction() {
  debugPrint("[Dart]: Dart 函数被调用了");
}
2. 编写c文件

定义一个注册函数 sample.h

void callDart(void (*callback)());

sample.c 实现

void callDart(void (*callback)()) {
    printf("[CPP]: 现在调用Dart函数");
    callback();
}

其中的callback就是接收到的Dart的函数,这里我们为了看效果,就在注册后直接调用Dart函数了。

然后我们将Dart函数转换成Pointer类型,并通过调用C的callDart函数传入到C中。

3. 编写调用代码
late final _callDartPtr = _lookup<
          ffi.NativeFunction<
              ffi.Void Function(
                  ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>>(
      'callDart');
late final _callDart = _callDartPtr.asFunction<
    void Function(ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>();
_callDart(ffi.Pointer.fromFunction(dartFunction));

这里,我们试用结果ffi.Pointer.fromFunction方法将Dart函数转换成C函数指针的Dart映射,然后通过_callDart来调用C的callDart函数。

4. 输出结果
[CPP]: 现在调用Dart函数
[Dart]: Dart 函数被调用了

3.2 Dart调C

1. 无传参无返回值

我们通过一个例子,让Dart来调用C的函数,并在C的函数中输出一句话。

sample.h

void hello_world();

sample.c

void hello_world()
{
    printf("[CPP]: Hello World");
}

ffi_sample.dart

late final _hello_worldPtr =
      _lookup<ffi.NativeFunction<ffi.Void Function()>>('hello_world');
late final _hello_world = _hello_worldPtr.asFunction<void Function()>();
print('[Dart]: ${_hello_world()}');

结果输出

[CPP]: Hello World
[Dart]: null
2. 有返回值

当C有返回值时,可以通过类型转换接收 sample.h

char* getName();

sample.c

char* getName()
{
    return "My name is 大哥大";
}

ffi_sample.dart

late final _getNamePtr =
      _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Int8> Function()>>('getName');
late final _getName =
    _getNamePtr.asFunction<ffi.Pointer<ffi.Int8> Function()>();
print("[Dart]: 有返回值 -> "+_getName().cast<Utf8>().toDartString());

输出结果

[Dart]: 有返回值 -> My name is 大哥大
3. 有传参

利用C的printf函数,实现一个Dart打印函数

sample.h

void cPrint(char *str);

sample.c

void cPrint(char *str) 
{
    printf("[CPP]: %s", str);
    free(str);
}

ffi_sample.dart

late final _cPrintPtr =
      _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Int8>)>>(
          'cPrint');
late final _cPrint =
    _cPrintPtr.asFunction<void Function(ffi.Pointer<ffi.Int8>)>();
_cPrint("我认为这个输出很有意义".toNativeUtf8().cast<ffi.Int8>());

输出结果

[CPP]: 我认为这个输出很有意义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值