dart vm 里面其实提供了 dart 跟 cpp 之间的相互调用接口,开发者可以基于这个接口自行扩展 dart 的 api, 那么就来看看 dart vm 以及 flutter 是如何做到的。
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";
}
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,设置为true
VM就会自动启用,false
就需要在调用的时候才启用。
typedef const uint8_t* (*Dart_NativeEntrySymbol)(Dart_NativeFunction nf);
symbol
是根据函数地址或者到函数名。
通过以上的方式就可以在Dart
中直接调用customLogFunc
函数了,但是我们看到在CPP
中的customLogFunc
实现还是带有Dart_NativeArguments
这一Dart VM
浓厚色彩的风格,如何才能将CPP
的实现参数自动展开然后像customLogFunc(a, b)
这样方便的使用呢。
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
负责管理了注册的信息而已,函数调用之后的参数展开还并没有完成。
在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)
宏就可以生成注册需要的一组信息出来。
有了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
接口就会使用到CPP
的Canvas::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);