鸿蒙 Native API 的封装库 h2lib_arkbinder
h2lib_arkbinder
介绍
code: https://gitee.com/evanown/h2lib_arkbinder
本类库实现 C++ 代码到鸿蒙 Native API 的封装与转换。
现在鸿蒙生态还处于热火朝天的建设阶段,能否快速的将其他平台如IOS、Android的APP快速、高效、高质量的移植到鸿蒙系统,关系到鸿蒙的兴衰大业。在鸿蒙APP开发中,华为目前主推的是ArkTS语音,实际上就是Typescript的一种变体,除了UI等代码的迁移,很多APP的核心资产都是C++代码比如一些高效的图像处理算法等。鸿蒙提供了Native API相关接口,可以实现ArkTS调用C++代码。本类库arkbinder可以大幅度的提升鸿蒙Native API的易用性,如果你也移植APP的过程中要处理老的C++代码,那么本类库可能会极大的加速你的工作。
目前Native API的问题
- Native Api的使用手册和示例代码都不算健全。APP的开发者对自己的C++资产代码是熟悉的,但是要通过ArkTS调用,则需要通过Native API封装,属性Native API的概念、api接口参数等细节,即枯燥乏味又常常让人疑惑,比如垃圾回收如何触发类的析构,文档实际给出了比较模糊的指引,比如 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/use-napi-object-wrap-V5 中使用垃圾回收是使用delete析构了对象,而 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/use-sendable-napi-V5 中示例只调用类的析构函数,但是没用使用delete析构内存,而在https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/use-napi-about-class-V5中DerefItem确不调用delete。
- 文档的示例代码似乎从不处理函数传功来的类型和数量,比如文档中最入门的导出c++的AddNum示例函数的代码如下,本人立即ArkTS是脚本代码,类型是允许传入任意类型的,而且鸿蒙的示例代码变量几乎不初始化(推断鸿蒙的核心开发都是搞C程序),如果运行期ArkTS传入的类型不合法(比如更新了版本?),那么这里是有安全隐患的。
// 此模块是一个Node-API的回调函数
static napi_value Add(napi_env env, napi_callback_info info)
{
// 接受传入两个参数
size_t requireArgc = 2;
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);//没有处理参数少于2个的情况
// 将传入的napi_value类型的参数转化为double类型
double valueLeft;
double valueRight;
napi_get_value_double(env, args[0], &valueLeft);//!如果使用者不小心传入了字符串会怎样?
napi_get_value_double(env, args[1], &valueRight);
- 对于C++的多态、继承似乎也没有文档说明。这个可以理解,官方似乎还是希望你多用ArkTS,毕竟C++的概念太多很难跟ArkTS中的概念一一对应。但是如果C++的资产中存在类的继承已经多态函数(这很正常的对吧?),你肯定不想修改八百年不曾修改的老C++代码(导致其他平台不兼容?)。
- 如果自己编写Native API封装老的C++代码,对于开发者有很强的专业要求,既要对Typscript(实际上是javascript)的概念有了解,又要对NativeAPI的一些类型判断、引用计数等时刻谨慎,同时编写的代码还要高效安全,这无疑是费力难做好的工作,单似乎又要每个APP的开发者独立的做一遍。
- 本类库基于以上原因,开发了arkbinder,提供一个企业级、高效安全、教科书级的NativeAPI调用的封装。
arkbinder使用说明
arkbinder移动三个宏H2_DEF_CLASS定义类,H2_DEF_ITERFACE定义函数、类方法、类属性,最后H2_DEF_MODULE_FINISH(模块名);完成注册。
定义全局函数
如定义一个全局函数AddNum, 直接使用宏H2_DEF_ITERFACE注册,在所有注册完成后使用H2_DEF_MODULE_FINISH定义导出的模块名(必须和CMakeList中的so名一致),在ArkTS中就可以直接用了。
static double AddNum(double a, double b){
return a*100 + b;
}
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
H2_DEF_ITERFACE(&AddNum);
H2_DEF_MODULE_FINISH("entry");//!一定要放在定义的最后
}
//ArkTS示例代码
import cppExt from 'libentry.so';
let a = cppExt.AddNum(2, 3);
C++类全局函数
若要定义C++的静态函数,同样使用H2_DEF_ITERFACE宏,它会自动判断这个全局函数属于哪个类,并正确的注册到对应的类上
class Animal
{
public:
static std::string PrintTypeName(){ return typeid(Animal).name();}
//注册代码如下
H2_DEF_ITERFACE(&Animal::PrintTypeName);//仍然使用宏注册
//ArkTS示例代码
cppExt.Animal.PrintTypeName();
注册C++类
定义构造函数使用H2_DEF_CLASS,支持多态,定义的时候需要显示的标明构造函数的类型
class Animal
{
public:
Animal(std::string s=""):name(s),color(0){}
Animal(std::string s, int c):name(s),color(c){}
//注册代码如下
H2_DEF_CLASS(Animal(const std::string&));
H2_DEF_CLASS(Animal(const std::string&, int));
//ArkTS示例代码
let dog = new cppExt.Animal("dog");
let dog2 = new cppExt.Animal("dog", 1);
定义C++类继承父类
如果C++的类是继承于父类,并且父类已经使用H2_DEF_CLASS定义,那么子类定义是使用H2_DEF_CLASS2显示的标明父类。
class Bird:public Animal{
public:
Bird(std::string strName=""):Animal(strName){}
virtual int Fly(int nHeight){
return nHeight;
}
//注册代码如下
H2_DEF_CLASS2(Bird(std::string), Animal);
//ArkTS示例代码
let eagle = new cppExt.Bird("eagle");
};
C++类属性
H2_DEF_ITERFACE用来定义类中的属性字段,数值类型、字符串、已经注册过的类、指针等都支持。
struct Pos{
Pos(int a = 0, int b = 0):x(a),y(b){}
int x;
int y;
};
//注册代码如下
H2_DEF_CLASS(Pos(int, int));
H2_DEF_ITERFACE(&Pos::x);
H2_DEF_ITERFACE(&Pos::y);
//ArkTS示例代码
let pos = new cppExt.Pos(50, 60);
C++类方法
H2_DEF_ITERFACE一键定义类中的方法,支持所有标准数值、字符串、注册过的类,以及各种函数的const、引用的使用。你如果对C++模板编程感兴趣,不妨可以看看类库是如何用模板特化机制来实现对Native API的封装的。
class Animal
{
virtual int Fly(int nHeight);
void SetColor(int c);
int GetColor() const;
const std::string& GetName() const;
void SetName(const char* s);
const Pos& GetPos() const;
//注册代码如下
H2_DEF_ITERFACE(&Animal::GetColor);
H2_DEF_ITERFACE(&Animal::SetColor);
H2_DEF_ITERFACE(&Animal::SetName);
H2_DEF_ITERFACE(&Animal::GetName);
H2_DEF_ITERFACE(&Animal::GetPos);
//ArkTS示例代码
dog.SetColor(66);
let c = dog.GetColor();
let n = dog2.GetName();
C++类多态
示例代码中Go函数是多态的,注册的时候要显示的标明注册的函数类型,其实就是转成函数指针,这样编译器就知道你要注册哪个了。
class Animal
{
virtual void Go(int x, int y);
virtual void Go(const Pos& p);
//注册代码如下
H2_DEF_ITERFACE((void(Animal::*)(int, int))(&Animal::Go));
H2_DEF_ITERFACE((void(Animal::*)(const Pos&))&Animal::Go);
//ArkTS示例代码
dog.Go(100, 200);
let pos = new cppExt.Pos(50, 60);
dog.Go(pos);
嵌套支持STL容器
arkbinder通过泛型模版编程嵌套的支持vector、list、set、map,所有这四个类型的任意组合和嵌套,全部支持。
容器的元素类型支持标准数值、字符串、string、注册的类对象、指针以及嵌套的这四个容器类型。
class Animal
{
std::map<std::string, std::string> Dump(){
std::map<std::string, std::string> ret;
ret["name"] = name;
ret["color"] = std::to_string(color);
ret["pos"] = "["+std::to_string(pos.x)+","+std::to_string(pos.y)+"]";
return ret;
}
//注册代码如下
H2_DEF_ITERFACE(&Animal::Dump);
//ArkTS示例代码
let m = dog.Dump([1, 2, 3, 4, 5]);
let ms = JSON.stringify(m, null, 4);
hilog.info(DOMAIN, 'testTag', 'stl = %{public}s', ms);
支持将C++类对象、引用、值对象传入ArkTS
已经定义的C++类指针和引用以及const引用都可以传入ArkTS,实际上以临时类指针的方式传入ArkTs,这种情况,ArkTS是不负责垃圾回收的,对象的生命周期管理仍然由C++负责,如果函数参数或者函数返回值为类对象的值对象,那么实际上arkbinder内部会new一个对象给arkTS,这样的临时对象是由artTS的垃圾回收负责的。
class Animal
{
virtual const Animal* GetSelf() {
return this;
}
bool IsSame(const Animal& p) const{
return &p == this;
}
Pos AllocPos();
//注册代码如下
H2_DEF_ITERFACE(&Animal::GetSelf);
H2_DEF_ITERFACE(&Animal::IsSame);//上面两个接口指针都是临时的,arkTS垃圾回收不会触发对象析构
H2_DEF_ITERFACE(&Animal::AllocPos);//!这里返回的Pos对象垃圾回收是ArkTs负责的。
//ArkTS示例代码
let dog2 = dog.GetSelf();
dog.IsSame(dog2)
支持将std::function和ArkTS的function进行互相映射
如果将ArkTs的function作为函数参数,自动转换成C++的std::function。
如果将std::function转入到ArkTS中,std::function会转换成ArkTS的一个类对象CppLambdaFunc,调用
call回调c++的函数,参数为任意参数类型的一个列表。
export class CppLambdaFunc{
call: (a: Array<any|number>) => string;
}
class Animal
{
int Touch(std::function<int(double)> cb){
return cb(66.99)+100;
}
std::function<std::string(int)> GenFunc(){
return [](int v)->std::string{
std::string ret = "GenFunc:"+std::to_string(v);
return ret;
};
}
//注册代码如下
H2_DEF_ITERFACE(&Animal::Touch);
H2_DEF_ITERFACE(&Animal::GenFunc);
//ArkTS示例代码
let tret = dog.Touch((a:number)=>{
return a+2000;
});
let cppcb = dog.GenFunc();
let cppcb_ret = cppcb.call([199]);
附加说明
高度可扩展
如果你使用了其他类型,在arkbinder中没有支持导致编译不过,自己可以通过模板特化实现扩展,因为arkbinder只有一个头文件完全模板代码,所以可以在编译器扩展。支持新类型只需要对ScriptCppTypeTraits类进行特化,编写两个函数,static void Script2CppType(napi_env env, napi_value nv, T& ret)函数将ArkTS中的类型转换为C++的类型,而static void Cpp2ScriptType(napi_env env, napi_value& ret, const T& val){则相反,将c++的类型转换成ArkTS类型。如std::string 的特化代码如下
template<>
struct ScriptCppTypeTraits<std::string>
{
static int TypeVal() { return napi_string;}
static void Script2CppType(napi_env env, napi_value nv, std::string& ret){
if (!nv){
return;
}
napi_valuetype valuetype0;
napi_typeof(env, nv, &valuetype0);
switch (valuetype0){
case napi_number:{
double tmpv = 0.0;
napi_get_value_double(env, nv, &tmpv);
ret = std::to_string(tmpv);
}break;
case napi_bigint:{
int64_t tmpv = 0;
napi_get_value_int64(env, nv, &tmpv);
ret = std::to_string(tmpv);
}break;
case napi_string:{
size_t length = 0;
napi_status status = napi_get_value_string_utf8(env, nv, nullptr, 0, &length);
// 传入一个非字符串 napi_get_value_string_utf8接口会返回napi_string_expected
if (status != napi_ok) {
return;
}
ret.reserve(length+1);
ret.resize(length, 0);
(void)ret.c_str();
napi_get_value_string_utf8(env, nv, &ret[0], length + 1, &length);
}break;
default:{
}break;
}
}
static void Cpp2ScriptType(napi_env env, napi_value& ret, const std::string& val){
napi_create_string_utf8(env, val.c_str(), val.size(), &ret);
}
static void Cpp2ScriptType(napi_env env, napi_value& ret, const char* val){
napi_create_string_utf8(env, val, NAPI_AUTO_LENGTH, &ret);
}
template<typename R>
static void Cpp2ScriptType(napi_env env, napi_value& ret, R val){
std::ostringstream oss;
oss << val;
std::string strVal = oss.str();
napi_create_string_utf8(env, strVal.c_str(), strVal.size(), &ret);
}
};
导出后ArkTS声明文件
ArkTS是有类型的语音,C++中的接口需要显示的声明才能在ArkTS中编译通过,这一步目前还是需要手动编写。
TODO:arkbinder可以默认有一个DumpInterface接口将注册的所有类和接口自动导出声明文件,由于时间原因暂时没有时间编写,哪位江湖侠客敢兴趣可自行编写一个。
export const AddNum: (a: number, b: number) => number;
export class Pos {
constructor(x?: number, y?:number);
x:number;
y:number;
}
export class CppLambdaFunc{
call: (a: Array<any|number>) => string;
}
export class Animal {
constructor(name: string, c?:number);
static PrintTypeName():string;
Go: (a: number|Pos, b?:number) => void;
GetColor: () => number;
SetColor: (a: number) => void;
SetName: (a: string) => void;
GetName: () => string;
GetSelf:()=>Animal;
IsSame:(a:Animal)=>boolean;
Fly: (a: number) => number;
Dump:(a:number[])=>object;
GetPos:()=>Pos;
Touch:(cb:any)=>number;
GenFunc:()=>CppLambdaFunc;
}
export class Bird extends Animal{
constructor(arg: string);
}
以上。
鸿蒙 Native API 的封装库 h2lib_arkbinder的更多相关文章
- VBA/VB6/VBS/VB.NET/C#/Python/PowerShell都能调用的API封装库
API函数很强大,但是声明的时候比较繁琐. 我开发的封装库,包括窗口.键盘.鼠标.消息等常用功能.用户不需要添加API函数的声明,就可以用到API的功能. 在VBA.VB6的引用对话框中引用API.t ...
- Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)
文章目录: 1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...
- Unity在Android和iOS中如何调用Native API
本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...
- node-webkit教程(9)native api 之Tray(托盘)
node-webkit教程(9)native api 之Tray(托盘) 文/玄魂 目录 node-webkit教程(9)native api 之Tray(托盘) 前言 9.1 Tray简介 9.2 ...
- QF——iOS中的数据库操作:SQLite数据库,第三方封装库FMDB,CoreData
SQLite数据库: SQLite是轻量级的数据库,适合应用在移动设备和小型设备上,它的优点是轻量,可移植性强.但它的缺点是它的API是用C写的,不是面向对象的.整体来说,操作起来比较麻烦.所以,一般 ...
- okhttputils【 Android 一个改善的okHttp封装库】使用(三)
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 这一篇主要讲一下将OkHttpUtils运用到mvp模式中. 数据请求地址:http://www.wanandroid.com/to ...
- 离线人脸识别 ArcFaceSharp -- ArcFace 2.0 SDK C#封装库分享
ArcFaceSharp ArcFaceSharp 是ArcSoft 虹软 ArcFace 2.0 SDK 的一个 C# 封装库,为方便进行 C# 开发而封装.欢迎 Start & Fork. ...
- 【JavaScript 封装库】BETA 5.0 测试版发布!
JavaScript 前端框架(封装库) BETA 5.0 已于10月10日正式发布,今天开始提供 BETA 5.0 的 API 参考文献.相较于之前 5 个版本的发布都是草草的提供源代码,并没有很多 ...
- Glide二次封装库的使用
更多代码可以查询本人GitHub:欢迎阅读,star点起来. Glide二次封装库源码 前言 为什么选择Glide? Glide 轻量级 速度快 可以根据所需加载图片的大小自动适配所需分辨率的图 支持 ...
- Python 的 Geth 封装库 PyGeth
PyGeth 是一个 Python 封装库,用来作为子进程运行 geth. 系统依赖 该库需要 geth 可执行文件. 安装 pip install py-geth 快速启动 运行连接到 mainne ...
随机推荐
- Oracle VM VirtualBox虚拟机安装Centos
virtualBOx 6.x.x版本跟 virtualBOx 5.x.x 界面已经不一样,但安装步骤还是一样的 镜像下载可以使用下面帖子中的,网易镜像或者阿里云镜像,选取适合自己的镜像 开源镜像站,v ...
- [SDR] GNU Radio 系列教程 —— GNU Radio RX PDU (接收据包操作)的基础知识(超全)
目录 1 接收概述 2 相关块介绍 2.1 相关性估计器(Correlation Estimator) 2.2 多相时钟同步(Polyphase Clock Sync) 2.3 线性均衡器(Linea ...
- ESXi、PVE、unRaid对比
目录 收起 [前言] [概述] [系统安装] [系统资源占用] [创建/编辑虚拟机] [硬盘直通] [PCI硬件直通] [显卡直通] [虚拟光驱] [自动开机.关机] [网络管理] [稳定性] [CP ...
- Open diary(每天更新)
.col-md-8 img { display: none } .comment img { display: unset } 这是一个open diary,就是公开日记. 为什么标题用英文呢?因为觉 ...
- exim4
exim4 一台 debian 机器日常执行 apt update 后发现需要更新如下软件包, 之前没见过, 特此记录下. root@idebian:~# apt list --upgradable ...
- python API 之 fastapi
为什么选择 FastAPI? 高性能:基于 Starlette 和 Uvicorn,支持异步请求处理 开发效率:自动交互文档.类型提示.代码自动补全 现代标准:兼容 OpenAPI 和 JSON Sc ...
- Armbian 安装与更换为国内软件源
Armbian 是为 ARM 架构的单板计算机(如树莓派.NanoPi.Orange Pi 等)提供的开源镜像系统,它基于 Debian 或 Ubuntu 系统.在使用 Armbian 进行开发.调试 ...
- Java+Appium+Junit实现app自动化demo
1.新建maven工程和引入库 步骤参考https://www.cnblogs.com/wanyuan/p/16408758.html 2.编写代码 代码如下: import org.junit.Af ...
- AQS的release(int)方法底层源码
一.定义 release(int) 是 AQS(AbstractQueuedSynchronizer)中的一个核心方法,用于在独占模式下释放同步状态.如果释放成功,则会唤醒等待队列中的后继节点,使其有 ...
- java基础之函数式接口
一.函数式接口在Java中是指:有且仅有一个抽象方法的接口,所以函数式接口就是可以适用于Lambda使用的接口 二.自定义函数式接口 格式: @FunctionalInterface //该注解可省, ...