鸿蒙OS开发实例:【NAPI入门】

背景

公司内部已经有现成的MQTT动态库,想在HarmonyOS平台上共享使用。查找官方指导后,发现可以通过NAPI方式,将MQTT C++库导入进来,然后封装一层ArkTS接口就可直接使用。

本篇内容是在按照官方指导下,自己做的一些调研性质的实践。

阅读完成后

将学会如何在已有的HarmonyOS应用工程中完成C++文件的添加以及TS与C++的交互

希望大家不用碰到类似的项目需求

NAPI简介

NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架

在HarmonyOS中,C API中的N-API接口可以实现ArkTS/TS/JS与C/C++之间的交互。N-API提供的接口名与三方Node.js一致,目前支持部分接口。

关于详细HarmonyOS 中的N-API开发教程,详见指导

搜狗高速浏览器截图20240326151547.png

环境条件

效果图

功能演示

  1. ets文件获取C++生成的JSON对象
  2. ets文件获取C++生成的数组
  3. 引入第三方源码,并且使用其API
  4. ets获取C++的运算结果
  5. ets实现回调方法, C++触发ets回调方法

Screenshot_20240312155256471.png

实践步骤

备注:以添加 源码 形式演示

简易目录结构

粉色为添加和改动部分

1. entry模块添加文件

  1. src/main 目录下创建“cpp”文件夹【名字必须为cpp】
  2. src/main 目录下创建主入口文件"main.cpp"【名字可以随意命名】
  3. src/main 目录下创建"CMakeLists.txt"文件【内容见 “片段1”】

片段1

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.4.1)
project(HarmonyLearn)

# 设置源码文件目录
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# 设置 .h 文件目录
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)

# 设置自己的动态名称 和 对应的编译文件
add_library(customnapi SHARED main.cpp
                         sm4.cpp
                         util.cpp)

# 设置链接自己的动态库与其它动态库
target_link_libraries(customnapi PUBLIC libace_napi.z.so)

2. 配置entry模块

entry模块中的build-profile.json5文件增加native选项【内容见 “片段2”】

...

"buildOption": {
  "externalNativeOptions": {
    "path": "./src/main/cpp/CMakeLists.txt",
    "arguments": "",
    "cppFlags": "",
  },
  
  ...
}

...

3. ets引用so

在需要使用到"customnapi"库的文件中,通过import方式来进行引用

注意 虽然我们自己定义的库名称为"customnapi", 但是引用的时候应该为"libcustomnapi.so", 注意是以"lib"为前缀

import customnapi from 'libcustomnapi.so'

...

customnapi.passStr(...)
...

经过这三步,即可完成C++源码在HarmonyOS应用工程中的添加使用

示例解读

main.cpp

这个文件在本示例中用来注册C++源码模块使用的,文件名称可任意修改,因为其最终会被CMakeLists.txt文件引用,所以其文件名称无关紧要

这个文件主要分为四部分

  1. so api注册
  2. so模块说明
  3. napi接口描述
  4. C++函数定义

so注册,即在ets文件被加载时,触发import动作,然后会通过系统机制自动执行 napi_module_register 代码

so模块说明,即对so 模块的描述,包含NAPI接口描述入口,so模块名称,版本等

so api注册, 即ets函数名称和NAPI 函数名称的映射关系。比如,ets中调用add,因为有这里的映射,所以执行的是C++文件中的Add函数。

C++函数定义,即so库对外暴露的接口实现,比如static napi_value Add(napi_env env, napi_callback_info info)

......

static napi_value Add(napi_env env, napi_callback_info info) {
    ......
}

......

// Init将在exports上挂上Add/NativeCallArkTS这些native方法,此处的exports就是开发者import之后获取到的ArkTS对象。

