Flutter 使用 C、C++ 代码库,突破性能敏感和“底层”库移植能力

Flutter 主要使用 Dart 语言,也就是说我们只要掌握了 Dart 语言调用 C/C++ 的方法,就知道了如何在 Flutter 中调用 C/C++ 编写的功能了。

Dart 的移动端、命令行和服务端应用所运行的 Dart 原生平台,均可以使用 dart:ffi 库调用原生的 C 语言 API,用于读、写、分配和销毁原生内存。 FFI(Foreign Function Interface)指的是外部函数接口。

一、dart:ffi

用于与 C 编程语言互操作的外部函数接口。

1.1 Class

Abi 应用程序二进制接口 (ABI)。
AbiSpecificInteger 所有 Abi-specific 整数类型的 supertype(超类型)。
AbiSpecificIntegerMapping 映射 AbiSpecificInteger 的 subtype(子类型)。
Allocator 管理 native 堆上的内存。
NativeType NativeType 的子类型表示 C 中的 native 类型。
Array<T extends NativeType> 固定大小的数组 T
Dart_CObject 不透明,不暴露其成员。
DartRepresentationOf 表示与 NativeType 对应的 Dart 类型。
DefaultAsset 指定当前库的默认 asset ID 的注解。
DynamicLibrary 动态加载的 native 库。
Finalizable 标记不应过早销毁对象的接口。
Handle 代表 Dart_Handle 来自 C 的 dart_api.h
Native<T> 将外部声明绑定到其 native 实现的注解。
NativeApi 用于从 Dart 代码或通过 C 代码(使用 dart_api_dl.h)访问 Dart VM API。
NativeCallable<T extends Function> 一个 native 可调用对象,用于监听对 native 函数的调用。
NativeFinalizer 可以附着到 Dart 对象的 native finalizer。
NativeFunction<T extends Function> 表示 C 中的函数类型。
Opaque Opaque 的 subtype 表示 C 中的不透明类型。
Packed 用于在 Struct subtype 上指定注解,以表明其成员需要进行打包。
Pointer<T extends NativeType> 表示指向 native C 内存的指针,无法扩展。
SizedNativeType 具有已知大小的 NativeType
Struct 所有 FFI 结构体类型的 supertype。
Union 所有 FFI 联合体类型的 supertype。
VarArgs<T extends Record> C 中传递的可变参数的类型。

Bool 表示 C 中的 native 布尔值。
Char 表示 C 中的 native char 型。

Double 表示 C 中的 native 64 位双精度浮点数。
Float 表示 C 中的 native 32 位单精度浮点数。

Int 表示 C 中的 native int 型。
Int8 表示 C 中的 native 有符号 8 位整数。
Int16 表示 C 中的 native 有符号 16 位整数。
Int32 表示 C 中的 native 有符号 32 位整数。
Int64 表示 C 中的 native 有符号 64 位整数。
Uint8 表示 C 中的 native 无符号 8 位整数。
Uint16 表示 C 中的 native 无符号 16 位整数。
Uint32 表示 C 中的 native 无符号 32 位整数。
Uint64 表示 C 中的 native 无符号 64 位整数。

UintPtr 表示 C 中的 native uintptr_t 型(指针)。
IntPtr 表示 C 中的 native intptr_t 型(指针)。

Long 表示 C 中的 native long int 型,又称 long 类型。
LongLong 表示 C 中的 native long long 类型。

Short 表示 C 中的 native short 型。
SignedChar 表示 C 中的 native signed char 型。
Size 表示 C 中的 native size_t 型。
Void 表示 C 中的 native void 类型。
WChar 表示 C 中的 native wchar_t 类型。

UnsignedChar 表示 C 中的 native unsigned char 类型。
UnsignedShort 表示 C 中的 native unsigned short 类型。
UnsignedInt 表示 C 中的 native unsigned int 类型。
UnsignedLong 表示 C 中的 native unsigned long int 类型,又称 unsigned long 类型。
UnsignedLongLong 表示 C 中的 native unsigned long long 类型。

1.2 Extension

AbiSpecificIntegerArray on Array<T>AbiSpecificInteger 数组(Array)的索引方法进行边界检查。
AbiSpecificIntegerPointer on Pointer<T> 针对类型参数 AbiSpecificInteger 的指针(Pointer)扩展。
AllocatorAlloc on Allocator 扩展 Allocator 以提供 NativeType 的分配。

