Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
- 《Flutter FFI Dart Native API》
在前面的章节中,介绍了基础数据类型、字符串类型。接下来将详细介绍结构体类型。
1、介绍
结构体一般用来组织对象的属性。在 C 语言中使用 struct 关键字来定义结构体。Dart 语言通过 Struct
来表示 C 语言中的 struct
。
Struct
的代码如下:
abstract class Struct extends NativeType {
@pragma("vm:entry-point")
final Object _addressOf;
/// Construct a reference to the [nullptr].
///
/// Use [StructPointer]'s `.ref` to gain references to native memory backed
/// structs.
Struct() : _addressOf = nullptr;
Struct._fromPointer(this._addressOf);
}
2、使用说明
如果需要在 Dart 中使用 C 中的结构体,我们可以在 Dart 中继承 Struct
类,编写一个属性与 C 中的结构体相类一致的类。
下面演示如何在 Dart 中映射 C 中的结构体。
首先,在 C 中定义一个结构体:
typedef struct {
int a;
float b;
void* c;
} my_struct;
然后,在 Dart 中定义一个继承于 Struct
的类,类的字段和 C 中的字段一致 :
class MyStruct extends Struct {
@Int32
external int a;
@Float
external double b;
external Pointer<Void> c;
}
代码说明:
- 定义与 C 结构体相对应的字段(注意顺序一致),FFI 会自动生成
setter
/getter
方法,用于中访问内存中 Native 结构体的字段值; - 如果字段的数据类型不是
NativeType
,则需要使用NativeType
进行修饰(如:int
,double
);否则则不需要(如:Pointer
类型则不需要); Struct
中的所有字段都必须使用external
关键词进行修饰;- 不能实例化该 Dart 类,仅用于指向 Native 内存(即结构体是由C分配的,Dart只是持有一个引用而已),如果要实例化该类,则应该由 C 语言提供对应的创建/销毁方法,由Dart调用。
3、使用示例
下面这个示例演示了如何使用结构体。
首先,在 C 中定义结构体和相关函数:
#include <stdint.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
//定义一个名为Person的结构体
typedef struct {
const char *name;
int age;
float height;
double weight;
} Person;
//声明一个全局变量
DART_API Person g_alice{
"Alice\n", 18, 170.0, 100.0};
//create函数,用于构建一个Person结构体
DART_API Person createPerson(const char *name, int age, float height, double weight) {
return Person{
name, age, height, weight};
}
然后,在 Dart 中定义相对应的结构体以及函数类型:
///对应于C语言中的Person结构体
/// ...
/// typedef struct {
/// const char* name;
/// int age;
/// float height;
/// double weight;
/// } Person;
/// ...
class Person extends Struct {
external Pointer<Utf8> name;
@Int32()
external int age;
@Float()
external double height;
@Double()
external double weight;
}
typedef Native_createPerson = Person Function(
Pointer<Utf8> name, Int32 age, Float height, Double weight);
typedef FFI_createPerson = Person Function(
Pointer<Utf8> name, int age, double height, double weight);
最后,在 Dart 中编写代码调用 C 的结构体:
//加载库
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//查找全局变量
final Pointer<Person> pAlice = nativeApi.lookup<Person>("g_alice");
final Person alice = pAlice.ref; //获取指针所指向的对象
//打印数据
print("alice.name=${alice.name.toDartString()}");
print("alice.age=${alice.age}");
print("alice.height=${alice.height}");
print("alice.weight=${alice.weight}");
//查找createPerson函数
FFI_createPerson createFunc = nativeApi
.lookupFunction<Native_createPerson, FFI_createPerson>("createPerson");
//创建Person
final Pointer<Utf8> nativeName = "Abigail".toNativeUtf8();
Person abigail = createFunc("Abigail".toNativeUtf8(), 20, 180.0, 120.0);
//toNativeUtf8创建的对象需要释放
calloc.free(nativeName);
//打印
print("abigail.name=${abigail.name.toDartString()}");
print("abigail.age=${abigail.age}");
print("abigail.height=${abigail.height}");
print("abigail.weight=${abigail.weight}");
// 输出结果:
// I/flutter (31858): alice.name=Alice
// I/flutter (31858): alice.age=18
// I/flutter (31858): alice.height=170.0
// I/flutter (31858): alice.weight=100.0
// I/flutter (31858): abigail.name=Abigail
// I/flutter (31858): abigail.age=20
// I/flutter (31858): abigail.height=180.0
// I/flutter (31858): abigail.weight=120.0
说明:
- 上面的示例中,可以从 C 中的全局变量获取结构体,也可以从 C 函数创建结构体;
- 上面的
createFunc()
函数返回了一个 DartPerson
对象,这个是传值而非传引用,因此在 Dart 中改变了abigail
的值,不会影响 C 对象的字段值。
4、在 C 中更改结构体的字段值
有时候需要在 C 中更改结体的字段值,例如,把 age 乘以 2,这个时候,需要使用指针类型才行(传值与传引用的区别)。
下面的示例,演示了如果在 C 中更改结构体的字段值。
首先,在 C 中定义相关函数:
#include <malloc.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API Person *createPersonPointer(const char *name, int age, float height, double weight) {
auto *person = (Person *) malloc(sizeof(Person));
person->name = name;
person->age = age;
person->height = height;
person->weight = weight;
return person;
}
DART_API void doubleAge(Person *person) {
person->age *= 2;
}
然后,在 Dart 中定义想对应的函数类型:
typedef Native_createPersonPointer = Pointer<Person> Function(
Pointer<Utf8> name, Int32 age, Float height, Double weight);
typedef FFI_createPersonPointer = Pointer<Person> Function(
Pointer<Utf8> name, int age, double height, double weight);
typedef Native_doubleAge = Void Function(Pointer<Person>);
typedef FFI_doubleAge = void Function(Pointer<Person>);
最后,在 Dart 中编写测试代码:
//查询函数
FFI_createPersonPointer createPointerFunc = nativeApi.lookupFunction<
Native_createPersonPointer,
FFI_createPersonPointer>("createPersonPointer");
FFI_doubleAge ageFunc = nativeApi
.lookupFunction<Native_doubleAge, FFI_doubleAge>("doubleAge");
//创建Person
final Pointer<Utf8> nativeName = "Jim".toNativeUtf8();
final Pointer<Person> jim = createPointerFunc(nativeName, 16, 175.0, 124.3);
//调用C方法更改年龄
print("before jim.age=${jim.ref.age}");
ageFunc(jim);
print("after jim.age=${jim.ref.age}");
//最后不要忘记释放内存
calloc.free(nativeName);
calloc.free(jim);
//输出结果:
//I/flutter ( 1379): before jim.age=16
//I/flutter ( 1379): after jim.age=32
说明:
- 由于
createPointerFunc()
此时返回了一个 结构体指针,所以,它是传引用而非传值,因此可以在 C 中更改该结构体的字段值; - 由于
createPointerFunc()
返回的指针,因此在不用的时候,需要释放内存。
5、总结
上面介绍了结构体在 C 和 Dart 中的映射方法,同时还介绍了传值与传引用的区别。后面的章节中,将会介绍如何把 C++ 中的类映射给 Dart 使用,欢迎关注。