Dart API 扩展

简介: Dart VM 里面其实提供了 dart 跟 cpp 之间的相互调用接口,开发者可以基于这个接口自行扩展 dart 的 api, 本文描述了如何方便的扩展,介绍了 Dart 和 Cpp 直接调用的基本使用方法。

Dart API 扩展

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);
目录
相关文章
|
5月前
|
Java API Spring
打造未来电商新引擎:揭秘Java可扩展API设计,让支付与物流灵活如丝,引领电商时代潮流!
【8月更文挑战第30天】本文通过电商平台案例,探讨了如何设计可扩展的Java API。首先定义支付和物流服务的接口与抽象类,然后实现具体服务,接着引入工厂模式或依赖注入管理服务实例,最后通过配置实现灵活扩展。这种设计确保了应用架构的灵活性和长期稳定性。
69 3
|
2月前
|
缓存 负载均衡 安全
后端开发的艺术:构建高效、可扩展的API
在现代软件开发中,后端开发扮演着至关重要的角色。它不仅负责处理数据存储、业务逻辑和安全性,还需要提供高效、可扩展的API供前端和其他服务使用。本文将深入探讨后端开发的关键概念和技术,帮助读者了解如何构建高效、可扩展的API,并提供一些实用的建议和最佳实践。
|
6月前
|
JSON Java API
如何设计可扩展的RESTful API?
如何设计可扩展的RESTful API?
|
2月前
|
Web App开发 人工智能 自然语言处理
WebChat:开源的网页内容增强问答 AI 助手,基于 Chrome 扩展的最佳实践开发,支持自定义 API 和本地大模型
WebChat 是一个基于 Chrome 扩展开发的 AI 助手,能够帮助用户理解和分析当前网页的内容,支持自定义 API 和本地大模型。
104 0
|
8月前
|
缓存 前端开发 API
构建可扩展的API:REST vs GraphQL
【5月更文挑战第14天】在API设计中,REST和GraphQL是两种主要的架构风格。REST基于HTTP协议,强调资源的无状态性和客户端-服务器模型,适合简单CRUD操作。而GraphQL提供更灵活的数据查询,允许客户端精确指定需求,减少HTTP请求和数据冗余,适合复杂场景和高性能需求。选择时要考虑项目需求、技术栈、性能和团队经验。
|
3月前
|
监控 安全 API
Docker + .NET API:简化部署和扩展
Docker + .NET API:简化部署和扩展
45 0
|
3月前
|
监控 安全 API
最完美的扩展Docker + .NET API:简化部署和扩展
最完美的扩展Docker + .NET API:简化部署和扩展
102 0
|
5月前
|
XML JSON API
RESTful API设计最佳实践:构建高效、可扩展的接口
【8月更文挑战第17天】RESTful API设计是一个涉及多方面因素的复杂过程。通过遵循上述最佳实践,开发者可以构建出更加高效、可扩展、易于维护的API。然而,值得注意的是,最佳实践并非一成不变,随着技术的发展和业务需求的变化,可能需要不断调整和优化API设计。因此,保持对新技术和最佳实践的关注,是成为一名优秀API设计师的关键。
|
5月前
|
存储 缓存 运维
平稳扩展:可支持RevenueCat每日12亿次API请求的缓存
平稳扩展:可支持RevenueCat每日12亿次API请求的缓存
53 1
|
5月前
|
Java 数据库连接 缓存
Hibernate性能调优:五大秘籍,让应用效能飙升,告别慢如蜗牛的加载,体验丝滑般流畅!
【8月更文挑战第31天】本文深入探讨了提升Hibernate应用性能的五大技巧,包括选择合适的缓存策略、优化查询语句、合理使用Eager与Lazy加载、批量操作与事务管理以及利用索引和数据库优化。通过正确配置多级缓存、分页查询、延迟加载、批量处理及合理创建索引,能够显著提高应用响应速度与吞吐量,改善用户体验。这些技巧需根据具体应用场景灵活调整,以实现最佳性能优化效果。
221 0