如何将C++中的复杂类传递至ArkTS
===================

#### 问题现象

1.  HarmonyOS项目中，如何从C++侧向ArkTS侧传递包含子对象和对象数组的复合类型？
2.  Native侧导出的多个类中包含相同的方法，如何在Index.d.ts文件以及napi\_init.cpp文件中做区分？

#### 背景知识

[NDK开发](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ndk-development-overview)是HarmonyOS SDK提供的Native API、相应编译脚本和编译工具链的集合，方便开发者使用C或C++语言实现应用的关键功能。

#### 解决方案

#### \[h2\]场景一：从C++侧向ArkTS侧传递包含子对象和对象数组的复合类型。

在NDK开发中，当需要从C++向ArkTS传递包含子对象和对象数组的复合类型时，需使用Node-API的对象构建接口，具体示例如下：

1.  C++侧传递代码：
    
    #include "napi/native\_api.h"
    
    _// 创建SubObj对象_
    napi\_value CreateSubObj(napi\_env env, int age) 
    {
        napi\_value subObj;
        napi\_create\_object(env, &subObj);
    
        napi\_value ageValue;
        napi\_create\_int32(env, age, &ageValue);
        napi\_set\_named\_property(env, subObj, "age", ageValue);
    
        return subObj;
    }
    
    _// 创建SuperObj对象_
    static napi\_value CreateSuperObj(napi\_env env, napi\_callback\_info info) 
    {
       _ // 创建主对象_
        napi\_value superObj;
        napi\_create\_object(env, &superObj);
    
      _  // 添加基础属性_
        napi\_value mainAge;
        napi\_create\_int32(env, 40, &mainAge);
        napi\_set\_named\_property(env, superObj, "age", mainAge);
    
      _  // 添加子对象_
        napi\_value subObj = CreateSubObj(env, 25);
        napi\_set\_named\_property(env, superObj, "subObj", subObj);
    
       _ // 创建子对象数组_
        napi\_value subObjs;
        napi\_create\_array(env, &subObjs);
    
       _ // 向数组添加元素_
        for (int i = 0; i < 3; i++) {
            napi\_value item = CreateSubObj(env, i + 10);
            napi\_set\_element(env, subObjs, i, item);    
        }
        napi\_set\_named\_property(env, superObj, "subObjs", subObjs);
    
        return superObj;
    }
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports)
    {
        napi\_property\_descriptor desc\[\] = {
            { "createSuperObj", nullptr, CreateSuperObj, nullptr, nullptr, nullptr, napi\_default, nullptr }
        };
        napi\_define\_properties(env, exports, sizeof(desc) / sizeof(desc\[0\]), desc);
        return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule = {
        .nm\_version = 1,
        .nm\_flags = 0,
        .nm\_filename = nullptr,
        .nm\_register\_func = Init,
        .nm\_modname = "entry",
        .nm\_priv = ((void\*)0),
        .reserved = { 0 },
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterEntryModule(void)
    {
        napi\_module\_register(&demoModule);
    }
    
2.  Index.d.ts文件接口声明：
    
    export type SubObj = {
      age: number;
    }
    
    export type SuperObj = {
      subObj: SubObj;
      subObjs: SubObj\[\];
      age: number;
    }
    
    export const createSuperObj: () => SuperObj;
    
3.  ArkTS接收侧代码：
    
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import testNapi from 'libentry.so';
    
    const DOMAIN = 0x0000;
    
    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';
      superObj = testNapi.createSuperObj();
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                this.message = 'Welcome';
                hilog.info(DOMAIN, 'testTag', 'Main age: %{public}d', this.superObj.age);
                hilog.info(DOMAIN, 'testTag', 'SubObj age: %{public}d', this.superObj.subObj.age);
                hilog.info(DOMAIN, 'testTag', 'First subObj in array: %{public}d',
                  this.superObj.subObjs\[0\].age);
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    

#### \[h2\]场景二：从C++侧向ArkTS侧导出多个包含相同方法的类。

Native侧导出多个包含相同方法的类时，可以分别在每个类中单独实现注册函数用于方法导出，完整示例代码如下：

1.  Native侧分别实现注册函数：
    
    #include "napi/native\_api.h"
    #include "hilog/log.h"
    
    class SameClassOne {
    public:
        static napi\_value Constructor(napi\_env env, napi\_callback\_info info);
        static napi\_value Add(napi\_env env, napi\_callback\_info info);
        static void Init(napi\_env env, napi\_value exports);
    };
    
    class SameClassTwo {
    public:
        static napi\_value Constructor(napi\_env env, napi\_callback\_info info);
        static napi\_value Add(napi\_env env, napi\_callback\_info info);
        static void Init(napi\_env env, napi\_value exports);
    };
    
    napi\_value SameClassOne::Constructor(napi\_env env, napi\_callback\_info info) 
    {
        OH\_LOG\_INFO(LOG\_APP, "enter SameClassOne::Constructor");
    
        napi\_value jsthis;
        napi\_get\_cb\_info(env, info, 0, nullptr, &jsthis, nullptr);
    
        return jsthis;
    }
    
    napi\_value SameClassOne::Add(napi\_env env, napi\_callback\_info info) 
    {
        size\_t argc = 2;
        napi\_value args\[2\] = {nullptr};
    
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
    
        napi\_valuetype valuetype0;
        napi\_typeof(env, args\[0\], &valuetype0);
    
        napi\_valuetype valuetype1;
        napi\_typeof(env, args\[1\], &valuetype1);
    
        double value0;
        napi\_get\_value\_double(env, args\[0\], &value0);
    
        double value1;
        napi\_get\_value\_double(env, args\[1\], &value1);
    
        napi\_value sum;
        napi\_create\_double(env, value0 + value1, &sum);
    
        return sum;
    }
    
    void SameClassOne::Init(napi\_env env, napi\_value exports) 
    {
        OH\_LOG\_INFO(LOG\_APP, "SameClassOne::Init");
        napi\_status result = napi\_ok;
        napi\_property\_descriptor desc\[\] = {
            {"Add", nullptr, SameClassOne::Add, nullptr, nullptr, nullptr, napi\_default, nullptr}};
    
        napi\_value sameClassOne;
        result = napi\_define\_class(env, "SameClassOne", NAPI\_AUTO\_LENGTH, SameClassOne::Constructor, nullptr,
                                   sizeof(desc) / sizeof(desc\[0\]), desc, &sameClassOne);
    
        result = napi\_set\_named\_property(env, exports, "SameClassOne", sameClassOne);
    
        return;
    }
    
    napi\_value SameClassTwo::Constructor(napi\_env env, napi\_callback\_info info) 
    {
        OH\_LOG\_INFO(LOG\_APP, "enter SameClassTwo::Constructor");
    
        napi\_value jsthis;
        napi\_get\_cb\_info(env, info, 0, nullptr, &jsthis, nullptr);
    
        return jsthis;
    }
    
    napi\_value SameClassTwo::Add(napi\_env env, napi\_callback\_info info) 
    {
        size\_t argc = 2;
        napi\_value args\[2\] = {nullptr};
    
        napi\_get\_cb\_info(env, info, &argc, args, nullptr, nullptr);
    
        napi\_valuetype valuetype0;
        napi\_typeof(env, args\[0\], &valuetype0);
    
        napi\_valuetype valuetype1;
        napi\_typeof(env, args\[1\], &valuetype1);
    
        double value0;
        napi\_get\_value\_double(env, args\[0\], &value0);
    
        double value1;
        napi\_get\_value\_double(env, args\[1\], &value1);
    
        napi\_value sum;
        napi\_create\_double(env, value0 + value1, &sum);
    
        return sum;
    }
    
    void SameClassTwo::Init(napi\_env env, napi\_value exports) 
    {
        OH\_LOG\_INFO(LOG\_APP, "SameClassTwo::Init");
        napi\_status result = napi\_ok;
        napi\_property\_descriptor desc\[\] = {
            {"Add", nullptr, SameClassTwo::Add, nullptr, nullptr, nullptr, napi\_default, nullptr}};
    
        napi\_value sameClassTwo;
        result = napi\_define\_class(env, "SameClassTwo", NAPI\_AUTO\_LENGTH, SameClassTwo::Constructor, nullptr,
                                   sizeof(desc) / sizeof(desc\[0\]), desc, &sameClassTwo);
    
        result = napi\_set\_named\_property(env, exports, "SameClassTwo", sameClassTwo);
    
        return;
    }
    
    EXTERN\_C\_START
    static napi\_value Init(napi\_env env, napi\_value exports) 
    {
    
        SameClassOne::Init(env, exports);
        SameClassTwo::Init(env, exports);
        return exports;
    }
    EXTERN\_C\_END
    
    static napi\_module demoModule = {
        .nm\_version = 1,
        .nm\_flags = 0,
        .nm\_filename = nullptr,
        .nm\_register\_func = Init,
        .nm\_modname = "library",
        .nm\_priv = ((void \*)0),
        .reserved = {0},
    };
    
    extern "C" \_\_attribute\_\_((constructor)) void RegisterLibraryModule(void) { napi\_module\_register(&demoModule); }
    
2.  在Index.d.ts文件中声明方法：
    
    export class SameClassOne {
      constructor();
    
      Add(a: number, b: number);
    }
    
    export class SameClassTwo {
      constructor();
    
      Add(a: number, b: number);
    }
    
3.  ArkTS侧调用：
    
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import testNapi from 'liblibrary.so';
    
    const DOMAIN = 0x0000;
    
    @Component
    export struct MainPage {
      @State message: string = 'Hello World';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page\_text\_font\_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                let sameClassOne: testNapi.SameClassOne = new testNapi.SameClassOne();
                let sameClassTwo: testNapi.SameClassTwo = new testNapi.SameClassTwo();
                this.message = 'Welcome';
                hilog.info(DOMAIN, 'testTag', 'sameClassOne 2 + 3 = %{public}d', sameClassOne.Add(2, 3));
                hilog.info(DOMAIN, 'testTag', 'sameClassTwo 3 + 4 = %{public}d', sameClassTwo.Add(3, 4));
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    

#### FAQ

Q：如何在使用NAPI接口导出两个类的同时定义两个类的继承关系？

A：当前NAPI未提供实现继承对象的方法。可以参考JavaScript修改原型链的方法，达到类似的效果：

_// 定义构造函数_
function Person(name, age) {
    this.name = name
    this.age = age
}

Person.prototype.running = function () {
    console.info("跑步")
}
Person.prototype.eatting = function () {
    console.info("吃饭")
}

_// 定义学生类_
function Student(name, age, sno, score) {
    this.name = name
    this.age = age
    this.sno = sno
    this.score = score
}

_// 创建一个父类的实例对象(new Person())用这个实例对象来作为子类的原型对象_
let p1 = new Person()

Student.prototype = p1

Student.prototype.studying = function () {
    console.info("学习")
}

_// 实现方法继承_
let stu1 = new Student("hdc", 21, 111, 100)
stu1.running() _// 跑步_