06、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》
      
     

  在前面的章节中,介绍结构体在 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_getNameMonster_getHPMonster_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 类,继承于 Opaque,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_setHPMonster_attack 等方法时,需要传递 Pointer<Monster> 指针;
  • 最后,由于是在 C 分配的内存,因此 Dart 需要在不使用的时候调用 calloc.free() 释放内存,避免内存泄漏;
     

3、扩展知识

  纯手工编写上面的一个类可能不算什么,但是如果有非常多的 C/C++ 代码需要映射到 Dart 使用时,可能就需要使用一些工具来自动生成代码了。
  
  官方推荐的一个代码自动生成工具:ffigen,地址:https://pub.dev/packages/ffigen.
  
 

4、总结

  
  上面介绍了如何把 C++ 中的类映射给 Dart 使用。后面的章节中,将会介绍数组、内存管理等知识,欢迎关注。
  
  
 

猜你喜欢

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