diff --git a/.vscode/launch.json b/.vscode/launch.json index 73d2233b..3e2831e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,8 +6,8 @@ "request": "launch", "name": "Launch Program", "runtimeArgs": ["--expose-gc"], - "program": "${workspaceFolder}/test/function/function.test.js", - "args": ["--expose-gc"] + "program": "${workspaceFolder}/test/emnapi/emnapi.test.js", + "args": [] }, { "name": "Windows", diff --git a/README.md b/README.md index c31cadd1..7ca5c44b 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ npm test These APIs always return `napi_generic_failure`. -- ~~napi_create_external_arraybuffer~~ +- ~~napi_create_external_arraybuffer~~ (use [`emnapi_create_external_uint8array`][] instead) - ~~napi_adjust_external_memory~~ - ~~napi_detach_arraybuffer~~ - ~~napi_is_detached_arraybuffer~~ @@ -364,3 +364,64 @@ These APIs always return `napi_generic_failure`. - napi_object_seal - napi_type_tag_object - napi_check_object_type_tag + +### Additional + +These APIs are in `emnapi.h`. + +#### emnapi_get_module_object + +```c +napi_status emnapi_get_module_object(napi_env env, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[out] result`: A `napi_value` representing the `Module` object of Emscripten. + +Returns `napi_ok` if the API succeeded. + +#### emnapi_get_module_property + +```c +napi_status emnapi_get_module_property(napi_env env, + const char* utf8name, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] utf8Name`: The name of the `Module` property to get. +* `[out] result`: The value of the property. + +Returns `napi_ok` if the API succeeded. + +#### emnapi_create_external_uint8array + +```c +napi_status emnapi_create_external_uint8array(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] external_data`: Pointer to the underlying byte buffer of the + `Uint8Array`. +* `[in] byte_length`: The length in bytes of the underlying buffer. +* `[in] finalize_cb`: Optional callback to call when the `Uint8Array` is being + collected. +* `[in] finalize_hint`: Optional hint to pass to the finalize callback during + collection. +* `[out] result`: A `napi_value` representing a JavaScript `Uint8Array`. + +Returns `napi_ok` if the API succeeded. +Returns `napi_generic_failure` if `FinalizationRegistry` or `WeakRef` is not supported. + +This API returns an N-API value corresponding to a JavaScript `Uint8Array`. +The underlying byte buffer of the `Uint8Array` is externally allocated and +managed. The caller must ensure that the byte buffer remains valid until the +finalize callback is called. + +[`emnapi_create_external_uint8array`]: #emnapi_create_external_uint8array diff --git a/README_CN.md b/README_CN.md index 2587e537..f7a2a92c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -232,7 +232,7 @@ npm test 以下 API 不可实现,调用后将永远返回 `napi_generic_failure` 状态。 -- ~~napi_create_external_arraybuffer~~ +- ~~napi_create_external_arraybuffer~~ (使用 [`emnapi_create_external_uint8array`][] 代替) - ~~napi_adjust_external_memory~~ - ~~napi_detach_arraybuffer~~ - ~~napi_is_detached_arraybuffer~~ @@ -361,3 +361,64 @@ npm test - napi_object_seal - napi_type_tag_object - napi_check_object_type_tag + +### 额外新增的 API + +以下 API 在 `emnapi.h` 头文件中。 + +#### emnapi_get_module_object + +```c +napi_status emnapi_get_module_object(napi_env env, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[out] result`: A `napi_value` representing the `Module` object of Emscripten. + +Returns `napi_ok` if the API succeeded. + +#### emnapi_get_module_property + +```c +napi_status emnapi_get_module_property(napi_env env, + const char* utf8name, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] utf8Name`: The name of the `Module` property to get. +* `[out] result`: The value of the property. + +Returns `napi_ok` if the API succeeded. + +#### emnapi_create_external_uint8array + +```c +napi_status emnapi_create_external_uint8array(napi_env env, + void* external_data, + size_t byte_length, + napi_finalize finalize_cb, + void* finalize_hint, + napi_value* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] external_data`: Pointer to the underlying byte buffer of the + `Uint8Array`. +* `[in] byte_length`: The length in bytes of the underlying buffer. +* `[in] finalize_cb`: Optional callback to call when the `Uint8Array` is being + collected. +* `[in] finalize_hint`: Optional hint to pass to the finalize callback during + collection. +* `[out] result`: A `napi_value` representing a JavaScript `Uint8Array`. + +Returns `napi_ok` if the API succeeded. +Returns `napi_generic_failure` if `FinalizationRegistry` or `WeakRef` is not supported. + +This API returns an N-API value corresponding to a JavaScript `Uint8Array`. +The underlying byte buffer of the `Uint8Array` is externally allocated and +managed. The caller must ensure that the byte buffer remains valid until the +finalize callback is called. + +[`emnapi_create_external_uint8array`]: #emnapi_create_external_uint8array diff --git a/cgen.config.js b/cgen.config.js index f2f1bc80..52d6d3e0 100644 --- a/cgen.config.js +++ b/cgen.config.js @@ -88,6 +88,7 @@ module.exports = function (_options, { isDebug, isEmscripten }) { createTarget('number', ['./test/number/binding.c'], true), createTarget('symbol', ['./test/symbol/binding.c'], true), createTarget('typedarray', ['./test/typedarray/binding.c'], true), + createTarget('emnapi', ['./test/emnapi/binding.c'], true), createNodeAddonApiTarget('n_hello', ['./test/node-addon-api/hello/binding.cc']) ] diff --git a/include/emnapi.h b/include/emnapi.h index fa3169f7..746e4bd8 100644 --- a/include/emnapi.h +++ b/include/emnapi.h @@ -6,6 +6,10 @@ EXTERN_C_START +NAPI_EXTERN +napi_status emnapi_get_module_object(napi_env env, + napi_value* result); + NAPI_EXTERN napi_status emnapi_get_module_property(napi_env env, const char* utf8name, diff --git a/lib/emnapi.ts b/lib/emnapi.ts new file mode 100644 index 00000000..4c75fe8a --- /dev/null +++ b/lib/emnapi.ts @@ -0,0 +1,59 @@ +function emnapi_get_module_object (env: napi_env, result: Pointer): emnapi.napi_status { + return emnapi.preamble(env, (envObject) => { + return emnapi.checkArgs(env, [result], () => { + HEAP32[result >> 2] = envObject.ensureHandleId(Module) + return emnapi.getReturnStatus(env) + }) + }) +} + +function emnapi_get_module_property (env: napi_env, utf8name: const_char_p, result: Pointer): emnapi.napi_status { + return emnapi.preamble(env, (envObject) => { + return emnapi.checkArgs(env, [utf8name, result], () => { + HEAP32[result >> 2] = envObject.ensureHandleId(Module[UTF8ToString(utf8name)]) + return emnapi.getReturnStatus(env) + }) + }) +} + +function emnapi_create_external_uint8array ( + env: napi_env, + external_data: void_p, + byte_length: size_t, + finalize_cb: napi_finalize, + finalize_hint: void_p, + result: Pointer +): emnapi.napi_status { + if (!emnapi.supportFinalizer) return emnapi.napi_set_last_error(env, emnapi.napi_status.napi_generic_failure) + return emnapi.preamble(env, (envObject) => { + return emnapi.checkArgs(env, [result], () => { + byte_length = byte_length >>> 0 + if (external_data === emnapi.NULL) { + byte_length = 0 + } + + if (byte_length > 2147483647) { + throw new RangeError('Cannot create a Uint8Array larger than 2147483647 bytes') + } + if ((external_data + byte_length) > HEAPU8.buffer.byteLength) { + throw new RangeError('Memory out of range') + } + const u8arr = new Uint8Array(HEAPU8.buffer, external_data, byte_length) + const handle = envObject.getCurrentScope().add(u8arr) + if (finalize_cb !== emnapi.NULL) { + const status = emnapi.wrap(emnapi.WrapType.anonymous, env, handle.id, external_data, finalize_cb, finalize_hint, emnapi.NULL) + if (status === emnapi.napi_status.napi_pending_exception) { + throw envObject.tryCatch.extractException() + } else if (status !== emnapi.napi_status.napi_ok) { + return emnapi.napi_set_last_error(env, status) + } + } + HEAP32[result >> 2] = handle.id + return emnapi.getReturnStatus(env) + }) + }) +} + +emnapiImplement('emnapi_get_module_object', emnapi_get_module_object) +emnapiImplement('emnapi_get_module_property', emnapi_get_module_property) +emnapiImplement('emnapi_create_external_uint8array', emnapi_create_external_uint8array) diff --git a/package.json b/package.json index f94f2a71..186ddbb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tybys/emnapi", - "version": "0.1.4", + "version": "0.2.0", "description": "Node-API implementation for Emscripten", "main": "index.js", "typings": "index.d.ts", diff --git a/test/emnapi/binding.c b/test/emnapi/binding.c new file mode 100644 index 00000000..bda9fd29 --- /dev/null +++ b/test/emnapi/binding.c @@ -0,0 +1,84 @@ +#include +#include "js_native_api.h" +#include "emnapi.h" +#include "../common.h" + +static napi_value getModuleObject(napi_env env, napi_callback_info info) { + napi_value result; + NAPI_CALL(env, emnapi_get_module_object(env, &result)); + + return result; +} + +static napi_value getModuleProperty(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_string, + "Wrong type of arguments. Expects a string as first argument."); + + char name[64] = { 0 }; + NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], name, 64, NULL)); + + napi_value result; + NAPI_CALL(env, emnapi_get_module_property(env, name, &result)); + + return result; +} + +static void FinalizeCallback(napi_env env, + void* finalize_data, + void* finalize_hint) +{ + free(finalize_data); +} + +static napi_value External(napi_env env, napi_callback_info info) { + const uint8_t nElem = 3; + int8_t* externalData = malloc(nElem*sizeof(int8_t)); + externalData[0] = 0; + externalData[1] = 1; + externalData[2] = 2; + + napi_value output_array; + NAPI_CALL(env, emnapi_create_external_uint8array( + env, + externalData, + nElem*sizeof(int8_t), + FinalizeCallback, + NULL, // finalize_hint + &output_array)); + + return output_array; +} + + +static napi_value NullArrayBuffer(napi_env env, napi_callback_info info) { + static void* data = NULL; + napi_value output_array; + NAPI_CALL(env, + emnapi_create_external_uint8array(env, data, 0, NULL, NULL, &output_array)); + return output_array; +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("getModuleObject", getModuleObject), + DECLARE_NAPI_PROPERTY("getModuleProperty", getModuleProperty), + DECLARE_NAPI_PROPERTY("External", External), + DECLARE_NAPI_PROPERTY("NullArrayBuffer", NullArrayBuffer) + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} +EXTERN_C_END diff --git a/test/emnapi/emnapi.test.js b/test/emnapi/emnapi.test.js new file mode 100644 index 00000000..f7225342 --- /dev/null +++ b/test/emnapi/emnapi.test.js @@ -0,0 +1,28 @@ +/* eslint-disable no-new-object */ +/* eslint-disable no-new-wrappers */ +/* eslint-disable camelcase */ +'use strict' +const assert = require('assert') +const { load } = require('../util') + +module.exports = load('emnapi').then(test_typedarray => { + const mod = test_typedarray.getModuleObject() + + const HEAPU8 = test_typedarray.getModuleProperty('HEAPU8') + const mem = HEAPU8.buffer + assert.ok(mem instanceof ArrayBuffer) + assert.strictEqual(mod.HEAPU8, HEAPU8) + + const externalResult = test_typedarray.External() + assert.ok(externalResult instanceof Uint8Array) + assert.strictEqual(externalResult.length, 3) + assert.strictEqual(externalResult[0], 0) + assert.strictEqual(externalResult[1], 1) + assert.strictEqual(externalResult[2], 2) + + const buffer = test_typedarray.NullArrayBuffer() + assert.ok(buffer instanceof Uint8Array) + assert.strictEqual(buffer.length, 0) + assert.strictEqual(buffer.buffer, mem) + assert.notStrictEqual(buffer.buffer.byteLength, 0) +}) diff --git a/test/general/binding.c b/test/general/binding.c index 9733b574..dba2e0e2 100644 --- a/test/general/binding.c +++ b/test/general/binding.c @@ -78,16 +78,13 @@ static napi_value createNapiError(napi_env env, napi_callback_info info) { "Last error info code should match last status"); NAPI_ASSERT(env, error_info->error_message, "Last error info message should not be null"); - printf("create, code: %d, message: %s\n", error_info->error_code, error_info->error_message); NAPI_CALL(env, napi_get_last_error_info(env, &error_info)); - printf("create, code: %d, message: %s\n", error_info->error_code, error_info->error_message); return NULL; } static napi_value testNapiErrorCleanup(napi_env env, napi_callback_info info) { const napi_extended_error_info *error_info = 0; NAPI_CALL(env, napi_get_last_error_info(env, &error_info)); - printf("code: %d, message: %s\n", error_info->error_code, error_info->error_message); napi_value result; bool is_ok = error_info->error_code == napi_ok; NAPI_CALL(env, napi_get_boolean(env, is_ok, &result)); diff --git a/test/general/status.test.js b/test/general/status.test.js index 060aebfb..b042b91f 100644 --- a/test/general/status.test.js +++ b/test/general/status.test.js @@ -7,7 +7,5 @@ const { load } = require('../util') module.exports = load('general').then(addon => { addon.createNapiError() - const r = addon.testNapiErrorCleanup() - console.log(r) - assert(r, 'napi_status cleaned up for second call') + assert(addon.testNapiErrorCleanup(), 'napi_status cleaned up for second call') })