09、Flutter FFI Dart Native API

 
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》
      
     

  在这一章节中,将介绍 Dart Native API 的使用方法。
 

一、介绍

  
  Dart Native API 提供了 C/C++ 与 Dart 交互的能力。在 Dart SDK 目录下,有一个 include 文件夹,里面包含了 Dart Native API 的声明:
在这里插入图片描述
  上面这些文件,只需要导入到项目中,直接 include 即可使用。
  
  Dart SDK 提供了两种使用方式:

  • Statically Linking :静态链接;
  • Dynamically Linking:动态链接。

  【静态链接】适用于 Dart embedder,就是把 Dart SDK 直接编译到 app 的场景,例如:Flutter Engine 开发者。这种方式可以直接访问所有 Dart Native API。
  
  对于 Flutter App 开发者来说,只能使用【动态链接】的方式。这种方式只能访问少量 Dart Native API。也就是 dart_api_dl.h 中定义的函数dart_api.hdart_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 */

  说明

  • 接口函数都是以 DL 结尾的(即:Dynamically Linked 的缩写),例如:Dart_PostCObject 接口,在使用的时候就变成了 Dart_PostCObject_DL
  • 接口类型可以直接使用,不需要加 DL 后缀,例如:Dart_PortDart_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 机制,步骤如下:

  • 在 Dart 中创建 ReceivePort,并调用 listen 进行监听;
  • 将 上面 ReceivePort 所关联的 SendPort 传递给 C/C++;
  • C/C++ 保存接收到的 SendPort,并创建子线程执行耗时任务;
  • 当任务执行完成之后,通过 SendPort 将数据传递给 Dart 。

  下面直接给出详细代码和注释:
  C++ 代码:

#include <thread>
#include <string>

#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

说明:

  • 从输出可以得知,Dart 已经自动帮我们把 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 的对象和方法了。
  
  
  
  
 

  • 3
    点赞
  • 4
    收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 3

打赏作者

又吹风_Bassy

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值