ArrayAddress on Array<T>Array<T> 添加通过 address property 获取底层数据的内存地址的扩展。
ArrayArray on Array<Array<T>> 对数组(Array)的数组(Array)进行索引方法的边界检查。

BoolAddress on boolbool 添加通过 address property 获取底层数据的内存地址的扩展。
BoolArray on Array<Bool> Bool 数组(Array)的边界检查索引方法。
BoolPointer on Pointer<Bool> 针对类型参数 Bool 的指针(Pointer)扩展。

DoubleAddress on doubledouble 添加通过 address property 获取底层数据的内存地址的扩展。
DoubleArray on Array<Double> Double 数组(Array)的边界检查索引方法。
DoublePointer on Pointer<Double> 针对类型参数 Double 的指针(Pointer)扩展。

DynamicLibraryExtension on DynamicLibrary 不能动态调用的方法。

Float32ListAddress on Float32ListFloat32List 添加通过 address property 获取底层数据的内存地址的扩展。
Float64ListAddress on Float64ListFloat64List 添加通过 address property 获取底层数据的内存地址的扩展。
Int8ListAddress on Int8ListInt8List 添加通过 address property 获取底层数据的内存地址的扩展。
Int16ListAddress on Int16ListInt16List 添加通过 address property 获取底层数据的内存地址的扩展。
Int32ListAddress on Int32ListInt32List 添加通过 address property 获取底层数据的内存地址的扩展。
Int64ListAddress on Int64ListInt64List 添加通过 address property 获取底层数据的内存地址的扩展。

FloatArray on Array<Float> Float 数组(Array)的边界检查索引方法。
FloatPointer on Pointer<Float> 针对类型参数 Float 的指针(Pointer)扩展。

Int8Array on Array<Int8> Int8 数组(Array)的边界检查索引方法。
Int16Array on Array<Int16> Int16 数组(Array)的边界检查索引方法。
Int32Array on Array<Int32> Int32 数组(Array)的边界检查索引方法。
Int32Array on Array<Int64> Int64 数组(Array)的边界检查索引方法。

IntAddress on intint 添加通过 address property 获取底层数据的内存地址的扩展。

Int8Pointer on Pointer<Int8> 针对类型参数 Int8 的指针(Pointer)扩展。
Int16Pointer on Pointer<Int16> 针对类型参数 Int16 的指针(Pointer)扩展。
Int32Pointer on Pointer<Int32> 针对类型参数 Int32 的指针(Pointer)扩展。
Int64Pointer on Pointer<Int64> 针对类型参数 Int64 的指针(Pointer)扩展。

NativeFunctionPointer on Pointer<NativeFunction<NF>> 针对类型参数 NativeFunction 的指针(Pointer)扩展。
NativePort on SendPort Dart_PortSendPort 检索 native 的扩展。

PointerArray on Array<Pointer<T>> Pointer 数组(Array)的边界检查索引方法。
PointerPointer on Pointer<Pointer<T>> 针对类型参数 Pointer 的指针(Pointer)扩展。

Uint8Array on Array Uint8 数组(Array)的边界检查索引方法。
Uint16Array on Array Uint16 数组(Array)的边界检查索引方法。
Uint32Array on Array Uint32 数组(Array)的边界检查索引方法。
Uint64Array on Array Uint64 数组(Array)的边界检查索引方法。

Uint8Pointer on Pointer 针对类型参数 Int8 的指针(Pointer)扩展。
Uint16Pointer on Pointer 针对类型参数 Int16 的指针(Pointer)扩展。
Uint32Pointer on Pointer 针对类型参数 Int32 的指针(Pointer)扩展。
Uint64Pointer on Pointer 针对类型参数 Int64 的指针(Pointer)扩展。

Uint8ListAddress on Uint8ListUint8List 添加通过 address property 获取底层数据的内存地址的扩展。
Uint16ListAddress on Uint16ListUint16List 添加通过 address property 获取底层数据的内存地址的扩展。
Uint32ListAddress on Uint32ListUint32List 添加通过 address property 获取底层数据的内存地址的扩展。
Uint64ListAddress on Uint64ListUint64List 添加通过 address property 获取底层数据的内存地址的扩展。

