N-API offers a way to “wrap” C++ classes and instances so that the class constructor and methods can be called from JavaScript.

    1. The [napi_define_class][] API defines a JavaScript class with constructor, static properties and methods, and instance properties and methods that correspond to the C++ class.
    2. When JavaScript code invokes the constructor, the constructor callback uses [napi_wrap][] to wrap a new C++ instance in a JavaScript object, then returns the wrapper object.
    3. When JavaScript code invokes a method or property accessor on the class, the corresponding napi_callback C++ function is invoked. For an instance callback, [napi_unwrap][] obtains the C++ instance that is the target of the call.

    For wrapped objects it may be difficult to distinguish between a function called on a class prototype and a function called on an instance of a class. A common pattern used to address this problem is to save a persistent reference to the class constructor for later instanceof checks.

    1. napi_value MyClass_constructor = NULL;
    2. status = napi_get_reference_value(env, MyClass::es_constructor, &MyClass_constructor);
    3. assert(napi_ok == status);
    4. bool is_instance = false;
    5. status = napi_instanceof(env, es_this, MyClass_constructor, &is_instance);
    6. assert(napi_ok == status);
    7. if (is_instance) {
    8. // napi_unwrap() ...
    9. } else {
    10. // otherwise...
    11. }

    The reference must be freed once it is no longer needed.

    There are occasions where napi_instanceof() is insufficient for ensuring that a JavaScript object is a wrapper for a certain native type. This is the case especially when wrapped JavaScript objects are passed back into the addon via static methods rather than as the this value of prototype methods. In such cases there is a chance that they may be unwrapped incorrectly.

    1. const myAddon = require('./build/Release/my_addon.node');
    2. // `openDatabase()` returns a JavaScript object that wraps a native database
    3. // handle.
    4. const dbHandle = myAddon.openDatabase();
    5. // `query()` returns a JavaScript object that wraps a native query handle.
    6. const queryHandle = myAddon.query(dbHandle, 'Gimme ALL the things!');
    7. // There is an accidental error in the line below. The first parameter to
    8. // `myAddon.queryHasRecords()` should be the database handle (`dbHandle`), not
    9. // the query handle (`query`), so the correct condition for the while-loop
    10. // should be
    11. //
    12. // myAddon.queryHasRecords(dbHandle, queryHandle)
    13. //
    14. while (myAddon.queryHasRecords(queryHandle, dbHandle)) {
    15. // retrieve records
    16. }

    In the above example myAddon.queryHasRecords() is a method that accepts two arguments. The first is a database handle and the second is a query handle. Internally, it unwraps the first argument and casts the resulting pointer to a native database handle. It then unwraps the second argument and casts the resulting pointer to a query handle. If the arguments are passed in the wrong order, the casts will work, however, there is a good chance that the underlying database operation will fail, or will even cause an invalid memory access.

    To ensure that the pointer retrieved from the first argument is indeed a pointer to a database handle and, similarly, that the pointer retrieved from the second argument is indeed a pointer to a query handle, the implementation of queryHasRecords() has to perform a type validation. Retaining the JavaScript class constructor from which the database handle was instantiated and the constructor from which the query handle was instantiated in napi_refs can help, because napi_instanceof() can then be used to ensure that the instances passed into queryHashRecords() are indeed of the correct type.

    Unfortunately, napi_instanceof() does not protect against prototype manipulation. For example, the prototype of the database handle instance can be set to the prototype of the constructor for query handle instances. In this case, the database handle instance can appear as a query handle instance, and it will pass the napi_instanceof() test for a query handle instance, while still containing a pointer to a database handle.

    To this end, N-API provides type-tagging capabilities.

    A type tag is a 128-bit integer unique to the addon. N-API provides the napi_type_tag structure for storing a type tag. When such a value is passed along with a JavaScript object stored in a napi_value to napi_type_tag_object(), the JavaScript object will be “marked” with the type tag. The “mark” is invisible on the JavaScript side. When a JavaScript object arrives into a native binding, napi_check_object_type_tag() can be used along with the original type tag to determine whether the JavaScript object was previously “marked” with the type tag. This creates a type-checking capability of a higher fidelity than napi_instanceof() can provide, because such type- tagging survives prototype manipulation and addon unloading/reloading.

    Continuing the above example, the following skeleton addon implementation illustrates the use of napi_type_tag_object() and napi_check_object_type_tag().

    1. // This value is the type tag for a database handle. The command
    2. //
    3. // uuidgen | sed -r -e 's/-//g' -e 's/(.{16})(.*)/0x\1, 0x\2/'
    4. //
    5. // can be used to obtain the two values with which to initialize the structure.
    6. static const napi_type_tag DatabaseHandleTypeTag = {
    7. 0x1edf75a38336451d, 0xa5ed9ce2e4c00c38
    8. };
    9. // This value is the type tag for a query handle.
    10. static const napi_type_tag QueryHandleTypeTag = {
    11. 0x9c73317f9fad44a3, 0x93c3920bf3b0ad6a
    12. };
    13. static napi_value
    14. openDatabase(napi_env env, napi_callback_info info) {
    15. napi_status status;
    16. napi_value result;
    17. // Perform the underlying action which results in a database handle.
    18. DatabaseHandle* dbHandle = open_database();
    19. // Create a new, empty JS object.
    20. status = napi_create_object(env, &result);
    21. if (status != napi_ok) return NULL;
    22. // Tag the object to indicate that it holds a pointer to a `DatabaseHandle`.
    23. status = napi_type_tag_object(env, result, &DatabaseHandleTypeTag);
    24. if (status != napi_ok) return NULL;
    25. // Store the pointer to the `DatabaseHandle` structure inside the JS object.
    26. status = napi_wrap(env, result, dbHandle, NULL, NULL, NULL);
    27. if (status != napi_ok) return NULL;
    28. return result;
    29. }
    30. // Later when we receive a JavaScript object purporting to be a database handle
    31. // we can use `napi_check_object_type_tag()` to ensure that it is indeed such a
    32. // handle.
    33. static napi_value
    34. query(napi_env env, napi_callback_info info) {
    35. napi_status status;
    36. size_t argc = 2;
    37. napi_value argv[2];
    38. bool is_db_handle;
    39. status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
    40. if (status != napi_ok) return NULL;
    41. // Check that the object passed as the first parameter has the previously
    42. // applied tag.
    43. status = napi_check_object_type_tag(env,
    44. argv[0],
    45. &DatabaseHandleTypeTag,
    46. &is_db_handle);
    47. if (status != napi_ok) return NULL;
    48. // Throw a `TypeError` if it doesn't.
    49. if (!is_db_handle) {
    50. // Throw a TypeError.
    51. return NULL;
    52. }
    53. }