Calling ArkTS Functions from Non-ArkTS Threads in HarmonyOS

Module Registration

First, we need to register the native module. This is done in the queue_work.cpp file:

// queue_work.cpp
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        { "queueWork", nullptr, QueueWork, 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 nativeModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "queue_work",
    .nm_priv = nullptr,
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterQueueWorkModule() {
    napi_module_register(&nativeModule);
}

Queuing Work to the ArkTS Thread

Next, we need to get the event loop associated with the NAPI environment and queue a task to be executed on the ArkTS thread. This is also done in the queue_work.cpp file:

// queue_work.cpp
#include <thread>
#include <napi/native_api.h>
#include <uv.h>

struct CallbackContext {
    napi_env env = nullptr;
    napi_ref callbackRef = nullptr;
    int32_t retData = 0;
};

void StartThread(CallbackContext* context) {
    uv_loop_s* loop = nullptr;
    napi_get_uv_event_loop(context->env, &loop);

    // Create a uv_work_t to pass private data
    uv_work_t* work = new uv_work_t;
    context->retData = 1;
    work->data = context;

    // Queue the work to the ArkTS thread
    uv_queue_work(
        loop,
        work,
        [](uv_work_t* work) {},
        [](uv_work_t* work, int status) {
            CallbackContext* context = reinterpret_cast<callbackcontext>(work->data);
            napi_handle_scope scope = nullptr;
            napi_open_handle_scope(context->env, &scope);
            if (scope == nullptr) {
                return;
            }

            // Call the ArkTS function
            napi_value callback = nullptr;
            napi_get_reference_value(context->env, context->callbackRef, &callback);
            napi_value retArg;
            napi_create_int32(context->env, context->retData, &retArg);
            napi_value ret;
            napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
            napi_delete_reference(context->env, context->callbackRef);

            // Close the handle scope
            napi_close_handle_scope(context->env, scope);

            if (work != nullptr) {
                delete work;
            }
            delete context;
        }
    );
}

static napi_value QueueWork(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value argv[1] = {nullptr};
    napi_value thisVar = nullptr;
    void* data = nullptr;
    napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);

    // Parameter check
    if (argc < 1) {
        napi_throw_error(env, "ParameterError", "one param expected");
        return nullptr;
    }
    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, argv[0], &valueType);
    if (valueType != napi_function) {
        napi_throw_error(env, "ParameterError", "function expected");
        return nullptr;
    }

    // Save the env and callback for later use
    auto asyncContext = new CallbackContext();
    asyncContext->env = env;
    napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
    std::thread testThread(StartThread, asyncContext);
    testThread.detach();

    return nullptr;
}
</callbackcontext>

ArkTS Side Example Code

On the ArkTS side, you can call the queueWork function as follows:

import { queueWork } from 'libqueue_work.so';

queueWork((result: number) => {
    console.log("result = " + result);
});

Tags: HarmonyOS ArkTS NAPI libuv C++

Posted on Fri, 21 Aug 2026 16:48:40 +0000 by Tonsil