static napi_value Init(napi_env env, napi_value exports) {

    // 函数描述结构体,以Add为例,第三个参数"Add"为上述的native方法,
    // 第一个参数"add"为ArkTS侧对应方法的名称。
    napi_property_descriptor desc[] = {
        {"add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr},
     
     ......
     
    return exports;
}

// 准备模块加载相关信息,将上述Init函数与本模块名等信息记录下来。
static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "customnapi",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

// 打开so时,该函数将自动被调用,使用上述demoModule模块信息,进行模块注册相关动作。
extern "C" __attribute__((constructor)) void RegisterModule(void) {
    napi_module_register(&demoModule);
}

C++解析TS方法的参数

例如:Add函数

// 期望从ArkTS侧获取的参数的数量,napi_value可理解为ArkTS value在native方法中的表现形式。
//TS函数有2个参数
size_t argc = 2;

//创建一个包含2个元素的TS参数值数组
napi_value args[2] = {nullptr};

// 从info中,拿到从ArkTS侧传递过来的参数,此处获取了两个ArkTS参数,即arg[0]和arg[1]。
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

// 将获取的ArkTS参数转换为native信息,此处ArkTS侧传入了两个number,这里将其转换为native侧可以操作的double类型。

//解析TS函数中的第一个参数
double value0;
//声明于 js_native_api.h 文件,属于HarmonyOS SDK里边的一部分
napi_get_value_double(env, args[0], &value0);

//解析TS函数中的第二个参数
double value1;
//声明于 js_native_api.h 文件,属于HarmonyOS SDK里边的一部分
napi_get_value_double(env, args[1], &value1);

C++获取TS方法中的字符串类型参数长度

例如:passStr函数

static napi_value passStr(napi_env env, napi_callback_info info) {
    //TS函数有1一个参数
    size_t argc = 1;
    //创建一个包含1个元素的TS参数值数组
    napi_value args[1] = {nullptr};

    // 从info中,拿到从ArkTS侧传递过来的参数,此处获取了一个ArkTS参数,即arg[0]。
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    //获取字符串参数的长度
    size_t typeLen = 0;
    napi_get_value_string_utf8(env, args[0], nullptr, 0, &typeLen);

    //创建存储字符串的数组,数组大小为“字符串长度+1”, 1代表字符串结束符
    char *extContent = new char[typeLen + 1];
    //解析TS的字符串参数,将内容赋值到char数组中
    napi_get_value_string_utf8(env, args[0], extContent, typeLen + 1, &typeLen);
      
    napi_value result;
    //将字符串赋值给napi_value
    napi_create_string_utf8(env, extContent, typeLen + 1, &result);

    //释放数组空间
    delete[] extContent;

    //返回结果
    return result;
}

C++返回数组类型

例如:generatorMockArray函数

static napi_value generatorMockArray(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    //解析TS参数值,这里的值代表需要初始化的数组容量
    int result;
    napi_get_value_int32(env, args[0], &result);

    napi_value ret;
    // 创建返回类型为数组的对象
    napi_create_array(env, &ret);

    const int arrayLength = 4;
    const char *target[arrayLength] = {"Blue", "Red", "Orange", "Yellow"};

    // 根据TS入参设置数组的初始值
    for (int i = 0; i < result && i < arrayLength; i++) {
        napi_value tempValue;
        //创建字符串类型的tempValue对象,把字符串赋值给tempValue
        napi_create_string_utf8(env, target[i], strlen(target[i]),&tempValue);
        // 数组中添加元素
        napi_set_element(env, ret, i, tempValue);
    }

    return ret;
}

C++返回Object类型对象

例如:getCustomObject函数

/**
 * 获取mock对象
 * 
 * 格式
 * {
 *   "testNumber" : 123,
 *   "testString" : "welcome",
 *   "testNested" : { "child" : "welcome"}
 * }
 * 
 * @param env
 * @param info
 * @return 
 */
static napi_value getCustomObject(napi_env env, napi_callback_info info) {

    //创建一个数字类型的对象: value_number
    napi_value value_number;
    napi_create_int32(env, 123, &value_number);

    //创建一个字符串类型的对象: value_string
    char *contentStr = "welcome";

    napi_value value_string;
    napi_create_string_utf8(env, contentStr, strlen(contentStr), &value_string);

    //创建一个Object类型的对象:childObj
    napi_value childObj;
    napi_create_object(env, &childObj);
    //childObj对象添加一个名为“child”的属性,其值为value_string
    napi_set_named_property(env, childObj, "child", value_string);
    
    //创建一个返回类型为Object的对象: obj
    napi_value obj;
    napi_create_object(env, &obj);
    
    //obj对象添加一个名为“testNumber”的属性,其值为value_number
    napi_set_named_property(env, obj, "testNumber", value_number);
    
    //obj对象添加一个名为“testString”的属性,其值为value_string
    napi_set_named_property(env, obj, "testString", value_string);
    
    //obj对象添加一个名为“testNested”的属性,其值为childObj
    napi_set_named_property(env, obj, "testNested", childObj);
    
    return obj;
}

CMakeLists.txt

# cmake 最小版本
cmake_minimum_required(VERSION 3.4.1)
# 工程名称:HarmonyLearn
project(HarmonyLearn)

# set命令,格式为set(key value),表示设置key的值为value,其中value可以是路径,也可以是许多文件。
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

# 添加项目编译所需要的头文件的目录
include_directories(${NATIVERENDER_ROOT_PATH}
                    ${NATIVERENDER_ROOT_PATH}/include)


# 生成目标库文件customnapi.so,customnapi表示最终的库名称,SHARED表示生成的是动态链接库,
# main.cpp, sm4.cpp, util.cpp 表示最终生成的libcustomnapi.so中所包含的源码
# 如果要生成静态链接库,把SHARED该成STATIC即可
add_library(customnapi SHARED main.cpp
                         sm4.cpp
                         util.cpp)
# 把libcustomnapi.so链接到libace_napi.z.so上
target_link_libraries(customnapi PUBLIC libace_napi.z.so)

结尾

如果平时不太写C/C++的情况下,上手写NAPI,还是比较困难的,相当于HarmonyOS新手不知道如何展示一个文字一样。

最后呢,很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

高清完整版请点击《鸿蒙NEXT星河版开发学习文档》

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细资料鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

《鸿蒙 (OpenHarmony)开发学习视频》

图片

《鸿蒙生态应用开发V2.0白皮书》

图片

《鸿蒙 (OpenHarmony)开发基础到实战手册》

获取这份鸿蒙星河版学习资料,请点击→《鸿蒙NEXT星河版开发学习文档》

OpenHarmony北向、南向开发环境搭建

图片

《鸿蒙开发基础》

  1. ArkTS语言

  2. 安装DevEco Studio

  3. 运用你的第一个ArkTS应用

  4. ArkUI声明式UI开发

  5. .……

图片

《鸿蒙开发进阶》

  1. Stage模型入门

  2. 网络管理

  3. 数据管理

  4. 电话服务

  5. 分布式应用开发

  6. 通知与窗口管理

  7. 多媒体技术

  8. 安全技能

  9. 任务管理

  10. WebGL

  11. 国际化开发

  12. 应用测试

  13. DFX面向未来设计

  14. 鸿蒙系统移植和裁剪定制

  15. ……

图片

《鸿蒙开发实战》

  1. ArkTS实践

  2. UIAbility应用

  3. 网络案例

  4. ……

图片

 获取这份鸿蒙星河版学习资料,请点击→《鸿蒙NEXT星河版开发学习文档》

总结

鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Napi::String 是一种类型,它在 C++ 的 Node.js API 中用于表示 JavaScript 字符串。另一方面,std::string 是 C++ 标准库中的一个类,用于表示和操作字符串。 如果你想在 Node.js 中使用 C++ 代码来处理 JavaScript 字符串,并返回一个 std::string,那么你可以使用 Napi::String 和 Napi::Value 的类型转换。 以下是一个简单的示例,说明如何将 Napi::String 转换为 std::string: ```cpp #include <node_api.h> #include <string> void Example(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "Hello from C++").ToLocalChecked()); } void Init(v8::Local<v8::Object> exports) { NODE_SET_METHOD(exports, "example", Example); } NODE_MODULE(NODE_GYP_MODULE_NAME, Init) ``` 这段代码中,我们创建了一个名为 `example` 的方法,它接收一个参数(即 JavaScript 对象),并返回一个 Napi::String。然后,我们使用 Node.js 的 `NODE_SET_METHOD` 宏将这个方法添加到 `exports` 对象中。 然后,在 Node.js 中调用这个方法时,你会得到一个 Napi::String 对象。如果你想将其转换为 std::string,你可以使用 `Napi::String::Utf8Value` 方法来获取一个指针数组,并使用 C++ 的 `std::string` 来操作这些指针。例如: ```cpp std::string cppString; for (char* c : Napi::String::Utf8Value(args.As<String>())) { cppString += c; } ``` 这段代码将 Napi::String 对象转换为 std::string 对象。需要注意的是,这里使用的 C++ 的字符串是 Unicode 字符串,需要正确处理宽字符或字符串字面量以获得正确的结果。如果输入的 JavaScript 字符串是非 ASCII 字符(如 UTF-8 编码的 Unicode),可能需要额外处理以确保正确的编码和解码。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值