目录

native如何通过父类创建子类并返回到ArkTS侧

问题现象

C/C++侧同时创建了父类和子类,可以通过父类方法创建子类,ArkTS侧如何调用此类native实现获取到子类实例。

背景知识

解决方案

代码示例:

  1. native侧Parent类为父类,Child类为子类。
    extern class Child;
    
    class Parent {
      public:
        void DoSomething();
      Child *CreateChild();
    };
    
    class Child : Parent {
      public:
        void DoSomething();
    };

    通过napi方法napi_define_class定义ArkTS侧的Parent类和Child类并导出。

    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports)
    {
        napi_property_descriptor parentDesc[] = {
            {"doSomething", nullptr, ParentDoSomething, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"createChild", nullptr, ParentCreateChild, nullptr, nullptr, nullptr, napi_default, nullptr}
        };
    
        napi_property_descriptor childDesc[] = {
            {"doSomething", nullptr, ChildDoSomething, nullptr, nullptr, nullptr, napi_default, nullptr}
        };
    
        napi_value parentConstructor = nullptr;
        napi_define_class(env, "Parent", NAPI_AUTO_LENGTH, ParentConstructor, nullptr, sizeof(parentDesc) / sizeof(parentDesc[0]),
                          parentDesc, &parentConstructor);
        napi_set_named_property(env, exports, "Parent", parentConstructor);
    
        napi_value childConstructor = nullptr;
        napi_define_class(env, "Child", NAPI_AUTO_LENGTH, ChildConstructor, nullptr, sizeof(childDesc) / sizeof(childDesc[0]),
                          childDesc, &childConstructor);
        napi_set_named_property(env, exports, "Child", childConstructor);
        return exports;
    }
    EXTERN_C_END

    Index.d.ts中导出的定义:

    export class Parent {
      constructor()
    
      doSomething(): void
    
      createChild(obj: ESObject): Child
    }
    
    export class Child {
      constructor()
    
      doSomething(): void
    }
  2. 执行native侧的ParentCreateChild函数。
    1. 通过napi_new_instance在native端创建一个Child类的实例(也就是Child的native对象)。
    2. 该Child实例会被包装成一个可被ArkTS使用的对象(通过napi_wrap绑定)。
    3. 最后,该Child实例被返回给ArkTS侧的createChild()方法。
      • 在native层创建Child实例,通过napi_wrap包装并绑定,最终返回给ArkTS侧使用。
      static napi_value ParentCreateChild(napi_env env, napi_callback_info info)
      {
        size_t argc = 1;
        napi_value args[1] = {nullptr};
        napi_value jsthis;
        Parent *instanceParent = nullptr;
        bool bRet = false;
        napi_value result = nullptr;
        napi_status status;
      
        status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr);
        if (status != napi_ok) {
          OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_get_cb_info fail.Status:%{public}d", status);
          return nullptr;
        }
      
        status = napi_unwrap(env, jsthis, reinterpret_cast<void **>(&instanceParent));
        if (status != napi_ok) {
          OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_unwrap fail.Status:%{public}d", status);
          return nullptr;
        }
      
        status = napi_new_instance(env, args[0], 0, nullptr, &result);
        if (status != napi_ok) {
          OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_new_instance fail.Status:%{public}d", status);
          return nullptr;
        }
      
        Child *instanceChild = instanceParent->CreateChild();
      
      
        status = napi_wrap(env, result, reinterpret_cast<void *>(instanceChild), DerefChild, NULL, NULL);
        if (status != napi_ok) {
          // 主动释放内存
          OH_LOG_INFO(LOG_APP, "ParentcreateChild ChildConstructor napi_wrap fail status:%{public}d", status);
          delete instanceChild;
        }
      
        return result;
      }
  3. ArkTS侧的调用逻辑:
    @Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';
    
      build() {
        Row() {
          Column() {
            Text(this.message)
              .fontSize($r('app.float.page_text_font_size'))
              .fontWeight(FontWeight.Bold)
              .onClick(() => {
                this.message = 'Welcome';
                let parent = new testNapi.Parent();
                let child = new testNapi.Child();
                parent.doSomething();
                child.doSomething();
                let newChild: testNapi.Child = parent.createChild(testNapi.Child);
                newChild.doSomething();
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }

完整实现如下:

ArkTS侧实现:

import testNapi from 'libentry.so';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize($r('app.float.page_text_font_size'))
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.message = 'Welcome';
            let parent = new testNapi.Parent();
            let child = new testNapi.Child();
            parent.doSomething();
            child.doSomething();
            let newChild: testNapi.Child = parent.createChild(testNapi.Child);
            newChild.doSomething();
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

native侧实现:

#include "hilog/log.h"
#include "napi/native_api.h"

#define LOG_TAG "test"
extern class Child;

class Parent {
public:
    void DoSomething();
    Child *CreateChild();
};

class Child : Parent {
public:
    void DoSomething();
};
void Parent::DoSomething() { OH_LOG_ERROR(LOG_APP, "Parent DoSomething"); }

Child *Parent::CreateChild()
{
    Child *instance = new Child();
    return instance;
}

void Child::DoSomething() { OH_LOG_ERROR(LOG_APP, "Child DoSomething"); }


static napi_value 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;
}


static void DerefParent(napi_env env, void *data, void *hint)
{
    // 可选的原生回调,用于在ArkTS对象被垃圾回收时释放原生实例
    OH_LOG_INFO(LOG_APP, "Node-API DerefItem");
    Parent *obj = reinterpret_cast<Parent *>(data);
    if (obj != nullptr) {
        delete obj;
    }
}

static void DerefChild(napi_env env, void *data, void *hint)
{
    // 可选的原生回调,用于在ArkTS对象被垃圾回收时释放原生实例
    OH_LOG_INFO(LOG_APP, "Node-API DerefItem");
    Child *obj = reinterpret_cast<Child *>(data);
    if (obj != nullptr) {
        delete obj;
    }
}


static napi_value ParentConstructor(napi_env env, napi_callback_info info)
{
    napi_value undefineVar = nullptr;
    napi_get_undefined(env, &undefineVar);
    napi_value jsInstance = nullptr;
    if (napi_get_cb_info(env, info, nullptr, nullptr, &jsInstance, nullptr) != napi_ok) {
        return undefineVar;
    }

    Parent *instance = new Parent();

    napi_status status = napi_wrap(env, jsInstance, reinterpret_cast<void *>(instance), DerefParent, NULL, NULL);
    if (status != napi_ok) {
        // 主动释放内存
        OH_LOG_INFO(LOG_APP, "ParentConstructor napi_wrap fail");
        delete instance;
    }
    OH_LOG_INFO(LOG_APP, "ParentConstructor success");
    return jsInstance;
}

// 定义类Parent的方法
static napi_value ParentDoSomething(napi_env env, napi_callback_info info)
{
    napi_value jsthis;
    Parent *instance = nullptr;
    bool bRet = false;
    napi_value result = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr);

    napi_unwrap(env, jsthis, reinterpret_cast<void **>(&instance));


    instance->DoSomething();
    return result;
}

static napi_value ParentCreateChild(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_value jsthis;
    Parent *instanceParent = nullptr;
    bool bRet = false;
    napi_value result = nullptr;
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_get_cb_info fail.Status:%{public}d", status);
        return nullptr;
    }

    status = napi_unwrap(env, jsthis, reinterpret_cast<void **>(&instanceParent));
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_unwrap fail.Status:%{public}d", status);
        return nullptr;
    }

    status = napi_new_instance(env, args[0], 0, nullptr, &result);
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "ParentcreateChild napi_new_instance fail.Status:%{public}d", status);
        return nullptr;
    }

    Child *instanceChild = instanceParent->CreateChild();


    status = napi_wrap(env, result, reinterpret_cast<void *>(instanceChild), DerefChild, NULL, NULL);
    if (status != napi_ok) {
        // 主动释放内存
        OH_LOG_INFO(LOG_APP, "ParentcreateChild ChildConstructor napi_wrap fail status:%{public}d", status);
        delete instanceChild;
    }

    return result;
}


