Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
- 《Flutter FFI Dart Native API》
在前面的章节中,介绍结构体在 C 和 Dart 中的相互调用。接下来将介绍类在 C 和 Dart 中的相互调用。
由于 Dart 只能调用 C 风格的符号,并不能调用 C++ 风格的符号,而 class 是 C++ 才有的,因此想要在 Dart 调用 C++ 的类,需要做一些额外的工作才行。
1、基本思路
基本思路如下:
- 类的映射:写一个类,继承
Opaque
,用于表示 C++ 中的类; - 成员变量的映射:通过全局方法实现;
- 成员方法的映射:通过全局方法实现;
2、示例
下面的示例中,演示了如何将 C++ 中的类映射给 Dart 使用。
首先,在 C/C++ 中定义类,然后定义一些全局函数,如下:
#include <malloc.h>
#include <cstring>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
//定义一个类表示怪物
class Monster {
public:
char *name{}; //名称
int32_t hp = 255; //血量
int32_t atk = 10; //攻击力
public:
//攻击目标
void attack(Monster *target) const {
target->hp -= atk;
}
};
//创建一个怪物
DART_API Monster *createMonster(char *name, int32_t hp, int32_t atk) {
auto *monster = (Monster *) malloc(sizeof(Monster));
monster->hp = hp;
monster->atk = atk;
monster->name = name;
return monster;
}
DART_API const char *Monster_getName(Monster *monster) {
return monster->name;
}
DART_API int32_t Monster_getHP(Monster *monster) {
return monster->hp;
}
DART_API void Monster_setHP(Monster *monster, int32_t hp) {
monster->hp = hp;
}
DART_API int32_t Monster_getATK(Monster *monster) {
return monster->atk;
}
DART_API void Monster_setATK(Monster *monster, int32_t atk) {
monster->atk = atk;
}
DART_API void Monster_attack(Monster *monster, Monster *target) {
monster->attack(target);
}
代码说明:
- 上面的 C 代码中,定义了一个名为
Monster
的类,包括name
,hp
,atk
三个属性,和一个attack()
方法; - 对于 类中的成员变量,需要定义对应的 C 风格的函数,如:
Monster_getName
、Monster_getHP
、Monster_setHP
等; - 对应 类中的成员方法,同样需要定义对应的 C 风格的函数,如:
Monster_attack()
。
接着,在 Dart 代码定义相对应的函数类型 和Opaque
类型 如下:
//-------下面是函数定义-------
typedef Native_createMonster = Pointer<Monster> Function(
Pointer<Utf8> name, Int32 hp, Int32 atk);
typedef FFI_createMonster = Pointer<Monster> Function(
Pointer<Utf8> name, int hp, int atk);
typedef Native_getName = Pointer<Utf8> Function(Pointer<Monster> monster);
typedef FFI_getName = Pointer<Utf8> Function(Pointer<Monster> monster);
typedef Native_setHP = Void Function(Pointer<Monster> monster, Int32 hp);
typedef FFI_setHP = void Function(Pointer<Monster> monster, int hp);
typedef Native_getHP = Int32 Function(Pointer<Monster> monster);
typedef FFI_getHP = int Function(Pointer<Monster> monster);
typedef Native_setATK = Void Function(Pointer<Monster> monster, Int32 atk);
typedef FFI_setATK = void Function(Pointer<Monster> monster, int atk);
typedef Native_getATK = Int32 Function(Pointer<Monster> monster);
typedef FFI_getATK = int Function(Pointer<Monster> monster);
typedef Native_attack = Int32 Function(
Pointer<Monster> monster, Pointer<Monster> target);
typedef FFI_attack = int Function(
Pointer<Monster> monster, Pointer<Monster> target);
// ----------- 下面是类的定义 -------------
//一个Monster类,对应于C中的Monster
class Monster extends Opaque {
static FFI_createMonster? createFunc;
static FFI_getName? nameFunc;
static FFI_setHP? setHPFunc;
static FFI_getHP? getHPFunc;
static FFI_setATK? setATKFunc;
static FFI_getATK? getATKFunc;
static FFI_attack? attackFunc;
static init(DynamicLibrary dl) {
createFunc = dl.lookupFunction<Native_createMonster, FFI_createMonster>("createMonster");
nameFunc = dl.lookupFunction<Native_getName, FFI_getName>("Monster_getName");
setHPFunc = dl.lookupFunction<Native_setHP, FFI_setHP>("Monster_setHP");
getHPFunc = dl.lookupFunction<Native_getHP, FFI_getHP>("Monster_getHP");
setATKFunc = dl.lookupFunction<Native_setATK, FFI_setATK>("Monster_setATK");
getATKFunc = dl.lookupFunction<Native_getATK, FFI_getATK>("Monster_getATK");
attackFunc = dl.lookupFunction<Native_attack, FFI_attack>("Monster_attack");
}
//保存由C返回的实例的指针
late Pointer<Monster> _thiz;
late Pointer<Utf8> nativeNameValue;
Monster(String name, int hp, int atk) {
nativeNameValue = name.toNativeUtf8();
//创建Monster实例,并保存实例指针
_thiz = createFunc!(nativeNameValue, hp, atk);
}
String get name => nameFunc!(_thiz).toDartString();
int get hp => getHPFunc!(_thiz);
set hp(value) => setHPFunc!(_thiz, value);
int get atk => getATKFunc!(_thiz);
set atk(value) => setATKFunc!(_thiz, value);
void attack(Monster target) {
attackFunc!(_thiz, target._thiz);
}
void free() {
calloc.free(nativeNameValue);
calloc.free(_thiz);
}
String toDebugString() {
return "{name=$name, hp=$hp, atk=$atk}";
}
}
说明:
- 在 Dart 中,我们定义了一个
Monster
类,继承于Opaqu
e,Opaque
的意思是不透明,即其成员是不暴露的; init()
方法是一个初始化方法,可以提前把我们需要用到的函数提前映射好,方便后续使用;- 在
Monster
类中,我们通过定义setter
/getter
来表示成员变量。它们的实现是调用 C 中的全局方法,把实例(Monster
指针)传给这些全局方法,这样这些方法就知道对哪个实例调用相应的方法了; - 在
Monster
类中,我们定义了与 C 一致的成员方法。它们的实现也是调用 C 中的全局方法; - 最后,在
Monster
实例不使用的时候,可以调用free()
方法进行内存释放。
最后,我们就可以在 Dart 中使用该Monster
类了,使用方法如下:
//加载符号
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//初始化相关函数
Monster.init(nativeApi);
//创建两个Monster
Monster alice = Monster("Alice", 255, 10);
Monster nero = Monster("Nero", 200, 12);
print("before fighting, ${alice.toDebugString()}, ${nero.toDebugString()}");
//让两个Monster相互攻击
for (int i = 0; i < 10; i++) {
if (i.isEven) {
alice.attack(nero);
print("Alice =>>>>> Nero, ${nero.toDebugString()}");
} else {
nero.attack(alice);
print("Alice <<<<<= Nero, ${alice.toDebugString()}");
}
}
print("after fighting, ${alice.toDebugString()}, ${nero.toDebugString()}");
//最后不要忘记释放内存
alice.free();
nero.free();
代码说明:
- Dart 调用
createMonster
创建了Monster
实例之后,需要将实例的指针Pointer<Monster>
保存起来,以便后续使用; - 调用
Monster_setHP
、Monster_attack
等方法时,需要传递Pointer<Monster>
指针; - 最后,由于是在 C 分配的内存,因此 Dart 需要在不使用的时候调用
calloc.free()
释放内存,避免内存泄漏;
3、扩展知识
纯手工编写上面的一个类可能不算什么,但是如果有非常多的 C/C++ 代码需要映射到 Dart 使用时,可能就需要使用一些工具来自动生成代码了。
官方推荐的一个代码自动生成工具:ffigen,地址:https://pub.dev/packages/ffigen.
4、总结
上面介绍了如何把 C++ 中的类映射给 Dart 使用。后面的章节中,将会介绍数组、内存管理等知识,欢迎关注。