StructAddress on TT(T extends Struct) 添加通过 address property 获取底层数据的内存地址的扩展。
StructArray on Array<T> Struct 数组(Array)的边界检查索引方法。
StructPointer on Pointer<T> 针对类型参数 Struct 的指针(Pointer)扩展。

UnionAddress on T T(T extends Union) 添加通过 address property 获取底层数据的内存地址的扩展。
UnionArray on Array Union 联合体(Array)的边界检查索引方法。
UnionPointer on Pointer 针对类型参数 Union 的指针(Pointer)扩展。

1.3 Property

nullptr → Pointer 表示指向 C 语言 native 内存中对应于“NULL”的指针,例如地址为 0 的指针。

1.4 Function

sizeOf() → int 由 native 类型 T 占用的字节数。

1.5 Typedef

Dart_NativeMessageHandler = Void Function(Int64, Pointer<Dart_CObject>) Dart 和 native 之间通信的一个关键组件,主要用于处理 Dart 和 native 之间的消息传递。
NativeFinalizerFunction = NativeFunction<Void Function(Pointer token)> NativeFinalizer 的 native 函数类型。

二、常见用法

接下来先通过一个 Hello World 开始学习 FFI 如何使用,接下来会继续介绍函数入参中使用指针、函数返回值返回指针、结构体和回调函数四种使用场景。

2.1 Hello World

  1. 导入 dart:ffi
import 'package:ffi/ffi.dart';
  1. 用 C 函数的 FFI 类型签名定义一个 typedef
typedef HelloWorldFunc = Void Function();
  1. 为调用 C 函数的变量定义一个 typedef
typedef HelloWorldDart = void Function();
  1. 加载包含 C 函数的动态库。
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
  1. 获取该 C 函数的引用,接着将其赋予变量。这段代码使用了步骤 2 和 3 定义的 typedef,以及步骤 4 定义的动态库变量。
    // 查找 C 函数 hello_world
    final HelloWorldDart hello = nativeLib
        .lookup<NativeFunction<HelloWorldFunc>>('hello_world')
        .asFunction();

对应的 libnativelib.so 内 native 实现如下:

#include <android/log.h>

#define ATTRIBUTES extern "C" __attribute__((visibility("default"))) __attribute__((used))
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "DemoNative", __VA_ARGS__)

ATTRIBUTES void hello_world() {
    LOGD("Hello world from native!!!");
}

hello_world 函数使用 LOGD 打印,实际上是使用了 android 平台的 native __android_log_print 函数。

关于 ATTRIBUTES 宏的说明

这段 #define 预处理指令定义了一个名为 ATTRIBUTES 的宏,它展开后是 extern "C" __attribute__((visibility("default"))) __attribute__((used))。下面来详细解释一下这些部分的含义:

(1) extern "C":这是 C++ 语言中的声明,用于指定接下来的函数或变量使用 C 语言的链接规范。在 C++ 中,函数名在编译时会进行名称修饰(name mangling),以支持函数重载等特性。而 C 语言没有名称修饰的概念。使用 extern "C" 可以确保在 C++ 代码中调用 C 语言编写的函数,或者让 C 语言代码能够调用 C++ 中用 extern "C" 声明的函数。

(2) __attribute__((visibility("default"))):这是 GCC 编译器特有的属性(attribute),用于指定符号(函数、变量等)的可见性。visibility("default") 表示该符号具有默认的可见性,即对整个程序可见。这在构建共享库时特别有用,可以控制哪些符号可以被外部代码访问。例如,在共享库中,某些函数可能不希望被外部直接调用,就可以通过设置不同的可见性属性来隐藏它们。

(3) __attribute__((used)):同样是 GCC 编译器特有的属性,它告诉编译器即使某个符号看起来没有被使用(例如,某个函数没有在代码中被显式调用),也不要对其进行优化(例如,不要将其从目标文件中移除)。这在某些情况下很有用,比如当你需要确保某个函数或变量在目标文件中存在,即使它的调用是在运行时通过动态链接或其他间接方式实现的。

  1. 调用 C 函数。
    hello();

打印结果:

D/DemoNative(12514): Hello world from native!!!

2.2 普通入参函数

我们先来看 native 部分代码,简单的实现了加法和减法,native 函数入参都是普通 int 类型。

ATTRIBUTES int add_from_native(int a, int b) {
    return a + b;
}

ATTRIBUTES int sub_from_native(int a, int b) {
    return a - b;
}

