Dart:ffi上手二:C语言基本函数的调用

Dart 对 C 语言的调用我个人感觉比 Java 的 jni 要简单得多,用不着还在 C 语言中创建出对应的函数才能调用,Dart 都能直接调用 C 语言原有的那些函数,因为 java jni 调用 C 语言的完整流程我也上手写过,Dart 还是简易多了,不用打乱已有 C 语言的结构

上一篇
Dart:ffi 上手一:So 库编译篇

Dart:ffi 思想

在使用 ffi 的时候,只需要把 C 语言或者 C++语言的方法或者函数与 Dart 中一一对应起来

yaml 导入 ffi 包

在上手 ffi 了半天,官方的 example 弄到 Flutter 项目报错,提示 import ‘package:ffi/ffi.dart’;没有找到,我还以为是 Flutter 中的 Dart 没有这个包,因为我想到最初 Dart 支持 ffi 的时候 Flutter 中的 Dart Sdk 没有,然后自己**了半天,发现这是个 Dart Packages
所以

pubspec.yaml

1
ffi: ^0.1.3

调用 C 语言方法(void)

由于是在 Flutetr 中使用 ffi,并不是在纯 Dart 中,所以 C 语言的任何 Printf 都是看不到的,不会反馈到 Flutter 的控制台

一个简单的 C 语言方法

1
2
3
void hello_word(){
printf("hello word");
}

编译好后,重新调试一下 App,才会更新新编译的 So 库
需要这两个包的导入

Dart:

1
2
import 'dart:ffi';
import 'package:ffi/ffi.dart';

参照官方,如果要调用一个 C 语言函数写上两个 typedef,这的确会带来很多方便

1
typedef hello_word=Void Function();

这里一定要注意,Void 的 V 是 V 不是 v,这部分声明 C 语言中的函数,涉及到的所有类型必须全部用 ffi 中对应的 NativeType,而这个原生函数的声明后不是拿来调用的,切记,不是拿来调用的,是要用它跟下面 Dart 的方法绑定起来
接下来

1
typedef HelloWord=void Function();

这就是 Dart 中需要调用的方法,也有坑,这部分的原则就是,如果 Dart 有原生的那种类型就用 dart 已经有的类型,如果没有,还是得用 NativeType

举个例子
原生函数

1
int add(int a,int b)

这个时候对应的 dart 函数该怎么写呢,原生的 int,dart 也有,所以 dart 韩式也是返回 int,形参中也都是 int,所以

1
typedef Add=int Function(int a,int b);

涉及到其他类型下面说,接下来

1
final dylib = DynamicLibrary.open("libterm.so");

这行的作用就是找到对应的 so 库,记住这儿是相对路径,找的就是打包进 apk 中的 so,与 libflutter.so 同级
它的作用跟 java 中 jni 的 loadlibrary 类似
导入了一个动态库

找到原生方法的指针

1
2
final helloWordPointer = dylib
.lookup<NativeFunction<hello_word>>('hello_word');

用了刚才的 dylib 对象的 lookup 方法,中间的一堆<<>>都是泛型,NativeFunction 的泛型就是第一个 typedef,也是第一个 typedef 唯一用到的地方,最后 lookup 需要传入一个字符串,这个字符串就语言中的方法或者函数的名字,它返回的是一个 Pointer 类型,就是指针的意思,dart 用了自己对应的类型来表示原生中的一些类型
然后定义并且初始化第二个 typedef 的方法

1
HelloWord helloWord = helloWordPointer.asFunction<HelloWord>();

通过 Ponter 对象的 asFunction()方法初始化了这个 helloWord 对象,这个 helloWord 就是就是一个普通的 void
调用它

1
helloword();

这部分的打印是看不到的

调用 C 语言带返回值的 int 函数

C:

1
2
3
int add(int a,int b){
return a+b;
}

上面也提到了这个

两个 typedef

1
2
typedef add_func=Int32 Function(Int32 a,Int32 b);
typedef Add=int Function(int a,int b);

执行代码

1
2
3
4
5
6
7
var path = 'libterm.so';
final dylib = DynamicLibrary.open(path);
final addPinter = dylib.lookup<NativeFunction<add_func>>("add");
Add add = addPinter.asFunction<Add>();
print("${add(1,2)}");
print("${add(3,5)}");
print("${add(6,9)}");

控制台输出,避免内存泄漏,所有的指针类型用完需要调用

1
free(pointer);

传 char 到 C 语言,并获取返回的 char

这个相对来说难一点也麻烦一点,不过 ffi 官方文档有这个例子,挺不错的,我直接拿来用,dart 中对应 c char 的类型是 Utf8,
Utf8.toUtf8(“”)返回指针,即对应原生的 char *,用这玩意注意释放,不要把它当匿名对象使用,把它保存在一个对象里面,不然不好控制内存的释放

Utf8.fromUtf8(pointer)返回 str,切记,这个坑就是它只能转换最后以\0 结尾的字符

补上自己的注释

1
2
3
4
5
6
7
8
9
10
11
12
13
char *reverse(char *str, int length)
{
//动态申请length+1个长度的字符空间
char *reversed_str = (char *)malloc((length + 1) * sizeof(char));
//简单的倒置算法
for (int i = 0; i < length; i++)
{
reversed_str[length - i - 1] = str[i];
}
//这行很关键,切记,让它最后一个为\0
reversed_str[length] = '\0';
return reversed_str;
}

dart 部分声明

1
2
typedef reverse_func = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length);
typedef Reverse = Pointer<Utf8> Function(Pointer<Utf8> str, int length);

调用

1
2
3
4
final reversePointer = dylib.lookup<NativeFunction<reverse_func>>('reverse');
final reverse = reversePointer.asFunction<Reverse>();
final reversedMessage = Utf8.fromUtf8(reverse(Utf8.toUtf8('backwards'), 9));
print('$reversedMessage');

注意官方示例的所有指针全部没有释放,Utf8.toUtf8(‘’)还有 dylib.lookup()方法都会调用 allocate 函数,这个就会让原生给你动态分配空间,会执行 malloc 函数,我也看到一些大佬的帖子提到不释放是会内存泄漏的

OK 这篇结束,任何问题欢迎评论区留言,下一篇是传 char **和原生遍历这个 char **

作者

梦魇兽

发布于

2020-02-23

更新于

2023-03-11

许可协议

评论