Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
- 《Flutter FFI Dart Native API》
在前面的章节中,演示了如何在 Dart 中访问 C 中的函数。接下来将详细介绍 C 和 Dart 函数的相互调用。
1、Dart 调用 C 函数
1.1 C函数的定义
Dart 语言只能调用 C 语言风格的函数,不能调用 C++ 语言风格的函数,因此,函数需要加上 extern "C"
前缀,这表示告诉编译器按C语言风格编译该函数。同时,为了避免编译器在编译优化阶段把没有使用到的符号删除掉,需要在函数前面加上 __attribute__((visibility("default")))
和 __attribute__((used))
。
C 语言函数的定义格式如下:
#include <stdint.h>
extern "C" __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) {
return x + y;
}
为了编写方便,可以使用宏定义来简化代码:
#include <stdint.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API int32_t native_add(int32_t x, int32_t y) {
return x + y;
}
1.2 Dart函数类型定义
在 Dart 中,需要定义与 C 函数相对应的函数类型,以便在 Dart 中调用。
这里需要使用 Dart Function 定义两个函数类型,一个用于表示 C ,一个用于 Dart,如下:
///对应C语言函数声明:int32_t native_add(int32_t x, int32_t y)
typedef Native_add = Int32 Function(Int32 x, Int32 y);
///Dart使用的函数
typedef FFI_add = int Function(int x, int y);
说明:
- 上面的
Native_add
函数类型,是 C 函数在 Dart 中的表示形式,函数的参数和返回值都属性NativeType
(例如:Int32
); - 上面的
FFI_add
函数类型,是 Dart 风格的函数,供 Dart 调用,函数参数和返回值都是 Dart 中的数据类型; - 当使用 FFI 绑定这两个函数类型的时候,数据类型的转换将由 FFI 内部完成。
1.3 Dart调用C函数
完成函数的声明和定义之后,就可以通过以下步骤在 Dart 中调用 C 函数了:
- Android 平台使用
DynamicLibrary.open()
加载符号信息,iOS 平台使用DynamicLibrary.process()
加载符号信息; - 通过
DynamicLibrary.lookup()
或者DynamicLibrary.lookupFunction()
来查找函数符号,并转为 Dart 函数;
调用 Dart 函数。示例代码如下:
//加载符号
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//方法1 - 查找函数符号,并转为Dart函数
final addFunc1 = nativeApi
.lookupFunction<Native_add, FFI_add>("native_add");
//方法2 - 查找函数符号,并转为Dart函数
FFI_add addFunc2 = nativeApi
.lookup<NativeFunction<Native_add>>("native_add")
.asFunction();
//此时,调用Dart函数就是调用C函数
int result = addFunc1(1, addFunc2(1, 2));
print("1+(1+2)=$result");
//输出信息:
//1+(1+2)=4
说明:
lookupFunction()
其实就是lookup()
和asFunction()
的封装,简化代码。
1.4 变长参数函数
C 语言中具有变长参数函数,但是在 FFI 中,必须要指定参数类型和个数才行。
下面这个示例演示了如何在 Dart 调用 C 的变长参数函数:
首先,在 C 中定义一个函数:
#include <stdint.h>
#include <stdarg.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API int multi_sum(int nr_count, ...) {
va_list nums;
va_start(nums, nr_count);
int sum = 0;
for (int i = 0; i < nr_count; i++)
{
sum += va_arg(nums, int);
}
va_end(nums);
return sum;
}
然后,在 Dart 中定义两个函数类型,与 C 中的函数进行映射:
///对应C语言函数
typedef Native_multi_sum = Int32 Function(
Int32 numCount, Int32 a, Int32 b, Int32 c);
///供Dart所使用的函数
typedef FFI_multi_sum = int Function(int numCount, int a, int b, int c);
最后,通过 lookupFunction()
找到该符号并调用:
final sumFunc = nativeApi
.lookupFunction<Native_multi_sum,FFI_multi_sum>("multi_sum");
print("result:${sumFunc(3, 1, 2, 3)}");
//输出结果
//result:6
说明:
- 无
2、C 调用 Dart 函数
上面介绍了如何在 Dart 中调用 C 的函数,下面看看如何在 C 中调用 Dart 的函数。
在 Dart 中,只有全局函数才能给被 C 调用。我们可以通过 Pointer.fromFunction()
函数将 Dart Function 转为 C 的函数指针。fromFunction()
函数声明如下:
/// Convert Dart function to a C function pointer, automatically marshalling
/// the arguments and return value
///
/// If an exception is thrown while calling `f()`, the native function will
/// return `exceptionalReturn`, which must be assignable to return type of `f`.
///
/// The returned function address can only be invoked on the mutator (main)
/// thread of the current isolate. It will abort the process if invoked on any
/// other thread.
///
/// The pointer returned will remain alive for the duration of the current
/// isolate's lifetime. After the isolate it was created in is terminated,
/// invoking it from native code will cause undefined behavior.
///
/// Does not accept dynamic invocations -- where the type of the receiver is
/// [dynamic].
external static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
@DartRepresentationOf("T") Function f,
[Object? exceptionalReturn]);
下面通过一个示例来演示 C 如何调用 Dart 函数。
首先,在 C 定义一个函数:
#include <malloc.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API
void calc(int32_t src, void (*callback)(int32_t, int32_t)) {
int result = src * 10000;
callback(src, result);
}
然后在 Dart 定义相应的函数类型以及 全局回调函数:
typedef Callback = Void Function(Int32, Int32);
typedef Native_calc = Void Function(Int32, Pointer<NativeFunction<Callback>>);
typedef FFI_calc = void Function(int, Pointer<NativeFunction<Callback>>);
void globalCallback(int src, int result) {
print("globalCallback src=$src, result=$result");
}
说明:
- 函数指针的类型为:
Pointer<NativeFunction<...>
; globalCallback()
是一个全局函数,用于给 C 调用。
最后,在 Dart 调用 C 的函数:
//加载 C 符号
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//查找函数
FFI_calc calcFunc = nativeApi.lookupFunction<Native_calc, FFI_calc>("calc");
//调用函数
calcFunc(32, Pointer.fromFunction(globalCallback));
//输出结果:
//I/flutter ( 3920): globalCallback src=32, result=320000
说明:
- 我们在 Dart 中调用了 C 的
calc()
函数,并将globalCallback
转为函数指针作为calc()
的参数; - 在 C 的
calc()
函数内,直接把 Dart 的globalCallback
当作函数指针进行调用;
最后 globalCallback
被调用,打印了相应的信息。
3、总结
上面介绍如何通过 FFI 实现 C 与 Dart 的相互调用,在后面的章节中,将会介绍字符串、结构体、数组、内存管理等知识,欢迎关注。