现在回到 Flutter 的 Dart 代码中。首先为调用 C 函数的变量定义一个 NativeIntFunc,接着为调用 C 函数的变量定义一个 DartIntFunc。在 fromNative 函数中打开动态库,查找 C 函数 add_from_nativesub_from_native,最后调用它们 dart 层的代表。

typedef NativeIntFunc = Int32 Function(Int32 a, Int32 b);
typedef DartIntFunc = int Function(int a, int b);

static void fromNative(int a, int b) {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    // 查找 C 函数 add_from_native
    final DartIntFunc add = nativeLib
        .lookup<NativeFunction<NativeIntFunc>>("add_from_native")
        .asFunction();

    final DartIntFunc sub = nativeLib
        .lookup<NativeFunction<NativeIntFunc>>("sub_from_native")
        .asFunction();
    int result = add(a, b);
    print('dart -> add_from_native result=$result');
    result = sub(a, b);
    print('dart -> sub_from_native result=$result');
}

打印结果:

I/flutter (12514): dart -> add_from_native result=1005
I/flutter (12514): dart -> sub_from_native result=995

2.3 函数入参中使用指针

native 函数第一个入参函数我们使用了指针。

ATTRIBUTES int div_from_native(int *a, int b) {
    return (*a) / b;
}

唯一的主要区别在于在 dart 层创建一个指针,这调用了 callocimport 语句多了一条(import 'package:ffi/ffi.dart'),当然指针在使用完后还要释放(free)。

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

typedef DivFuncNative = Int32 Function(Pointer<Int32> a, Int32 b);
typedef DivDart = int Function(Pointer<Int32> a, int b);

static void fromNative(int a, int b) {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    final div = nativeLib
        .lookupFunction<DivFuncNative, DivDart>('div_from_native');

    // 创建一个指针
    final p = calloc<Int32>();
    // 在地址中放置一个值
    p.value = 20;
    result = div(p, 5);
    // 释放已分配的内存
    calloc.free(p);
    print('dart -> div_from_native = $result');
}

打印结果:

I/flutter (12514): dart -> div_from_native = 4

2.4 函数返回值返回指针

native 函数的第一个入参我们使用了指针,并在函数体内调用了 malloc 分配内存,所以需要 dart 再次调用到 native 去释放这块内存。

ATTRIBUTES int *mul_from_native(int a, int b) {
    // Allocates native memory in C.
    int *mult = (int *) malloc(sizeof(int));
    *mult = a * b;
    return mult;
}

ATTRIBUTES void free_pointer(int *int_pointer) {
    // Free native memory in C which was allocated in C.
    free(int_pointer);
}

区别在于我们获取返回结果的方式,从一个 Pointer 中去取。另外我们还需要释放这块 native 申请的内存,通过调用 free_pointer 实现。

typedef MultiplyFuncNative = Pointer<Int32> Function(Int32 a, Int32 b);
typedef MultiplyDart = Pointer<Int32> Function(int a, int b);

typedef FreePointerFuncNative = Void Function(Pointer<Int32> a);
typedef FreePointerDart = void Function(Pointer<Int32> a);

static void fromNative(int a, int b) {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    final multiply = nativeLib
        .lookupFunction<MultiplyFuncNative, MultiplyDart>('mul_from_native');
    final resultPointer = multiply(3, 5);
    // 到指定地址获取结果
    result = resultPointer.value;
    print('dart -> mul_from_native = $result');

    // 释放已分配的内存,因为内存是在C语言中分配的
    final freePointerPointer =
    nativeLib.lookup<NativeFunction<FreePointerFuncNative>>('free_pointer');
    final freePointer = freePointerPointer.asFunction<FreePointerDart>();
    freePointer(resultPointer);
}

打印结果:

I/flutter (12514): dart -> mul_from_native = 15

2.5 结构体

native 函数创建了一个 Coordinate 结构体并返回。

typedef struct Coordinate {
    double latitude;
    double longitude;
} Coordinate;

ATTRIBUTES Coordinate create_coordinate(double latitude, double longitude) {
    Coordinate coordinate;
    coordinate.latitude = latitude;
    coordinate.longitude = longitude;
    return coordinate;
}

dart 层定义了一个对等的结构体 Coordinate,这里值得注意的是这种实现方式无需在 dart 调用释放内存的操作。和 native 栈上分配是一致的,无需手动释放。

final class Coordinate extends Struct {
  ()
  external double latitude;

  ()
  external double longitude;
}

typedef CreateCoordinateNative = Coordinate Function(
    Double latitude, Double longitude);
typedef CreateCoordinateDart = Coordinate Function(
    double latitude, double longitude);

static void fromNative() {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    final createCoordinate =
    nativeLib.lookupFunction<CreateCoordinateNative, CreateCoordinateDart>(
        'create_coordinate');
    final coordinate = createCoordinate(3.5, 4.6);
    print('dart -> Coordinate is lat ${coordinate.latitude}, long ${coordinate.longitude}');
}

打印结果:

I/flutter (12514): dart -> Coordinate is lat 3.5, long 4.6

2.6 回调函数

native 创建了一个接收函数指针 Callback 作为入参的函数。

typedef int (*Callback)(int);

ATTRIBUTES int native_cb(Callback cb) {
    int value = 100;
    int result = cb(value);
    LOGD("Native function received result from callback: %d\n", result);
    return result;
}

int dartCallback(int value) {...}:定义了一个 Dart 回调函数,接收一个整数并将其乘以 2 后返回。

int result = nativeFunction(Pointer.fromFunction<NativeCallback>(dartCallback, 0).address);:将 Dart 回调函数转换为指针,并传递给 nativeCb,这里的 0 作为异常返回值,当调用出现异常时会返回这个值。

typedef NativeCallback = Int32 Function(Int32);
typedef DartCallback = int Function(int);

// 定义 Dart 中的回调函数
int dartCallback(int value) {
  print('Dart callback received value: $value');
  return value * 2;
}

static void fromNative() {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    final nativeCb = nativeLib
        .lookupFunction<NativeCallback, DartCallback>('native_cb');
    // 调用动态库中的函数,并传递回调函数
    int resultVal = nativeCb(Pointer.fromFunction<NativeCallback>(dartCallback, 0).address);
    print('dart -> Result from native function: $resultVal');
}

打印结果:

I/flutter (15141): Dart callback received value: 100
D/DemoNative(15141): Native function received result from callback: 200
I/flutter (15141): dart -> Result from native function: 200

2.7 处理字符串

native 函数的入参使用了 char 指针,指向了一个字符串,函数内部做了字符串反转的动作。

ATTRIBUTES char *reverse_native(char *str, int length)
{
    // Allocates native memory in C.
    char *reversed_str = (char *)malloc((length + 1) * sizeof(char));
    for (int i = 0; i < length; i++)
    {
        reversed_str[length - i - 1] = str[i];
    }
    reversed_str[length] = '\0';
    return reversed_str;
}

ATTRIBUTES void free_string_native(char *str)
{
    // Free native memory in C which was allocated in C.
    free(str);
}

首先将 hello 字符串转化为 native Utf8,接着传递给 reverse,这里需要注意使用 calloc free 去释放 helloStrUtf8。由于 reverse 返回的 Pointer<Utf8> 是个指针,需要 dart 层调用 toDartString 转换为 dart 的 String。最后要控制从 dart 层调用去释放 native 的内存,和上一小结是类似的。

typedef ReverseFuncNative = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length);
typedef ReverseDart = Pointer<Utf8> Function(Pointer<Utf8> str, int length);

typedef FreeStringFuncNative = Void Function(Pointer<Utf8> str);
typedef FreeStringDart = void Function(Pointer<Utf8> str);

static void fromNative() {
    // 打开动态库
    final DynamicLibrary nativeLib = Platform.isAndroid
        ? DynamicLibrary.open("libnativelib.so")
        : DynamicLibrary.process();
        
    final reverse = nativeLib
        .lookupFunction<ReverseFuncNative, ReverseDart>('reverse_native');
    final helloStr = 'hello';
    final helloStrUtf8 = helloStr.toNativeUtf8();
    final reversedMessageUtf8 = reverse(helloStrUtf8, helloStr.length);
    final reversedMessage = reversedMessageUtf8.toDartString();

    calloc.free(helloStrUtf8);

    print('dart -> $helloStr reversed is $reversedMessage');

    final freeString =
        nativeLib.lookupFunction<FreeStringFuncNative, FreeStringDart>(
            'free_string_native');
    freeString(reversedMessageUtf8);
}

打印结果:

I/flutter (12514): dart -> hello reversed is olleh
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TYYJ-洪伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值