Skip to content

Latest commit

 

History

History
4776 lines (3925 loc) · 166 KB

n-api.md

File metadata and controls

4776 lines (3925 loc) · 166 KB

N-API

Stability: 2 - Stable

N-API (pronounced N as in the letter, followed by API) is an API for building native Addons. It is independent from the underlying JavaScript runtime (ex V8) and is maintained as part of Node.js itself. This API will be Application Binary Interface (ABI) stable across versions of Node.js. It is intended to insulate Addons from changes in the underlying JavaScript engine and allow modules compiled for one major version to run on later major versions of Node.js without recompilation. The ABI Stability guide provides a more in-depth explanation.

Addons are built/packaged with the same approach/tools outlined in the section titled C++ Addons. The only difference is the set of APIs that are used by the native code. Instead of using the V8 or Native Abstractions for Node.js APIs, the functions available in the N-API are used.

APIs exposed by N-API are generally used to create and manipulate JavaScript values. Concepts and operations generally map to ideas specified in the ECMA262 Language Specification. The APIs have the following properties:

  • All N-API calls return a status code of type napi_status. This status indicates whether the API call succeeded or failed.
  • The API's return value is passed via an out parameter.
  • All JavaScript values are abstracted behind an opaque type named napi_value.
  • In case of an error status code, additional information can be obtained using napi_get_last_error_info. More information can be found in the error handling section Error Handling.

The N-API is a C API that ensures ABI stability across Node.js versions and different compiler levels. A C++ API can be easier to use. To support using C++, the project maintains a C++ wrapper module called node-addon-api. This wrapper provides an inlineable C++ API. Binaries built with node-addon-api will depend on the symbols for the N-API C-based functions exported by Node.js. node-addon-api is a more efficient way to write code that calls N-API. Take, for example, the following node-addon-api code. The first section shows the node-addon-api code and the second section shows what actually gets used in the addon.

Object obj = Object::New(env);
obj["foo"] = String::New(env, "bar");
napi_status status;
napi_value object, string;
status = napi_create_object(env, &object);
if (status != napi_ok) {
  napi_throw_error(env, ...);
  return;
}

status = napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string);
if (status != napi_ok) {
  napi_throw_error(env, ...);
  return;
}

status = napi_set_named_property(env, object, "foo", string);
if (status != napi_ok) {
  napi_throw_error(env, ...);
  return;
}

The end result is that the addon only uses the exported C APIs. As a result, it still gets the benefits of the ABI stability provided by the C API.

When using node-addon-api instead of the C APIs, start with the API docs for node-addon-api.

Implications of ABI Stability

Although N-API provides an ABI stability guarantee, other parts of Node.js do not, and any external libraries used from the addon may not. In particular, none of the following APIs provide an ABI stability guarantee across major versions:

  • the Node.js C++ APIs available via any of
    #include <node.h>
    #include <node_buffer.h>
    #include <node_version.h>
    #include <node_object_wrap.h>
  • the libuv APIs which are also included with Node.js and available via
    #include <uv.h>
  • the V8 API available via
    #include <v8.h>

Thus, for an addon to remain ABI-compatible across Node.js major versions, it must make use exclusively of N-API by restricting itself to using

#include <node_api.h>

and by checking, for all external libraries that it uses, that the external library makes ABI stability guarantees similar to N-API.

Usage

In order to use the N-API functions, include the file node_api.h which is located in the src directory in the node development tree:

#include <node_api.h>

This will opt into the default NAPI_VERSION for the given release of Node.js. In order to ensure compatibility with specific versions of N-API, the version can be specified explicitly when including the header:

#define NAPI_VERSION 3
#include <node_api.h>

This restricts the N-API surface to just the functionality that was available in the specified (and earlier) versions.

Some of the N-API surface is considered experimental and requires explicit opt-in to access those APIs:

#define NAPI_EXPERIMENTAL
#include <node_api.h>

In this case the entire API surface, including any experimental APIs, will be available to the module code.

N-API Version Matrix

1 2 3
v4.x
v6.x v6.14.2*
v8.x v8.0.0* v8.10.0*
v9.x v9.0.0* v9.3.0* v9.11.0*
v10.x v10.0.0

* Indicates that the N-API version was released as experimental

