diff --git a/README.md b/README.md index a141b026692442..409bbee02830c7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Node.js: Embedding in C++ [![Build Status](https://travis-ci.org/hpicgs/node.svg?branch=node_lib)](https://travis-ci.org/hpicgs/node) + This fork proposes changes to node.js' shared library interface to allow for a easier and more flexible development experience when embedding node.js into C++ applications. This project is a work in progress in the "Advanced Development in C++" project seminar at Hasso Platter Institute's Chair of Computer Graphics Systems. For example applications showing how to use node's current embedding interface, as well as examples for our proposed new interface, please visit [this repository](https://github.com/hpicgs/node-embed). @@ -11,4 +13,4 @@ For information concerning the setup, usage etc. of this project, please read th ``` ./configure --shared make -j4 -``` \ No newline at end of file +``` diff --git a/src/node.cc b/src/node.cc index b25ec6c9e4c2f2..6ddaf04e564ac8 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4800,21 +4800,23 @@ Local NewContext(Isolate* isolate, } inline static bool TickEventLoop(Environment & env) { - bool more = false; uv_run(env.event_loop(), UV_RUN_NOWAIT); + if (uv_loop_alive(env.event_loop())) { + return true; + } + v8_platform.DrainVMTasks(); - more = uv_loop_alive(env.event_loop()); - if (more) - return more; + if (uv_loop_alive(env.event_loop())) { + return true; + } EmitBeforeExit(&env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. - more = uv_loop_alive(env.event_loop()); - return more; + return uv_loop_alive(env.event_loop()); } // This is where the magic happens. Creates JavaScript context and a JS Environment, then runs the uv event loop until it is no longer alive (see TickEventLoop()), then tears down Env and context and returns JS exit code. @@ -4992,22 +4994,117 @@ int Start(int argc, char** argv) { namespace lib { -struct CmdArgs { +/** + * @brief The CmdArgs class is a container for argc and argv. + */ +class CmdArgs { + +public: + /** + * @brief CmdArgs creates valid argc and argv variables from a program name and arguments. + * + * The argv buffer is a contiguous, adjacent char buffer and contains the program name + * as its first item followed by the provided arguments. argc is the number of + * arguments + 1 (the program name). + * The resulting argv buffer should not be modified. + * + * @param program_name the name of the executable + * @param arguments the arguments for the program + */ + CmdArgs(const std::string& program_name, const std::vector& arguments) + : argc(0) + , argv(nullptr) + { + size_t total_size = 0; + total_size += program_name.size() + 1; + for (const auto& argument: arguments) { + total_size += argument.size() + 1; + } + + std::vector offsets; + argument_data.reserve(total_size); + offsets.push_back(argument_data.size()); + argument_data += program_name; + argument_data += char(0x0); + for (const auto& argument: arguments) { + offsets.push_back(argument_data.size()); + argument_data += argument; + argument_data += char(0x0); + } + + argument_pointers.resize(offsets.size()); + for (std::size_t i=0; i argument_pointers; }; ArrayBufferAllocator* allocator; Isolate::CreateParams params; Locker* locker; -Isolate* isolate; IsolateData* isolate_data; Isolate::Scope* isolate_scope; -Environment* env; Local context; Context::Scope* context_scope; bool request_stop = false; CmdArgs* cmd_args = nullptr; +bool _event_loop_running = false; +v8::Isolate* _isolate = nullptr; +Environment* _environment = nullptr; + +bool EventLoopIsRunning() { + return _event_loop_running; +} + +namespace internal { + v8::Isolate* isolate() { + return _isolate; + } + + Environment* environment() { + return _environment; + } +} + +void _RegisterModuleCallback(v8::Local exports, + v8::Local module, + v8::Local context, + void* priv) { + auto module_functions = static_cast*>(priv); + if (!module_functions) { + fprintf(stderr, "_RegisterModuleCallback: module_functions is null"); + return; + } + for (std::pair element : *module_functions) { + NODE_SET_METHOD(exports, element.first.c_str(), element.second); + } + + delete module_functions; +} namespace deinitialize { @@ -5015,28 +5112,28 @@ void deleteCmdArgs() { if (!cmd_args) { return; } - delete[] cmd_args->argv; delete cmd_args; + cmd_args = nullptr; } int _StopEnv() { - env->set_trace_sync_io(false); + _environment->set_trace_sync_io(false); - int exit_code = EmitExit(env); - RunAtExit(env); + int exit_code = EmitExit(_environment); + RunAtExit(_environment); uv_key_delete(&thread_local_env); v8_platform.DrainVMTasks(); - WaitForInspectorDisconnect(env); + WaitForInspectorDisconnect(_environment); return exit_code; } void deleteIsolate() { Mutex::ScopedLock scoped_lock(node_isolate_mutex); - CHECK_EQ(node_isolate, isolate); + CHECK_EQ(node_isolate, _isolate); node_isolate = nullptr; - isolate->Dispose(); + _isolate->Dispose(); } void deinitV8() { @@ -5060,15 +5157,6 @@ void deinitV8() { namespace initialize { -void generateCmdArgsFromProgramName(const std::string& program_name) { - deinitialize::deleteCmdArgs(); - int argc = 1; - char* program_name_c_string = new char[program_name.length() + 1]; - std::strcpy(program_name_c_string, program_name.c_str()); - char** argv = new char*(program_name_c_string); - cmd_args = new CmdArgs{argc, argv}; -} - void initV8() { v8_platform.Initialize(v8_thread_pool_size, uv_default_loop()); // Enable tracing when argv has --trace-events-enabled. @@ -5089,45 +5177,45 @@ void createIsolate() { params.code_event_handler = vTune::GetVtuneCodeEventHandler(); #endif - isolate = Isolate::New(params); - if (isolate == nullptr) { + _isolate = Isolate::New(params); + if (_isolate == nullptr) { fprintf(stderr, "Could not create isolate."); fflush(stderr); return; // TODO: Handle error //return 12; // Signal internal error. } - isolate->AddMessageListener(OnMessage); - isolate->SetAbortOnUncaughtExceptionCallback(ShouldAbortOnUncaughtException); - isolate->SetAutorunMicrotasks(false); - isolate->SetFatalErrorHandler(OnFatalError); + _isolate->AddMessageListener(OnMessage); + _isolate->SetAbortOnUncaughtExceptionCallback(ShouldAbortOnUncaughtException); + _isolate->SetAutorunMicrotasks(false); + _isolate->SetFatalErrorHandler(OnFatalError); if (track_heap_objects) { - isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); + _isolate->GetHeapProfiler()->StartTrackingHeapObjects(true); } { Mutex::ScopedLock scoped_lock(node_isolate_mutex); CHECK_EQ(node_isolate, nullptr); - node_isolate = isolate; + node_isolate = _isolate; } } void createInitialEnvironment() { - locker = new Locker(isolate); - isolate_scope = new Isolate::Scope(isolate); - static HandleScope handle_scope(isolate); // TODO (jh): Once we write a Deinit(), we need to put this on the heap to call the deconstructor. - isolate_data = new IsolateData(isolate, uv_default_loop(), allocator->zero_fill_field()); + locker = new Locker(_isolate); + isolate_scope = new Isolate::Scope(_isolate); + static HandleScope handle_scope(_isolate); // TODO (jh): Once we write a Deinit(), we need to put this on the heap to call the deconstructor. + isolate_data = new IsolateData(_isolate, uv_default_loop(), allocator->zero_fill_field()); ////////// // Start 3 ////////// //HandleScope handle_scope(isolate); // (jh) in the initial Start functions, two handle scopes were created (one in Start() 2 and one in Start() 3). Currently, we have no idea why. - context = NewContext(isolate); + context = NewContext(_isolate); context_scope = new Context::Scope(context); - env = new Environment(isolate_data, context); + _environment = new node::Environment(isolate_data, context); CHECK_EQ(0, uv_key_create(&thread_local_env)); - uv_key_set(&thread_local_env, env); + uv_key_set(&thread_local_env, _environment); } void configureOpenSsl() { @@ -5154,35 +5242,35 @@ void _StartEnv(int argc, int v8_argc = 0; const char* const* v8_argv = nullptr; - env->Start(argc, argv, v8_argc, v8_argv, v8_is_profiling); + _environment->Start(argc, argv, v8_argc, v8_argv, v8_is_profiling); const char* path = argc > 1 ? argv[1] : nullptr; - StartInspector(env, path, debug_options); + StartInspector(_environment, path, debug_options); - if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(env)) { + if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(_environment)) { return; // TODO (jh): Handle error //return 12; // Signal internal error. } - env->set_abort_on_uncaught_exception(abort_on_uncaught_exception); + _environment->set_abort_on_uncaught_exception(abort_on_uncaught_exception); if (no_force_async_hooks_checks) { - env->async_hooks()->no_force_checks(); + _environment->async_hooks()->no_force_checks(); } { - Environment::AsyncCallbackScope callback_scope(env); - env->async_hooks()->push_async_ids(1, 0); - LoadEnvironment(env); - env->async_hooks()->pop_async_id(1); + Environment::AsyncCallbackScope callback_scope(_environment); + _environment->async_hooks()->push_async_ids(1, 0); + LoadEnvironment(_environment); + _environment->async_hooks()->pop_async_id(1); } - env->set_trace_sync_io(trace_sync_io); + _environment->set_trace_sync_io(trace_sync_io); } } // namespace initialize -void Initialize(const std::string& program_name) { +void Initialize(const std::string& program_name, const std::vector& node_args) { ////////// // Start 1 ////////// @@ -5190,12 +5278,11 @@ void Initialize(const std::string& program_name) { PlatformInit(); node::performance::performance_node_start = PERFORMANCE_NOW(); - // currently we do not support additional commandline options for node, uv, or v8 - // we explicitily only set the first argument to the program name - initialize::generateCmdArgsFromProgramName(program_name); + cmd_args = new CmdArgs(program_name, node_args); - // Hack around with the argv pointer. Used for process.title = "blah". - cmd_args->argv = uv_setup_args(cmd_args->argc, cmd_args->argv); + // Hack around with the argv pointer. Used for process.title = "blah --args". + // argv won't be modified + uv_setup_args(cmd_args->argc, const_cast(cmd_args->argv)); // This needs to run *before* V8::Initialize(). The const_cast is not // optional, in case you're wondering. @@ -5203,7 +5290,7 @@ void Initialize(const std::string& program_name) { // don't support these, they are not used. int exec_argc = 0; const char** exec_argv = nullptr; - Init(&cmd_args->argc, const_cast(cmd_args->argv), &exec_argc, &exec_argv); + Init(&cmd_args->argc, cmd_args->argv, &exec_argc, &exec_argv); initialize::configureOpenSsl(); @@ -5244,7 +5331,7 @@ int Deinitialize() { return exit_code; } -v8::Local Run(const std::string& path) { +v8::MaybeLocal Run(const std::string& path) { // Read entire file into string. There is most certainly a better way ;) // https://stackoverflow.com/a/2602258/2560557 std::ifstream t(path); @@ -5254,28 +5341,22 @@ v8::Local Run(const std::string& path) { return Evaluate(buffer.str()); } -v8::Local Evaluate(const std::string& java_script_code) { - EscapableHandleScope scope(env->isolate()); - TryCatch try_catch(env->isolate()); +v8::MaybeLocal Evaluate(const std::string& java_script_code) { + EscapableHandleScope scope(_environment->isolate()); + TryCatch try_catch(_environment->isolate()); // try_catch must be nonverbose to disable FatalException() handler, // we will handle exceptions ourself. try_catch.SetVerbose(false); //ScriptOrigin origin(filename); // TODO jh: set reasonable ScriptOrigin. This is used for debugging - MaybeLocal script = v8::Script::Compile(env->context(), v8::String::NewFromUtf8(isolate, java_script_code.c_str())/*, origin*/); + MaybeLocal script = v8::Script::Compile(_environment->context(), v8::String::NewFromUtf8(_isolate, java_script_code.c_str())/*, origin*/); if (script.IsEmpty()) { - ReportException(env, try_catch); - exit(3); //TODO jh: don't exit process when function breaks. Handle error differently. + ReportException(_environment, try_catch); + return MaybeLocal(); } - Local result = script.ToLocalChecked()->Run(); - if (result.IsEmpty()) { - ReportException(env, try_catch); - exit(4); //TODO jh: don't exit process when function breaks. Handle error differently. - } - - return scope.Escape(result); + return MaybeLocal(scope.Escape(script.ToLocalChecked()->Run())); } void RunEventLoop(const std::function& callback) { @@ -5294,82 +5375,112 @@ void RunEventLoop(const std::function& callback) { _event_loop_running = false; } -v8::Local GetRootObject() { +v8::MaybeLocal GetRootObject() { + if (context.IsEmpty()) { + return MaybeLocal(); + } return context->Global(); } -v8::Local Call(v8::Local receiver, v8::Local function, const std::vector> & args = {}) { +v8::MaybeLocal Call(v8::Local receiver, v8::Local function, const std::vector> & args) { return function->Call(receiver, args.size(), const_cast*>(&args[0])); } -v8::Local Call(v8::Local receiver, v8::Local function, std::initializer_list> args) { +v8::MaybeLocal Call(v8::Local receiver, v8::Local function, std::initializer_list> args) { return Call(receiver, function, std::vector>(args)); } -// TODO: Error handling: Node.js has exceptions disabled. -v8::Local Call(v8::Local object, const std::string& function_name, const std::vector>& args) { - Local v8_function_name = v8::String::NewFromUtf8(isolate, function_name.c_str()); +v8::MaybeLocal Call(v8::Local object, const std::string& function_name, const std::vector>& args) { + MaybeLocal maybe_function_name = v8::String::NewFromUtf8(_isolate, function_name.c_str()); + Local v8_function_name; + + if (!maybe_function_name.ToLocal(&v8_function_name)) { + // cannot create v8 string. + return MaybeLocal(); + } + + MaybeLocal maybe_value = object->Get(v8_function_name); + Local value; - Local value = object->Get(v8_function_name); - if (!value->IsFunction()) { - //throw new Exception(":(("); - // TODO (js): at least return at this point + if (!maybe_value.ToLocal(&value)) { + // cannot get member of object + return MaybeLocal(); + } else if (!value->IsFunction()) { + // cannot execute non-function + return MaybeLocal(); } return Call(object, v8::Local::Cast(value), args); } -v8::Local Call(v8::Local object, const std::string & function_name, std::initializer_list> args) { +v8::MaybeLocal Call(v8::Local object, const std::string & function_name, std::initializer_list> args) { return Call(object, function_name, std::vector>(args)); } -// TODO: Node.js has exceptions disabled. -v8::Local IncludeModule(const std::string& module_name) { - std::vector> args = {v8::String::NewFromUtf8(isolate, module_name.c_str())}; +v8::MaybeLocal IncludeModule(const std::string& module_name) { + MaybeLocal maybe_arg = v8::String::NewFromUtf8(_isolate, module_name.c_str()); + Local arg; - auto module = Call(GetRootObject(), "require", args); - if (module->IsUndefined()) { - //TODO jh: throw new Exception(":(("); // repuire() call failed, but did not throw a JS exception. - // TODO (js): at least return at this point + if (!maybe_arg.ToLocal(&arg)) { + // cannot create v8 string + return MaybeLocal(); } - return v8::Local::Cast(module); -} + Local root_object; -void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv) { - node::node_module* module = new node::node_module(); + if (!GetRootObject().ToLocal(&root_object)) { + // cannot get root object + return MaybeLocal(); + } - module->nm_version = NODE_MODULE_VERSION; - module->nm_flags = NM_F_BUILTIN; - module->nm_filename = __FILE__; - module->nm_context_register_func = callback; - module->nm_modname = name.c_str(); - module->nm_priv = priv; + std::vector> args = { arg }; - node_module_register(module); + MaybeLocal maybe_module = Call(root_object, "require", args); + Local module; + + if (!maybe_module.ToLocal(&module)) { + // cannot get module + return MaybeLocal(); + } + + return MaybeLocal(Local::Cast(module)); } -void RegisterModule(const std::string & name, - const std::map & module_functions) { - auto map_on_heap = new const std::map(module_functions); - RegisterModule(name, node::lib::_RegisterModuleCallback, const_cast*>(map_on_heap)); +v8::MaybeLocal GetValue(v8::Local object, const std::string& value_name) { + MaybeLocal maybe_key = v8::String::NewFromUtf8(_isolate, value_name.c_str()); + Local key; + + if (!maybe_key.ToLocal(&key)) { + // cannot create v8::String + return MaybeLocal(); + } + + return object->Get(context, key); } -void _RegisterModuleCallback(v8::Local exports, - v8::Local module, - v8::Local context, - void* priv) { - auto module_functions = static_cast*>(priv); - if (!module_functions) { - fprintf(stderr, "_RegisterModuleCallback: module_functions is null"); - return; - } - for (std::pair element : *module_functions) { - NODE_SET_METHOD(exports, element.first.c_str(), element.second); - } +void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv, const std::string & target) { + node::node_module* module = new node::node_module(); - delete module_functions; + module->nm_version = NODE_MODULE_VERSION; + module->nm_flags = NM_F_BUILTIN; + module->nm_filename = __FILE__; + module->nm_context_register_func = callback; + module->nm_modname = name.c_str(); + module->nm_priv = priv; + + node_module_register(module); + + if(target != "") { + Evaluate("const " + target + " = process.binding('" + name + "')"); + } +} + +void RegisterModule(const std::string & name, + const std::map & module_functions, + const std::string & target) { + auto map_on_heap = new const std::map(module_functions); + RegisterModule(name, node::lib::_RegisterModuleCallback, const_cast*>(map_on_heap), target); } void StopEventLoop() { @@ -5377,11 +5488,10 @@ void StopEventLoop() { return; } request_stop = true; - //while (request_stop && _event_loop_running) { } } bool ProcessEvents() { - return TickEventLoop(*env); + return TickEventLoop(*_environment); } } // namespace node::lib diff --git a/src/node_lib.h b/src/node_lib.h index de77ab1eff8398..1edc89e68209c7 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -9,59 +9,92 @@ namespace node { namespace lib { - namespace { // private variables - bool _event_loop_running = false; + namespace internal { // internals, made for experienced users + + /** + * @brief Returns the `v8::Isolate` for Node.js. + * + * Returns a pointer to the currently used `v8::Isolate`, if the Node.js engine is initialized already. + * *Important* Use with caution, changing this object might break Node.js. + * @return Pointer to the `v8::Isolate`. + */ + v8::Isolate* isolate(); + + /** + * @brief Returns the `node::Environment` for Node.js. + * + * Returns a pointer to the currently used `node::Environment`, if the Node.js engine is initialized already. + * *Important* Use with caution, changing this object might break Node.js. + * @return Pointer to the `node::Environment`. + */ + Environment* environment(); } - bool EventLoopIsRunning() { return _event_loop_running; } - - /********************************************************* - * Function types - *********************************************************/ - - void _RegisterModuleCallback(v8::Local exports, - v8::Local module, - v8::Local context, - void* priv); - + /** + * @brief Indicates, whether the Node.js event loop is executed by `RunEventLoop`. + * @return True, if the Node.js event loop is executed by `RunEventLoop`. False otherwise. + */ + bool EventLoopIsRunning(); /********************************************************* * Start Node.js engine *********************************************************/ - /* - Starts the Node.js engine without a concrete script file to execute. - *Important*: This requires the C++ developer to call `ProcessEvents()` periodically OR call `RunMainLoop()` to start the uv event loop. - */ - NODE_EXTERN void Initialize(const std::string& program_name = "node_lib_executable"); - - - /* - Stops the existing Node.js engine. Eventloop should not be running at this point. - *Important*: Once this was called, Initialize() will have to be called again for Node.js' library functions to be available again. - */ + /** + * @brief Starts the Node.js engine without executing a concrete script. + * + * Starts the Node.js engine by executing bootstrap code. + * This is required in order to load scripts (e.g. `Run`) or evaluate JavaScript code (e.g. `Evaluate`). + * Additionally, Node.js will not process any pending events caused by the JavaScript execution as long as + * `ProcessEvents` or `RunMainLoop` is not called. + * @param program_name The name for the Node.js application. + * @param node_args List of arguments for the Node.js engine. + */ + NODE_EXTERN void Initialize(const std::string& program_name = "node_lib_executable", const std::vector& node_args = {}); + + /** + * @brief Stops the Node.js engine and destroys all current state. + * + * Stops the Node.js engine. + * This is done in two steps: + * 1. Issues the Node.js event loop to no longer accept any incoming events. + * 2. Waits for the event loop to be empty and then executes clean up code. + */ NODE_EXTERN int Deinitialize(); - /* - Executes a given JavaScript file and returns once the execution has finished. - *Important*: Node.js has to have been initialized by calling Initialize(). - */ - NODE_EXTERN v8::Local Run(const std::string & path); + /** + * @brief Executes the content of a given JavaScript file. + * + * Loads and executes the content of the given file. + * This method returns after the script was evaluated once. + * This means, that any pending events will not be processed as long as + * `ProcessEvents` or `RunEventLoop` is not called. + * @param path The path to the JavaScript file. + * @return The return value of the given JavaScript file. + */ + NODE_EXTERN v8::MaybeLocal Run(const std::string & path); /********************************************************* * Handle JavaScript events *********************************************************/ - /* - Processes the pending event queue of a *running* Node.js engine once. - */ + /** + * @brief Executes the Node.js event loop once. + * + * Processes all currently pending events in the Node.js event loop. + * This method returns immediately if there are no pending events. + * @return True, if more events need to be processed. False otherwise. + */ NODE_EXTERN bool ProcessEvents(); - /* - Starts the execution of the Node.js event loop, which processes any events in JavaScript. - Additionally, the given callback will be executed once per main loop run. - *Important*: Call `Initialize()` before using this method. - */ + /** + * @brief Starts the execution of the Node.js event loop. Calling the given callback once per loop tick. + * + * Executes the Node.js event loop as long as events keep coming. + * Once per loop execution, after events were processed, the given callback is executed. + * The event loop can be paused by calling `StopEventLoop`. + * @param callback The callback, which should be executed periodically while the calling thread is blocked. + */ NODE_EXTERN void RunEventLoop(const std::function & callback); @@ -69,72 +102,140 @@ namespace node { namespace lib { * Stop Node.js engine *********************************************************/ - /* - Stops the Node.js event loop after its current execution. Execution can be resumed by calling RunEventLoop() again. - */ + /** + * @brief Issues the Node.js event loop to stop. + * + * Issues the Node.js event loop to stop. + * The event loop will finish its current execution. + * This means, that the loop is not guaranteed to have stopped when this method returns. + * The execution can be resumed by using `RunEventLoop` again. + */ NODE_EXTERN void StopEventLoop(); /********************************************************* * Basic operations *********************************************************/ - /* - Executes a given piece of JavaScript code, using the *running* Node.js engine. - */ - NODE_EXTERN v8::Local Evaluate(const std::string & java_script_code); - - /* - Returns the JavaScript root object for the running application - */ - NODE_EXTERN v8::Local GetRootObject(); - - /* - Registers a C++ module in the *running* Node.js engine. - */ - NODE_EXTERN void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv = nullptr); - - /* - Registers a C++ module in the *running* Node.js engine exporting the given set of functions. - */ - NODE_EXTERN void RegisterModule(const std::string & name, - const std::map & module_functions); + /** + * @brief Evaluates the given JavaScript code. + * + * Parses and runs the given JavaScipt code. + * @param java_script_code The code to evaluate. + * @return The return value of the evaluated code. + */ + NODE_EXTERN v8::MaybeLocal Evaluate(const std::string & java_script_code); + + /** + * @brief Returns the JavaScript root object. + * + * Returns the global root object for the current JavaScript context. + * @return The global root object. + */ + NODE_EXTERN v8::MaybeLocal GetRootObject(); + + /** + * @brief Registers a native C++ module. + * + * Adds a native module to the Node.js engine. + * The module is initialized within the given callback. Additionally, private data can be included in the module + * using the priv pointer. + * The module can be used in JavaScript by calling `let cpp_module = process.binding('module_name')`. + * @param name The name for the module. + * @param callback The method, which initializes the module (e.g. by adding methods to the module). + * @param priv Any private data, which should be included within the module. + * @param target The name for the module within the JavaScript context. (e.g. `const target = process.binding(module_name)`) + * If empty, the module will *not* be registered within the global JavaScript context automatically. + */ + NODE_EXTERN void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv = nullptr, const std::string & target = ""); + + /** + * @brief Registers a native C++ module. + * + * Adds a native module to the Node.js engine. + * Additionally, this method adds the given methods to the module. + * The module can be used in JavaScript by calling `let cpp_module = process.binding('module_name')`. + * @param name The name for the module. + * @param module_functions A list of functions and their names for the module. + * @param target The name for the module within the JavaScript context. (e.g. `const target = process.binding(module_name)`) + * If empty, the module will *not* be registered within the global JavaScript context automatically. + */ + NODE_EXTERN void RegisterModule(const std::string & name, const std::map & module_functions, const std::string & target = ""); /********************************************************* * Convenience operations *********************************************************/ - /* - Adds a new JavaScript module to the *running* Node.js engine. - */ - NODE_EXTERN v8::Local IncludeModule(const std::string & module_name); - - /* - Returns the local value (specified by its name) of the module (defined in the `exports`-object). - */ - NODE_EXTERN v8::MaybeLocal GetValue(v8::MaybeLocal object, const std::string & value_name); - - /* - Calls a function (specified by its name) on a given object passing the given arguments. - *Important*: Throws an exception if the receiver does not define the specified function. - */ - NODE_EXTERN v8::Local Call(v8::Local object, const std::string & function_name, const std::vector> & args = {}); - - /* - Calls a function (specified by its name) on a given object passing the given arguments. - *Important*: Throws an exception if the receiver does not define the specified function. - */ - NODE_EXTERN v8::Local Call(v8::Local object, const std::string & function_name, std::initializer_list> args); - - /* - Calls a given function on a given receiver passing the given arguments. - *Important*: The amount of arguments can be changed at runtime (for JS var arg functions). - */ - NODE_EXTERN v8::Local Call(v8::MaybeLocal receiver, v8::MaybeLocal function, const std::vector> & args = {}); - - /* - Calls a given function on a given receiver passing the given arguments. - *Important*: The amount of arguments must be known at compile time. - */ - NODE_EXTERN v8::Local Call(v8::MaybeLocal receiver, v8::MaybeLocal function, std::initializer_list> args); + /** + * @brief Adds a NPM module to the current JavaScript context. + * + * Adds a given NPM module to the JavaScript context. + * This is achieved by calling `require('module_name')`. + * *Important* Make sure the NPM module is installed before using this method. + * @param module_name The name of the NPM module. + * When using just the modules name, the "node_modules" directory should be located within the working directory. + * You can also load modules from different locations by providing the full path to the module. + * @return The export object of the NPM module. + */ + NODE_EXTERN v8::MaybeLocal IncludeModule(const std::string & module_name); + + /** + * @brief Returns a member of the given object. + * + * Returns a member of the given object, specified by the members name. + * @param object The container for the requested value. + * @param value_name The name of the requested value. + * @return The requested value. + */ + NODE_EXTERN v8::MaybeLocal GetValue(v8::Local object, const std::string & value_name); + + /** + * @brief Calls a method on a given object. + * + * Calls a method on a given object. + * The function is retrieved by using the functions name. + * Additionally, a list of parameters is passed to the called function. + * @param object The container of the called function. + * @param function_name The name of the function to call. + * @param args The parameters to pass to the called function. + * @return The return value of the called function. + */ + NODE_EXTERN v8::MaybeLocal Call(v8::Local object, const std::string & function_name, const std::vector> & args = {}); + + /** + * @brief Calls a method on a given object. + * + * Calls a method on a given object. + * The function is retrieved by using the functions name. + * Additionally, a list of parameters is passed to the called function. + * @param object The container of the called function. + * @param function_name The name of the function to call. + * @param args The parameters to pass to the called function. The amount of arguments must be known at compile time. + * @return The return value of the called function. + */ + NODE_EXTERN v8::MaybeLocal Call(v8::Local object, const std::string & function_name, std::initializer_list> args); + + /** + * @brief Calls a given method on a given object. + * + * Calls a given method on a given object. + * Additionally, a list of parameters is passed to the called function. + * @param object The receiver of the given function. + * @param function The function to be called. + * @param args The parameters to pass to the called function. + * @return The return value of the called function. + */ + NODE_EXTERN v8::MaybeLocal Call(v8::Local receiver, v8::Local function, const std::vector> & args = {}); + + /** + * @brief Calls a given method on a given object. + * + * Calls a given method on a given object. + * Additionally, a list of parameters is passed to the called function. + * @param object The receiver of the given function. + * @param function The function to be called. + * @param args The parameters to pass to the called function. The amount of arguments must be known at compile time. + * @return The return value of the called function. + */ + NODE_EXTERN v8::MaybeLocal Call(v8::Local receiver, v8::Local function, std::initializer_list> args); }}