Dart FFI 探索(三)| 新思路使用 FFI

在 7 月 28 日 ,dart 在 pub 上发布了ffigen,用于解析 c/cpp 头文件,来自动生成可以对应调用的 dart 函数。

前言

由于 dart 对于底层的 c 语言的有些函数还不支持,比如我遇到的,对文件描述符的操作,还有诸多需要用ffi的地方,例如视频的解码等,拿在 Flutter 上使用 dart 的情况,对于视频解码类,我们一般是通过原生通道,让java/kotlinswift去调c/cpp,我之前的文章也自实现了dart ffi调用ffmpeg解码来实现视屏播放。

而使用原生通道这个思路来调一些c/cpp其实就多此一举了,而且增加了不少的平台代码,不方便移植与适配,除非目前的视频播放器类,还不能完全离开平台的一些支持,所以其他能脱离平台的,我们无需再通过平台插件。

在 Flutter 官方放出 ffi 在 Flutter 上的使用,我就比较关注,并借此完成了一个残废的终端模拟器 😄 。

原始 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代码即可。

更多的是在 linux、macos、android 上。
这种思路一些开发者也早就想到过,包括 Flutter 前不久放出了一个在 Flutter 平台调用一个 win32 api 的例子

有兴趣看这个包—> win32

一个使用 FFI 包装一些最常见的 Win32 API 调用的程序包,使它们可以使用 Dart 代码进行访问,而无需 C 编译器或 Windows SDK。

使用

环境

ubuntu/linux

安装 libclangdev - sudo apt-get install libclang-dev.

Windows

安装 Visual Studio with C++ development support.

安装 LLVM or winget install -e –id LLVM.LLVM.

MacOS

安装 Xcode。

安装 LLVM - brew install llvm

新建文件,并编写 pubspec.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

name: dart_ffi

environment:
sdk: ">=2.8.1 <3.0.0"

dev_dependencies:
ffigen: ^0.1.5
ffi: ^0.1.3

# ffigen:
# name: Dirent
# description: dirent.h 头文件在dart的移植.
# output: 'dirent.dart'
# headers:
# entry-points:
# - '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/dirent.h'

ffigen:
name: Stdio
description: stdio.h 头文件在dart的移植.
output: "stdio.dart"
headers:
entry-points:
- "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stdio.h"

然后执行pub get

我想通过一个项目来解析多个头文件,采用了比较 low 的注释来实现,需要解析哪个,就单独放开哪部分的注释。

更多请移步ffigen

转换

执行pub run ffigen

好像不太顺利,其中的警告就是,c 语言的有些变量是 “_“ 开头的,被转换后在 dart 会限制为私有。

还有一些 error 直接被我注释掉了,是一些不会用到的函数或变量。

使用

我新建了一个 test.dart 文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:convert';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'stdio.dart';

void main() {
Stdio stdio = Stdio(DynamicLibrary.process());
String str = '123\n';
List<int> list = utf8.encode(str);
print(list);
Pointer<Int8> data = allocate<Int8>(count: 4);
for (int i = 0; i < list.length; i++) {
data[i] = list[i];
}
stdio.printf(data);
str = '456\n';
data = Utf8.toUtf8(str).cast<Int8>();
stdio.printf(data);
free(data);
}

printf函数的参数为Pointer<Int8>,可以理解成一维数组,跟 c 语言的charint[]都是差不多的,而 c 语言的chardart中对应类型是Pointer<Utf8>,其实都是 8 位的数组,所以我使用了两种传参方式,第二种要更方便。

还有需要注意,如果你的程序不是立马就结束的,比如一个 Flutter app ,请在任何使用完allocate函数后对它及时的释放,例如Utf8.toUtf8这个函数,内部也使用的allocate函数。

运行结果


这样就在dart完成了c语言printf函数的调用。

其实按照c语言的预编译思想,被调用到函数的实现在编译期间会copy到当前文件,所以你可以根据自己使用到的去转换后的dart删减来节省编译后的内存。

结语

  • 由于我了解到这个,也是通过一位也在使用 Flutter 开发终端模拟器的大佬那儿得知,而他是打算重构完整的终端模拟器,重构所有的终端序列。
  • 但对于这种突然发布的 dart 包,大部分人是不会知道它的,除非被正式列入了一些官网,但是的确很有用,所以,推荐给大家,这种方案,个人感觉真的很不错。
  • 放假开始兼职,学习时间真的少太多,不过实战经验的确是增加了。
作者

梦魇兽

发布于

2020-08-04

更新于

2023-03-11

许可协议

评论