The N-APIs associated strictly with accessing ECMAScript features from native code can be found separately in js_native_api.h and js_native_api_types.h. The APIs defined in these headers are included in node_api.h and node_api_types.h. The headers are structured in this way in order to allow implementations of N-API outside of Node.js. For those implementations the Node.js specific APIs may not be applicable.

The Node.js-specific parts of an addon can be separated from the code that exposes the actual functionality to the JavaScript environment so that the latter may be used with multiple implementations of N-API. In the example below, addon.c and addon.h refer only to js_native_api.h. This ensures that addon.c can be reused to compile against either the Node.js implementation of N-API or any implementation of N-API outside of Node.js.

addon_node.c is a separate file that contains the Node.js specific entry point to the addon and which instantiates the addon by calling into addon.c when the addon is loaded into a Node.js environment.

// addon.h
#ifndef _ADDON_H_
#define _ADDON_H_
#include <js_native_api.h>
napi_value create_addon(napi_env env);
#endif  // _ADDON_H_
// addon.c
#include "addon.h"
napi_value create_addon(napi_env env) {
  napi_value result;
  assert(napi_create_object(env, &result) == napi_ok);
  napi_value exported_function;
  assert(napi_create_function(env,
                              "doSomethingUseful",
                              NAPI_AUTO_LENGTH,
                              DoSomethingUseful,
                              NULL,
                              &exported_function) == napi_ok);
  assert(napi_set_named_property(env,
                                 result,
                                 "doSomethingUseful",
                                 exported_function) == napi_ok);
  return result;
}
// addon_node.c
#include <node_api.h>

