Sample Project Source Code Analysis
The sample project uses a Native C++ template with the Stage model. This analysis focuses on key implementation files for class object export and lifecycle management.
Class Definition and Method Implementation
NapiTest.h Header File
#ifndef __NAPI_TEST_H__
#define __NAPI_TEST_H__
#include "napi/native_api.h"
#include <js_native_api_types.h>
#include <iostream>
#define NAPI_CLASS_NAME "NapiTestClass"
class NapiTest {
public:
NapiTest() : mEnv(nullptr), mRef(nullptr) {
}
NapiTest(napi_env env) : mEnv(env), mRef(nullptr) {
}
~NapiTest();
// Factory method to create instances for JavaScript
static napi_value Create(napi_env env, napi_callback_info info);
// Initialize JS class and set properties for export
static napi_value Init(napi_env env, napi_value exports);
private:
// Setter method exposed to JavaScript
static napi_value SetMsg(napi_env env, napi_callback_info info);
// Getter method exposed to JavaScript
static napi_value GetMsg(napi_env env, napi_callback_info info);
// JavaScript constructor callback
static napi_value Constructor(napi_env env, napi_callback_info info);
// Finalizer callback (similar to destructor)
static void Destructor(napi_env env, void *nativeObject, void *finalize);
// Class-level reference for constructor
static napi_ref sConstructor_;
// Internal data storage
static std::string _msg;
// Environment storage
napi_env mEnv = nullptr;
// Instance-level reference
napi_ref mRef = nullptr;
};
#endif /* __NAPI_TEST_H__ */
Core NAPI Types
napi_value
The Node-API defines napi_value as an opaque pointer representing JavaScript values. This type encapsulates all ECMAScript primitive types (Boolean, Null, Undefined, Number, BigInt, String, Symbol, Object) and Functon types into a unified interface for data exchange between native code and ArkUI.
napi_ref
This abstract type manages references to napi_value instances, enabling explicit control over JavaScript value lifetimes by defining minimum retention periods.
napi_env
The environment handle (napi_env) carries context information that Node-API implementations use to persist VM-specific state across API calls.
Defining JavaScript Class Methods
Before defining the JS class, specify the methods to export:
napi_property_descriptor desc[] = {
{ "getMsg", nullptr, NapiTest::GetMsg, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "setMsg", nullptr, NapiTest::SetMsg, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "create", nullptr, NapiTest::Create, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_property_descriptor Structure
typedef struct {
const char* utf8name; // UTF-8 encoded property name
napi_value name; // Alternative: napi_value key
napi_callback method; // Function implementation
napi_callback getter; // Get accessor
napi_callback setter; // Set accessor
napi_value value; // Data value
napi_property_attributes attributes;
void* data; // Callback data
} napi_property_descriptor;
Key parameters:
- utf8name: UTF-8 encoded method name (mutually exclusive with
name) - method: Function assigned to the property's value attribute
- attributes: Property characteristics (readable, writable, configurable, etc.)
- data: User data passed to method/getter/setter callbacks
Creating the JavaScript Class
napi_value constructor = nullptr;
if (napi_define_class(env, NAPI_CLASS_NAME, NAPI_AUTO_LENGTH, Constructor, nullptr,
sizeof(desc) / sizeof(desc[0]), desc, &constructor) != napi_ok) {
return nullptr;
}
napi_define_class Function Signature
napi_status napi_define_class(napi_env env,
const char* utf8name,
size_t length,
napi_callback constructor,
void* data,
size_t property_count,
const napi_property_descriptor* properties,
napi_value* result);
Parameters:
- utf8name: C++ class name exposed to JavaScript
- length: Name length, use
NAPI_AUTO_LENGTHfor null-terminated strings - constructor: Callback handling C++ instance creation (must have
napi_callbacksignature) - data: Optional data passed to constructor callback
- property_count: Number of proeprties in the descriptor array
- result: Output napi_value bound to the class constructor
Note: The JavaScript constructor is a function invoked with new. C++ callbacks are functions that third-party APIs call back into your code.
Constructor Implementation
When ArkTS invokes the class with new, the constructor callback executes:
napi_value NapiTest::Constructor(napi_env env, napi_callback_info info)
{
napi_value undefined = nullptr, thisVar = nullptr;
napi_get_undefined(env, &undefined);
if (napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr) == napi_ok && thisVar != nullptr) {
NapiTest *instance = new NapiTest(env);
if (napi_wrap(env, thisVar, reinterpret_cast<void *>(instance), NapiTest::Destructor,
nullptr, &(instance->mRef)) == napi_ok) {
return thisVar;
}
return thisVar;
}
return undefined;
}
void NapiTest::Destructor(napi_env env, void *nativeObject, void *finalize)
{
NapiTest *instance = reinterpret_cast<NapiTest*>(nativeObject);
instance->~NapiTest();
}
napi_wrap Function
napi_status napi_wrap(napi_env env,
napi_value js_object,
void* native_object,
napi_finalize finalize_cb,
void* finalize_hint,
napi_ref* result);
This binds a C++ instance to a JavaScript object and associates lifecycle management:
- js_object: Target JavaScript object
- native_object: C++ instance to bind
- finalize_cb: Callback invoked when the JS object is garbage collected
- result: Reference handle for the bound object
napi_get_cb_info Function
napi_status napi_get_cb_info(napi_env env,
napi_callback_info cbinfo,
size_t* argc,
napi_value* argv,
napi_value* this_arg,
void** data);
Retrieves callback information including:
- argc: Input/output parameter specifying buffer size and actual argument count
- argv: Output buffer for argument values
- this_arg: Output receiver for the JavaScript
thisvalue - data: Optional context data from the callback registration
Exporting the JavaScript Class
Creating the Class Reference
if (napi_create_reference(env, constructor, 1, &sConstructor_) != napi_ok) {
return nullptr;
}
napi_create_reference Function
napi_status napi_create_reference(napi_env env,
napi_value value,
uint32_t initial_refcount,
napi_ref* result);
Creates a strong reference extending the target object's lifetime. The caller must manage the reference lifecycle explicitly.
Binding Properties to Exports
if (napi_set_named_property(env, exports, NAPI_CLASS_NAME, constructor) != napi_ok) {
return nullptr;
}
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
napi_set_named_property Function
napi_status napi_set_named_property(napi_env env,
napi_value object,
const char* utf8Name,
napi_value value);
Sets a named property on the target object using a UTF-8 encoded string key.
napi_define_properties Function
napi_status napi_define_properties(napi_env env,
napi_value object,
size_t property_count,
const napi_property_descriptor* properties);
Batch-defines multiple properties on a JavaScript object in a single call.
Creating Instance Objects via Factory Method
ArkTS can obtain instances through the Create factory method instead of new:
napi_value NapiTest::Create(napi_env env, napi_callback_info info) {
napi_status status;
napi_value constructor = nullptr, result = nullptr;
status = napi_get_reference_value(env, sConstructor_, &constructor);
status = napi_new_instance(env, constructor, 0, nullptr, &result);
auto instance = new NapiTest();
if (napi_wrap(env, result, reinterpret_cast<void *>(instance), Destructor,
nullptr, &(instance->mRef)) == napi_ok) {
return result;
}
return nullptr;
}
napi_get_reference_value Function
napi_status napi_get_reference_value(napi_env env,
napi_ref ref,
napi_value* result);
Retrieves the JavaScript object associated with a reference handle.
napi_new_instance Function
napi_status napi_new_instance(napi_env env,
napi_value cons,
size_t argc,
napi_value* argv,
napi_value* result)
Constructs a new JavaScript object using the specified constructor function.
TypeScript Declaration File
export const create: () => NapiTest;
export class NapiTest {
setMsg(msg: string): void;
getMsg(): string;
}
Alternative structure with static factory method:
export class NapiTest {
create();
setMsg(msg: string): void;
getMsg(): string;
}
The declaration file can be auto-generated from headers using NAPI tooling.
Build Configuration
cmake_minimum_required(VERSION 3.4.1)
project(ObjectWrapTest)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED hello.cpp NapiTest.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
ArkTS Consumer Code
import testNapi from "libentry.so";
@Entry
@Component
struct Index {
@State message: string = "Exported Object"
@State nativePointer: number = 0
private testInstance = testNapi.create();
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
try {
if (this.nativePointer == 0) {
console.info("[NapiTest] Computing 2 + 3");
this.nativePointer = testNapi.add(2, 3);
this.testInstance.setMsg("2+3");
} else {
console.info("[NapiTest] Computing 4 + 5");
this.nativePointer = testNapi.add(4, 5);
this.testInstance.setMsg("4+5");
}
} catch(e) {
console.info("[NapiTest] Error: " + JSON.stringify(e));
}
console.info("[NapiTest] Result: " + this.testInstance.getMsg() + " = " + this.nativePointer);
})
}
.width("100%")
}
.height("100%")
}
}