// 定义类Child的构造函数
static napi_value ChildConstructor(napi_env env, napi_callback_info info)
{
    napi_value undefineVar = nullptr;
    napi_get_undefined(env, &undefineVar);
    napi_value jsInstance = nullptr;
    if (napi_get_cb_info(env, info, nullptr, nullptr, &jsInstance, nullptr) != napi_ok) {
    return undefineVar;
}

    Child *instance = new Child();
    napi_status status = napi_wrap(env, jsInstance, reinterpret_cast<void *>(instance), DerefChild, NULL, NULL);
    if (status != napi_ok) {
        // 主动释放内存
        OH_LOG_INFO(LOG_APP, "ChildConstructor napi_wrap fail");
        delete instance;
    }
    OH_LOG_INFO(LOG_APP, "ChildConstructor success");
    return jsInstance;
}

// 定义类Child的方法
static napi_value ChildDoSomething(napi_env env, napi_callback_info info)
{
    napi_value jsthis;
    Child *instance = nullptr;
    bool bRet = false;
    napi_value result = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr);

    napi_unwrap(env, jsthis, reinterpret_cast<void **>(&instance));

    instance->DoSomething();
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor parentDesc[] = {
        {"doSomething", nullptr, ParentDoSomething, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"createChild", nullptr, ParentCreateChild, nullptr, nullptr, nullptr, napi_default, nullptr}
    };

    napi_property_descriptor childDesc[] = {
        {"doSomething", nullptr, ChildDoSomething, nullptr, nullptr, nullptr, napi_default, nullptr}
    };

    napi_value parentConstructor = nullptr;
    napi_define_class(env, "Parent", NAPI_AUTO_LENGTH, ParentConstructor, nullptr, sizeof(parentDesc) / sizeof(parentDesc[0]),
                      parentDesc, &parentConstructor);
    napi_set_named_property(env, exports, "Parent", parentConstructor);

    napi_value childConstructor = nullptr;
    napi_define_class(env, "Child", NAPI_AUTO_LENGTH, ChildConstructor, nullptr, sizeof(childDesc) / sizeof(childDesc[0]),
                      childDesc, &childConstructor);
    napi_set_named_property(env, exports, "Child", childConstructor);
    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); }

CMakeLists.txt编译脚本:

# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(MultipleClass)

set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})

if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()

include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)

add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)

常见FAQ

Q:如何构建一个ArkTS指定对象并调用其构造方法?

A:可以通过napi_new_instance接口调用给定的构造函数实现对象的实例化。

总结

通过napi_new_instance可以在native侧创建一个ArkTS的类实例,调用napi_wrap方法与native实例进行绑定,再将ArkTS的类实例返回,即可实现通过父类创建子类并返回的效果。