static napi_value Init(napi_env env, napi_value exports) {
  return create_addon(env);
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

Basic N-API Data Types

N-API exposes the following fundamental datatypes as abstractions that are consumed by the various APIs. These APIs should be treated as opaque, introspectable only with other N-API calls.

napi_status

Integral status code indicating the success or failure of a N-API call. Currently, the following status codes are supported.

typedef enum {
  napi_ok,
  napi_invalid_arg,
  napi_object_expected,
  napi_string_expected,
  napi_name_expected,
  napi_function_expected,
  napi_number_expected,
  napi_boolean_expected,
  napi_array_expected,
  napi_generic_failure,
  napi_pending_exception,
  napi_cancelled,
  napi_escape_called_twice,
  napi_handle_scope_mismatch,
  napi_callback_scope_mismatch,
  napi_queue_full,
  napi_closing,
  napi_bigint_expected,
} napi_status;

If additional information is required upon an API returning a failed status, it can be obtained by calling napi_get_last_error_info.

napi_extended_error_info

typedef struct {
  const char* error_message;
  void* engine_reserved;
  uint32_t engine_error_code;
  napi_status error_code;
} napi_extended_error_info;
  • error_message: UTF8-encoded string containing a VM-neutral description of the error.
  • engine_reserved: Reserved for VM-specific error details. This is currently not implemented for any VM.
  • engine_error_code: VM-specific error code. This is currently not implemented for any VM.
  • error_code: The N-API status code that originated with the last error.

See the Error Handling section for additional information.

napi_env

napi_env is used to represent a context that the underlying N-API implementation can use to persist VM-specific state. This structure is passed to native functions when they're invoked, and it must be passed back when making N-API calls. Specifically, the same napi_env that was passed in when the initial native function was called must be passed to any subsequent nested N-API calls. Caching the napi_env for the purpose of general reuse is not allowed.

napi_value

This is an opaque pointer that is used to represent a JavaScript value.

napi_threadsafe_function

Stability: 2 - Stable

This is an opaque pointer that represents a JavaScript function which can be called asynchronously from multiple threads via napi_call_threadsafe_function().

napi_threadsafe_function_release_mode

Stability: 2 - Stable

A value to be given to napi_release_threadsafe_function() to indicate whether the thread-safe function is to be closed immediately (napi_tsfn_abort) or merely released (napi_tsfn_release) and thus available for subsequent use via napi_acquire_threadsafe_function() and napi_call_threadsafe_function().

typedef enum {
  napi_tsfn_release,
  napi_tsfn_abort
} napi_threadsafe_function_release_mode;

napi_threadsafe_function_call_mode

Stability: 2 - Stable

A value to be given to napi_call_threadsafe_function() to indicate whether the call should block whenever the queue associated with the thread-safe function is full.

typedef enum {
  napi_tsfn_nonblocking,
  napi_tsfn_blocking
} napi_threadsafe_function_call_mode;

N-API Memory Management types

napi_handle_scope

This is an abstraction used to control and modify the lifetime of objects created within a particular scope. In general, N-API values are created within the context of a handle scope. When a native method is called from JavaScript, a default handle scope will exist. If the user does not explicitly create a new handle scope, N-API values will be created in the default handle scope. For any invocations of code outside the execution of a native method (for instance, during a libuv callback invocation), the module is required to create a scope before invoking any functions that can result in the creation of JavaScript values.

Handle scopes are created using napi_open_handle_scope and are destroyed using napi_close_handle_scope. Closing the scope can indicate to the GC that all napi_values created during the lifetime of the handle scope are no longer referenced from the current stack frame.

For more details, review the Object Lifetime Management.

napi_escapable_handle_scope

Escapable handle scopes are a special type of handle scope to return values created within a particular handle scope to a parent scope.

napi_ref

This is the abstraction to use to reference a napi_value. This allows for users to manage the lifetimes of JavaScript values, including defining their minimum lifetimes explicitly.

For more details, review the Object Lifetime Management.

N-API Callback types

napi_callback_info

Opaque datatype that is passed to a callback function. It can be used for getting additional information about the context in which the callback was invoked.

napi_callback

Function pointer type for user-provided native functions which are to be exposed to JavaScript via N-API. Callback functions should satisfy the following signature:

typedef napi_value (*napi_callback)(napi_env, napi_callback_info);

napi_finalize

Function pointer type for add-on provided functions that allow the user to be notified when externally-owned data is ready to be cleaned up because the object with which it was associated with, has been garbage-collected. The user must provide a function satisfying the following signature which would get called upon the object's collection. Currently, napi_finalize can be used for finding out when objects that have external data are collected.

typedef void (*napi_finalize)(napi_env env,
                              void* finalize_data,
                              void* finalize_hint);

napi_async_execute_callback

Function pointer used with functions that support asynchronous operations. Callback functions must statisfy the following signature:

typedef void (*napi_async_execute_callback)(napi_env env, void* data);

Implementations of this type of function should avoid making any N-API calls that could result in the execution of JavaScript or interaction with JavaScript objects. Most often, any code that needs to make N-API calls should be made in napi_async_complete_callback instead.

napi_async_complete_callback

Function pointer used with functions that support asynchronous operations. Callback functions must statisfy the following signature:

typedef void (*napi_async_complete_callback)(napi_env env,
                                             napi_status status,
                                             void* data);

napi_threadsafe_function_call_js

Stability: 2 - Stable

Function pointer used with asynchronous thread-safe function calls. The callback will be called on the main thread. Its purpose is to use a data item arriving via the queue from one of the secondary threads to construct the parameters necessary for a call into JavaScript, usually via napi_call_function, and then make the call into JavaScript.

The data arriving from the secondary thread via the queue is given in the data parameter and the JavaScript function to call is given in the js_callback parameter.

N-API sets up the environment prior to calling this callback, so it is sufficient to call the JavaScript function via napi_call_function rather than via napi_make_callback.

Callback functions must satisfy the following signature:

typedef void (*napi_threadsafe_function_call_js)(napi_env env,
                                                 napi_value js_callback,
                                                 void* context,
                                                 void* data);
  • [in] env: The environment to use for API calls, or NULL if the thread-safe function is being torn down and data may need to be freed.
  • [in] js_callback: The JavaScript function to call, or NULL if the thread-safe function is being torn down and data may need to be freed.
  • [in] context: The optional data with which the thread-safe function was created.
  • [in] data: Data created by the secondary thread. It is the responsibility of the callback to convert this native data to JavaScript values (with N-API functions) that can be passed as parameters when js_callback is invoked. This pointer is managed entirely by the threads and this callback. Thus this callback should free the data.

Error Handling

N-API uses both return values and JavaScript exceptions for error handling. The following sections explain the approach for each case.

Return values

All of the N-API functions share the same error handling pattern. The return type of all API functions is napi_status.

The return value will be napi_ok if the request was successful and no uncaught JavaScript exception was thrown. If an error occurred AND an exception was thrown, the napi_status value for the error will be returned. If an exception was thrown, and no error occurred, napi_pending_exception will be returned.

In cases where a return value other than napi_ok or napi_pending_exception is returned, napi_is_exception_pending must be called to check if an exception is pending. See the section on exceptions for more details.

The full set of possible napi_status values is defined in napi_api_types.h.

The napi_status return value provides a VM-independent representation of the error which occurred. In some cases it is useful to be able to get more detailed information, including a string representing the error as well as VM (engine)-specific information.

In order to retrieve this information napi_get_last_error_info is provided which returns a napi_extended_error_info structure. The format of the napi_extended_error_info structure is as follows:

typedef struct napi_extended_error_info {
  const char* error_message;
  void* engine_reserved;
  uint32_t engine_error_code;
  napi_status error_code;
};
  • error_message: Textual representation of the error that occurred.
  • engine_reserved: Opaque handle reserved for engine use only.
  • engine_error_code: VM specific error code.
  • error_code: n-api status code for the last error.

napi_get_last_error_info returns the information for the last N-API call that was made.

Do not rely on the content or format of any of the extended information as it is not subject to SemVer and may change at any time. It is intended only for logging purposes.

napi_get_last_error_info

napi_status
napi_get_last_error_info(napi_env env,
                         const napi_extended_error_info** result);
  • [in] env: The environment that the API is invoked under.
  • [out] result: The napi_extended_error_info structure with more information about the error.

Returns napi_ok if the API succeeded.

This API retrieves a napi_extended_error_info structure with information about the last error that occurred.

The content of the napi_extended_error_info returned is only valid up until an n-api function is called on the same env.

Do not rely on the content or format of any of the extended information as it is not subject to SemVer and may change at any time. It is intended only for logging purposes.

This API can be called even if there is a pending JavaScript exception.

Exceptions

Any N-API function call may result in a pending JavaScript exception. This is obviously the case for any function that may cause the execution of JavaScript, but N-API specifies that an exception may be pending on return from any of the API functions.

If the napi_status returned by a function is napi_ok then no exception is pending and no additional action is required. If the napi_status returned is anything other than napi_ok or napi_pending_exception, in order to try to recover and continue instead of simply returning immediately, napi_is_exception_pending must be called in order to determine if an exception is pending or not.

In many cases when an N-API function is called and an exception is already pending, the function will return immediately with a napi_status of napi_pending_exception. However, this is not the case for all functions. N-API allows a subset of the functions to be called to allow for some minimal cleanup before returning to JavaScript. In that case, napi_status will reflect the status for the function. It will not reflect previous pending exceptions. To avoid confusion, check the error status after every function call.

When an exception is pending one of two approaches can be employed.

The first approach is to do any appropriate cleanup and then return so that execution will return to JavaScript. As part of the transition back to JavaScript the exception will be thrown at the point in the JavaScript code where the native method was invoked. The behavior of most N-API calls is unspecified while an exception is pending, and many will simply return napi_pending_exception, so it is important to do as little as possible and then return to JavaScript where the exception can be handled.

The second approach is to try to handle the exception. There will be cases where the native code can catch the exception, take the appropriate action, and then continue. This is only recommended in specific cases where it is known that the exception can be safely handled. In these cases napi_get_and_clear_last_exception can be used to get and clear the exception. On success, result will contain the handle to the last JavaScript Object thrown. If it is determined, after retrieving the exception, the exception cannot be handled after all it can be re-thrown it with napi_throw where error is the JavaScript Error object to be thrown.

The following utility functions are also available in case native code needs to throw an exception or determine if a napi_value is an instance of a JavaScript Error object: napi_throw_error, napi_throw_type_error, napi_throw_range_error and napi_is_error.

The following utility functions are also available in case native code needs to create an Error object: napi_create_error, napi_create_type_error, and napi_create_range_error, where result is the napi_value that refers to the newly created JavaScript Error object.

The Node.js project is adding error codes to all of the errors generated internally. The goal is for applications to use these error codes for all error checking. The associated error messages will remain, but will only be meant to be used for logging and display with the expectation that the message can change without SemVer applying. In order to support this model with N-API, both in internal functionality and for module specific functionality (as its good practice), the throw_ and create_ functions take an optional code parameter which is the string for the code to be added to the error object. If the optional parameter is NULL then no code will be associated with the error. If a code is provided, the name associated with the error is also updated to be:

originalName [code]

where originalName is the original name associated with the error and code is the code that was provided. For example, if the code is 'ERR_ERROR_1' and a TypeError is being created the name will be: