05、Flutter FFI 结构体

 
Flutter FFI 学习笔记系列

  1. 《Flutter FFI 最简示例》
  2. 《Flutter FFI 基础数据类型》
  3. 《Flutter FFI 函数》
  4. 《Flutter FFI 字符串》
  5. 《Flutter FFI 结构体》
  6. 《Flutter FFI 类》
  7. 《Flutter FFI 数组》
  8. 《Flutter FFI 内存管理》
  9. 《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() 函数返回了一个 Dart Person 对象,这个是传值而非传引用,因此在 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 使用,欢迎关注。
  
  
 

猜你喜欢

转载自blog.csdn.net/eieihihi/article/details/119219348