Dart API 扩展

Dart API 扩展

Dart和CPP的通道

dart vm 里面其实提供了 dart 跟 cpp 之间的相互调用接口,开发者可以基于这个接口自行扩展 dart 的 api, 那么就来看看 dart vm 以及 flutter 是如何做到的。

Dart VM

Dart VM中提供了以下接口:

DART_EXPORT Dart_Handle
Dart_SetNativeResolver(Dart_Handle library,
                       Dart_NativeEntryResolver resolver,
                       Dart_NativeEntrySymbol symbol);

通过以上的API就可以注册进去一个扩展的dart库:

Dart_SetNativeResolver(Dart_LookupLibrary(ToDart("dart:test")),
                        CustomNativeEntryResolver,
                        CustomNativeEntrySymbol);

Dart VM会在使用的时候调用注册时候的两个函数来查找函数:

static void customLogFunc(Dart_NativeArguments arguments) {
    // LOG("invoke custom log func");
}

static Dart_NativeFunction CustomNativeEntryResolver(Dart_Handle name,
                                              int num_of_arguments,
                                              bool* auto_setup_scope) {
    return &customLogFunc;
}

static const uint8_t* CustomNativeEntrySymbol(Dart_NativeFunction native_function) {
    return (const uint8_t *)"customLogFunc";
}

Dart_NativeEntryResolver

typedef Dart_NativeFunction (*Dart_NativeEntryResolver)(Dart_Handle name,
                                                        int num_of_arguments,
                                                        bool* auto_setup_scope);

resolver是根据name函数名来查找函数地址Dart_NativeFunction的,num_of_arguments是这个cpp的函数参数个数,auto_setup_scope是表示这个符号是否是要成为 Dart API,设置为trueVM就会自动启用,false就需要在调用的时候才启用。

Dart_NativeEntrySymbol

typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf);

symbol是根据函数地址或者到函数名。


通过以上的方式就可以在Dart中直接调用customLogFunc函数了,但是我们看到在CPP中的customLogFunc实现还是带有Dart_NativeArguments这一Dart VM浓厚色彩的风格,如何才能将CPP的实现参数自动展开然后像customLogFunc(a, b)这样方便的使用呢。

Flutter

Flutter使用tonic给出了一个完美的解决方案,来看一下使用tonic::DartLibraryNatives是如何为Dart扩展接口的,DartLibraryNatives的接口:

void Register(std::initializer_list<Entry> entries);

  Dart_NativeFunction GetNativeFunction(Dart_Handle name,
                                        int argument_count,
                                        bool* auto_setup_scope);
  const uint8_t* GetSymbol(Dart_NativeFunction native_function);

只需要构建一个DartLibraryNatives对象,然后通过Register接口传入需要注册的信息

natives->Register({{"Canvas_constructor", Canvas_constructor, 6, true})

再把DartLibraryNatives对象的两个解析信息的函数通过Dart_SetNativeResolver提供给Dart VM即可:

Dart_SetNativeResolver(Dart_LookupLibrary(ToDart("dart:Canvas")),
                        native->GetNativeFunction, native->GetSymbol));

但这也只是一个简单的封装,tonic::DartLibraryNatives负责管理了注册的信息而已,函数调用之后的参数展开还并没有完成。

Dart_NativeArguments 参数和返回值的处理

CPP的实现中,一般是类的形式:

void Canvas::saveLayer(double left,
                       double top,
                       double right,
                       double bottom,
                       const Paint& paint,
                       const PaintData& paint_data)

Dart调用的时候参数和返回值又是通过Dart_NativeArguments传递的,所以这里需要一个中间层来屏蔽细节。 所以在tonic中提供了一组封装,解决Dart_NativeArguments调用参数和返回值的问题:

#define DART_NATIVE_CALLBACK(CLASS, METHOD)                 \
  static void CLASS##_##METHOD(Dart_NativeArguments args) { \
    tonic::DartCall(&CLASS::METHOD, args);                  \
  }

#define DART_NATIVE_CALLBACK_STATIC(CLASS, METHOD)          \
  static void CLASS##_##METHOD(Dart_NativeArguments args) { \
    tonic::DartCallStatic(&CLASS::METHOD, args);            \
  }

通过这两个宏,就可以实现函数的封装和Dart_NativeArguments的处理,只需要使用 DART_NATIVE_CALLBACK(Canvas, saveLayer) 就会生成以下方法:

static void Canvas_saveLayer(Dart_NativeArguments args) {
    tonic::DartCall(&Canvas::saveLayer, args);
  }

tonic::DartCall会处理好参数和返回值,直接调用Canvas::saveLayer(left, top, right, bottom, paint, paint_data),所以注册的时候只需要使用通过DART_NATIVE_CALLBACK宏生成的Canvas_saveLayer函数即可。

注册信息的生成

通过tonic::DartLibraryNatives注册CPP信息的时候是要提供四个信息的,函数名,地址,参数个数以及auto_setup_scope的标记值。

natives->Register({"Canvas_saveLayer", Canvas_saveLayer, 6, true})

这一组信息tonic也提供了一组宏来自动生成:

#define DART_REGISTER_NATIVE(CLASS, METHOD) \
  {#CLASS "_" #METHOD, CLASS##_##METHOD,    \
   tonic::IndicesForSignature<decltype(&CLASS::METHOD)>::count + 1, true},

#define DART_REGISTER_NATIVE_STATIC(CLASS, METHOD)                           \
  {                                                                          \
#CLASS "_" #METHOD, CLASS##_##METHOD,                                    \
        tonic::IndicesForSignature < decltype(&CLASS::METHOD)> ::count, true \
  }

所以使用DART_REGISTER_NATIVE(Canvas, saveLayer)宏就可以生成注册需要的一组信息出来。

Dart 封装

有了tonic提供的一组接口,就可以做到使用简单的几行代码来注册接口

#define FOR_EACH_BINDING(V)         \
  V(Canvas, save)                   \
  V(Canvas, saveLayerWithoutBounds) \
  V(Canvas, saveLayer)

FOR_EACH_BINDING(DART_NATIVE_CALLBACK)

natives->Register({FOR_EACH_BINDING(DART_REGISTER_NATIVE)});

而我们只需要在CPP中完成代码的实现就可以了。 当然也需要在Dart中声明这个接口,非常类似 Java/Jni 的实现,以下是Dart代码:

void _saveLayer(double left,
                  double top,
                  double right,
                  double bottom,
                  List<dynamic> paintObjects,
                  ByteData paintData) native 'Canvas_saveLayer';

Dart中直接调用_saveLayer接口就会使用到CPPCanvas::saveLayer方法。

反射

Dart VM也提供了CPP调用Dart的能力,也是类型Java的反射机制,主要是以下三个API,使用起来也比较简单,就不详细介绍了

DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_Invoke(Dart_Handle target,
            Dart_Handle name,
            int number_of_arguments,
            Dart_Handle* arguments);


DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_InvokeClosure(Dart_Handle closure,
                   int number_of_arguments,
                   Dart_Handle* arguments);


DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle
Dart_InvokeConstructor(Dart_Handle object,
                       Dart_Handle name,
                       int number_of_arguments,
                       Dart_Handle* arguments);
发布于 2018-11-07