Flutter FFI 学习笔记系列
在这一章节中,将介绍 Dart Native API 的使用方法。
Dart Native API 提供了 C/C++ 与 Dart 交互的能力。在 Dart SDK 目录下,有一个 include 文件夹,里面包含了 Dart Native API 的声明:
上面这些文件,只需要导入到项目中,直接 include 即可使用。
Dart SDK 提供了两种使用方式:
【静态链接】适用于 Dart embedder,就是把 Dart SDK 直接编译到 app 的场景,例如:Flutter Engine 开发者。这种方式可以直接访问所有 Dart Native API。
对于 Flutter App 开发者来说,只能使用【动态链接】的方式。这种方式只能访问少量 Dart Native API。也就是 dart_api_dl.h
中定义的函数和 dart_api.h
、dart_native_api.h
中定义的类型。
dart_api_dl.h
代码如下:
#ifndef RUNTIME_INCLUDE_DART_API_DL_H_
#define RUNTIME_INCLUDE_DART_API_DL_H_
#include "dart_api.h" /* NOLINT */
#include "dart_native_api.h" /* NOLINT */
/** \mainpage Dynamically Linked Dart API
*
* This exposes a subset of symbols from dart_api.h and dart_native_api.h
* available in every Dart embedder through dynamic linking.
*
* All symbols are postfixed with _DL to indicate that they are dynamically
* linked and to prevent conflicts with the original symbol.
*
* Link `dart_api_dl.c` file into your library and invoke
* `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`.
*/
#ifdef __cplusplus
#define DART_EXTERN extern "C"
#else
#define DART_EXTERN extern
#endif
DART_EXTERN intptr_t Dart_InitializeApiDL(void* data);
// ============================================================================
// IMPORTANT! Never update these signatures without properly updating
// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
//
// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types
// to trigger compile-time errors if the sybols in those files are updated
// without updating these.
//
// Function return and argument types, and typedefs are carbon copied. Structs
// are typechecked nominally in C/C++, so they are not copied, instead a
// comment is added to their definition.
typedef int64_t Dart_Port_DL;
typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id,
Dart_CObject* message);
// dart_native_api.h symbols can be called on any thread.
#define DART_NATIVE_API_DL_SYMBOLS(F) \
/***** dart_native_api.h *****/ \
/* Dart_Port */ \
F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \
F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \
F(Dart_NewNativePort, Dart_Port_DL, \
(const char* name, Dart_NativeMessageHandler_DL handler, \
bool handle_concurrently)) \
F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id))
// dart_api.h symbols can only be called on Dart threads.
#define DART_API_DL_SYMBOLS(F) \
/***** dart_api.h *****/ \
/* Errors */ \
F(Dart_IsError, bool, (Dart_Handle handle)) \
F(Dart_IsApiError, bool, (Dart_Handle handle)) \
F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle)) \
F(Dart_IsCompilationError, bool, (Dart_Handle handle)) \
F(Dart_IsFatalError, bool, (Dart_Handle handle)) \
F(Dart_GetError, const char*, (Dart_Handle handle)) \
F(Dart_ErrorHasException, bool, (Dart_Handle handle)) \
F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle)) \
F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle)) \
F(Dart_NewApiError, Dart_Handle, (const char* error)) \
F(Dart_NewCompilationError, Dart_Handle, (const char* error)) \
F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception)) \
F(Dart_PropagateError, void, (Dart_Handle handle)) \
/* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \
F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object)) \
F(Dart_HandleFromWeakPersistent, Dart_Handle, \
(Dart_WeakPersistentHandle object)) \
F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object)) \
F(Dart_SetPersistentHandle, void, \
(Dart_PersistentHandle obj1, Dart_Handle obj2)) \
F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object)) \
F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle, \
(Dart_Handle object, void* peer, intptr_t external_allocation_size, \
Dart_HandleFinalizer callback)) \
F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \
F(Dart_UpdateExternalSize, void, \
(Dart_WeakPersistentHandle object, intptr_t external_allocation_size)) \
F(Dart_NewFinalizableHandle, Dart_FinalizableHandle, \
(Dart_Handle object, void* peer, intptr_t external_allocation_size, \
Dart_HandleFinalizer callback)) \
F(Dart_DeleteFinalizableHandle, void, \
(Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object)) \
F(Dart_UpdateFinalizableExternalSize, void, \
(Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object, \
intptr_t external_allocation_size)) \
/* Dart_Port */ \
F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \
F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \
F(Dart_SendPortGetId, Dart_Handle, \
(Dart_Handle port, Dart_Port_DL * port_id)) \
/* Scopes */ \
F(Dart_EnterScope, void, ()) \
F(Dart_ExitScope, void, ())
#define DART_API_ALL_DL_SYMBOLS(F) \
DART_NATIVE_API_DL_SYMBOLS(F) \
DART_API_DL_SYMBOLS(F)
// IMPORTANT! Never update these signatures without properly updating
// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION.
//
// End of verbatim copy.
// ============================================================================
#define DART_API_DL_DECLARATIONS(name, R, A) \
typedef R(*name##_Type) A; \
DART_EXTERN name##_Type name##_DL;
DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS)
#undef DART_API_DL_DEFINITIONS
#undef DART_EXTERN
#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */
说明:
Dart_PostCObject
接口,在使用的时候就变成了 Dart_PostCObject_DL
。Dart_Port
、Dart_CObject
;
调用非 DL 结尾的函数会导致 Linker 链接失败,例如,Dart_IsNull
用于判断一个 Dart 对象是否为 null,它属于 Dart Native API 的一个接口,但是并没有在 dart_api_dl.h
中声明,如果直接调用会报错,如下:
E:/Team_Bass/flutter_ffi/ios/Runner/native_ffi.cpp:22: undefined reference to `Dart_IsNull'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
所以,就目前看来,我们只能使用 dart_api_dl.h
来中声明的API。最常用的用途就是用来解决 C 与 Dart 异步通讯的事情。下面来介绍一下具体用法。
先将 include 目录下的文件拷贝到项目中,然后修改 CMakeLists.txt
,如下:
cmake_minimum_required(VERSION 3.4.1) # for example
add_library(native_ffi
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
../../ios/Runner/dart_api/dart_api.h
../../ios/Runner/dart_api/dart_api_dl.h
../../ios/Runner/dart_api/dart_native_api.h
../../ios/Runner/dart_api/dart_version.h
../../ios/Runner/dart_api/internal/dart_api_dl_impl.h
../../ios/Runner/dart_api/dart_api_dl.c
../../ios/Runner/native_ffi.cpp
)
使用 Dart Native API,必须需要初始化:
首先需要在 C 定义一个初始化函数,供 Dart 使用:
#include "dart_api/dart_api.h"
#include "dart_api/dart_native_api.h"
#include "dart_api/dart_api_dl.h"
// Initialize `dart_api_dl.h`
DART_EXPORT intptr_t InitDartApiDL(void* data) { //定义一个初始化函数给Dart使用
return Dart_InitializeApiDL(data); //这里调用了DartSDK的函数进行初始化
}
说明:
InitDartApiDL
函数,这个函数是给 Dart 使用的,所以需要加上 DART_EXPORT
修饰;DART_EXPORT
是 Dart Native API 里面的一个宏定义,其实就是 extern "C"
;Dart_InitializeApiDL
是 Dart Native API 里面的一个初始化 API,返回 0
表示成功;void* data
是从 Dart 传递进来的指针,指针向 C 中的 DartApi
结构体。可以在 Dart 中调用 NativeApi.initializeApiDLData
获取到这个指针;DartApi
结构体中包含了版本(major
, minor
)以及函数表(functions
)信息,在 Dart_InitializeApiDL()
被调用的时候,该函数表与 dart_api_dl.h
中定义的宏进行映射,这样我们在调用 Dart_PostCObject_DL
等以 DL 结尾的 API 时,就可以正确关联到原始的函数 Dart_PostCObject
。感兴趣的同学可以在 Dart_InitializeApiDL()
函数中下断点分析一下。 然后,需要在 Dart 调用 C 中的 InitDartApiDL
接口:
函数类型定义:
typedef Native_Dart_InitializeApiDL = Int32 Function(Pointer<Void> data);
typedef FFI_Dart_InitializeApiDL = int Function(Pointer<Void> data);
Dart 调用 C 的 InitDartApiDL
函数:
//加载符号信息
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//查找初始化函数
FFI_Dart_InitializeApiDL initializeFunc = nativeApi.lookupFunction<
Native_Dart_InitializeApiDL, FFI_Dart_InitializeApiDL>("InitDartApiDL");
//调用初始化函数
var nativeInited = initializeFunc(NativeApi.initializeApiDLData);
//检查是否成功
assert(nativeInited == 0, 'init api-dl failed.');
说明:
InitDartApiDL
成功之后,会返回 0
;
Dart 中的代码默认都是在 Main Isolate 中执行的,这个相当于主线程。因此,当 C / C++ 回调 Dart 函数的时候,也必须保证是同一个线程。否则的话,会出现崩溃。但是在实际开发中,我们需要把一个任务放到 C/C++ 的子线程中执行,如果在子线程直接调用 Dart 的全局函数,必定会引起崩溃。
解决异步回调的方法,就是使用 Dart 的 SendPort / ReceivePort
机制,步骤如下:
ReceivePort
,并调用 listen
进行监听;ReceivePort
所关联的 SendPort
传递给 C/C++;SendPort
,并创建子线程执行耗时任务;SendPort
将数据传递给 Dart 。 下面直接给出详细代码和注释:
C++ 代码:
#include
#include
#include "dart_api/dart_api.h"
#include "dart_api/dart_native_api.h"
#include "dart_api/dart_api_dl.h"
// 1.声明线程执行函数
void thread_func(Dart_Port sendPort, char *name);
// 2.初始化 Dart Native API
DART_EXPORT intptr_t InitDartApiDL(void* data) {
return Dart_InitializeApiDL(data);
}
// 3.开启线程
DART_EXPORT void NativeAsyncExecute(Dart_Port sendPort, char *name) {
std::thread thread1(thread_func, sendPort, name);
thread1.detach();
}
// 4.线程实际操作
void thread_func(Dart_Port sendPort, char *name) {
printf("thread is running, arg=%s", name);
//等待一段时间
std::this_thread::sleep_for(std::chrono::seconds(3));
std::string greeting("Hello, ");
greeting += std::string(name);
//创建一个Dart对象,然后发给给Dart
Dart_CObject dart_object;
dart_object.type = Dart_CObject_kString; //Dart对象的类型
dart_object.value.as_string = (char*) greeting.c_str(); //Dart对象的值
Dart_PostCObject_DL(sendPort, &dart_object); //发送给Dart
free(name); //释放内存
printf("thread is over, return=%s", greeting.c_str());
}
说明:
Dart_PostCObject_DL
向指定的 SendPort 发送数据;Dart_CObject_kString
是一个枚举类型,其它枚举值请参考 Dart_CObject_Type
;Dart_PostCObject_DL
似乎一次只能发送一个对象,因此如果需要传递复杂的数据类型的话,可以考虑传递 结构体 或 JSON 字符串等其它方案。Dart 代码如下:
typedef Native_Dart_InitializeApiDL = Int32 Function(Pointer<Void> data);
typedef FFI_Dart_InitializeApiDL = int Function(Pointer<Void> data);
typedef Native_NativeAsyncExecute = Void Function(Int64, Pointer<Int8>);
typedef FFI_NativeAsyncExecute = void Function(int, Pointer<Int8>);
class _DemoState extends State<Demo> {
late DynamicLibrary nativeApi;
late ReceivePort _receivePort;
@override
void initState() {
super.initState();
testNative();
}
@override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(body: Center(child: Text("FFI Demo"))));
}
void testNative() {
//加载符号表
nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//1.查找初始化函数
FFI_Dart_InitializeApiDL initFunc = nativeApi.lookupFunction<
Native_Dart_InitializeApiDL, FFI_Dart_InitializeApiDL>("InitDartApiDL");
//2.调用初始化函数,并判断是否成功
var nativeInited = initFunc(NativeApi.initializeApiDLData);
assert(nativeInited == 0, '初始化Dart Native API失败');
//3.创建ReceivePort,用于接收Native异步返回的数据
_receivePort = new ReceivePort();
_receivePort.listen((message) {
print("ReceivePort, message=$message, type=${message.runtimeType}");
_receivePort.close();
});
//4.查找Native异步函数
FFI_NativeAsyncExecute asyncExecuteFunc = nativeApi.lookupFunction<
Native_NativeAsyncExecute, FFI_NativeAsyncExecute>("NativeAsyncExecute");
//5.调用Native异步函数
final name = "Clark".toNativeUtf8().cast<Int8>();
asyncExecuteFunc(_receivePort.sendPort.nativePort, name);
}
}
说明:
receivePort.sendPort.nativePort
可以获取到 SendPort
的句柄,它是一个 int64
类型,可以传递给 C/C++ 使用。上述代码输出如下:
I/flutter: ReceivePort, message=Hello, Clark, type=String
说明:
char*
类型转化为 Dart String 类型了,十分方便;
Dart Native API 提供了 Native 与 Dart 交互的能力,作为 Flutter App 开发者,我们可以使用 dart_api_dl.h
中定义的 API 实现 C 与 Dart 的异步回调功能。
当然, dart_api_dl.h
还有很多 API 这里没有介绍,用到时候直接看 dart_native_api.h 这些头文件,里面有详细的解释。
另外,如果自己定制 Flutter Engine 的话,甚至可以把更多的 Dart Native API 暴露出来,这样就可以在 C/C++ 中自由地调用 Dart 的对象和方法了。