From 6ea63e2106e10b958d8c2e2ac835d20d24f84e82 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Mon, 22 Jan 2018 10:45:50 +0100 Subject: [PATCH 01/15] added code docu --- src/node_lib.h | 230 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 161 insertions(+), 69 deletions(-) diff --git a/src/node_lib.h b/src/node_lib.h index de77ab1eff8398..033af67db7c412 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -9,59 +9,88 @@ namespace node { namespace lib { - namespace { // private variables - bool _event_loop_running = false; - } - - bool EventLoopIsRunning() { return _event_loop_running; } - - /********************************************************* - * Function types - *********************************************************/ - - void _RegisterModuleCallback(v8::Local exports, + namespace { // private definitions + bool _event_loop_running = false; /**< Holds the current state of the event loop. */ + + /** + * @brief Internal function to register a native C++ module. + * + * Internally used function to register a new C++ module for Node.js. + * @param exports The exports object, which is exposed to JavaScript. + * @param module The v8 module. + * @param context The current v8 context. + * @param priv Private data for the module. + */ + 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() { return _event_loop_running; } /********************************************************* * 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. - */ + /** + * @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. + */ 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 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(). - */ + /** + * @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 processes 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::Local 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,33 +98,59 @@ 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 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. - */ + /** + * @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::Local Evaluate(const std::string & java_script_code); - /* - Returns the JavaScript root object for the running application - */ + /** + * @brief Returns the JavaScript root object. + * + * Returns the global root object for the current JavaScript context. + * @return The global root object. + */ NODE_EXTERN v8::Local GetRootObject(); - /* - Registers a C++ module in the *running* Node.js engine. - */ + /** + * @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. + */ 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. - */ + /** + * @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. + */ NODE_EXTERN void RegisterModule(const std::string & name, const std::map & module_functions); @@ -104,37 +159,74 @@ namespace node { namespace lib { * Convenience operations *********************************************************/ - /* - Adds a new JavaScript module to the *running* Node.js engine. - */ + /** + * @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. + * @return The export object of the NPM module. + */ 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). - */ + /** + * @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::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. - */ + /** + * @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::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. - */ + /** + * @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::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). - */ + /** + * @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::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. - */ + /** + * @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::Local Call(v8::MaybeLocal receiver, v8::MaybeLocal function, std::initializer_list> args); }} From a323279a866d2b512ebf65450dc814e1ec6d77c7 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Wed, 24 Jan 2018 10:34:38 +0100 Subject: [PATCH 02/15] refactored internals --- src/node.cc | 80 ++++++++++++++++++++++++-------------------------- src/node_lib.h | 8 ++++- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/node.cc b/src/node.cc index b25ec6c9e4c2f2..96b594019b53c5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5000,10 +5000,8 @@ struct CmdArgs { 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; @@ -5020,23 +5018,23 @@ void deleteCmdArgs() { } 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() { @@ -5089,45 +5087,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,30 +5152,30 @@ 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 @@ -5255,23 +5253,23 @@ v8::Local Run(const std::string& path) { } v8::Local Evaluate(const std::string& java_script_code) { - EscapableHandleScope scope(env->isolate()); - TryCatch try_catch(env->isolate()); + 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); + ReportException(_environment, try_catch); exit(3); //TODO jh: don't exit process when function breaks. Handle error differently. } Local result = script.ToLocalChecked()->Run(); if (result.IsEmpty()) { - ReportException(env, try_catch); + ReportException(_environment, try_catch); exit(4); //TODO jh: don't exit process when function breaks. Handle error differently. } @@ -5309,7 +5307,7 @@ v8::Local Call(v8::Local receiver, 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()); + Local v8_function_name = v8::String::NewFromUtf8(_isolate, function_name.c_str()); Local value = object->Get(v8_function_name); if (!value->IsFunction()) { @@ -5326,7 +5324,7 @@ v8::Local Call(v8::Local object, const std::string & func // 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())}; + std::vector> args = {v8::String::NewFromUtf8(_isolate, module_name.c_str())}; auto module = Call(GetRootObject(), "require", args); if (module->IsUndefined()) { @@ -5381,7 +5379,7 @@ void StopEventLoop() { } 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..953cbf7b4da65e 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -11,9 +11,15 @@ namespace node { namespace lib { namespace { // private variables bool _event_loop_running = false; + v8::Isolate* _isolate = nullptr; + Environment* _environment = nullptr; } - bool EventLoopIsRunning() { return _event_loop_running; } + const bool EventLoopIsRunning() { return _event_loop_running; } + + const v8::Isolate* Isolate() { return _isolate; } + + const Environment* Environment() { return _environment; } /********************************************************* * Function types From 6126411d9de3c077245ab17c509a0725c65d7b75 Mon Sep 17 00:00:00 2001 From: Justus Hildebrand Date: Wed, 24 Jan 2018 11:57:38 +0100 Subject: [PATCH 03/15] Option to auto-call process.binding() when calling RegisterModule() (#38) add option to autobind modules in RegisterModule() Can be done by passing an optional string to the function. Default is an empty string and will force the user to bind the module in JS themselves. --- src/node.cc | 27 ++++++++++++++++----------- src/node_lib.h | 5 ++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/node.cc b/src/node.cc index b25ec6c9e4c2f2..ccf98159af591f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5337,23 +5337,28 @@ v8::Local IncludeModule(const std::string& module_name) { return v8::Local::Cast(module); } -void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv) { - node::node_module* module = new node::node_module(); +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(); - 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; + 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); + 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::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)); + RegisterModule(name, node::lib::_RegisterModuleCallback, const_cast*>(map_on_heap), target); } void _RegisterModuleCallback(v8::Local exports, diff --git a/src/node_lib.h b/src/node_lib.h index de77ab1eff8398..d5baccbe2be10e 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -91,13 +91,12 @@ namespace node { namespace lib { /* 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); + NODE_EXTERN void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv = nullptr, const std::string & target = ""); /* 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); + NODE_EXTERN void RegisterModule(const std::string & name, const std::map & module_functions, const std::string & target = ""); /********************************************************* From 0ee43485346988ee49b53f95de669f215da3b7af Mon Sep 17 00:00:00 2001 From: Christian Flach Date: Mon, 29 Jan 2018 10:13:43 +0100 Subject: [PATCH 04/15] Rename getters to match style guide --- src/node_lib.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_lib.h b/src/node_lib.h index 953cbf7b4da65e..af92f99eea5a7c 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -17,9 +17,9 @@ namespace node { namespace lib { const bool EventLoopIsRunning() { return _event_loop_running; } - const v8::Isolate* Isolate() { return _isolate; } + const v8::Isolate* isolate() { return _isolate; } - const Environment* Environment() { return _environment; } + const Environment* environment() { return _environment; } /********************************************************* * Function types From 200c1a9e6d6cb19d13d5e46226347828a605b1f7 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Mon, 29 Jan 2018 10:20:35 +0100 Subject: [PATCH 05/15] added error handling via MaybeLocal (#40) * added error handling via MaybeLocal * implemented missing method * re-enabled scope.Escape in order to save the script return value --- src/node.cc | 94 ++++++++++++++++++++++++++++++++++---------------- src/node_lib.h | 18 +++++----- 2 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/node.cc b/src/node.cc index ccf98159af591f..e6ab66f6d69aa6 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5244,7 +5244,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,7 +5254,7 @@ v8::Local Run(const std::string& path) { return Evaluate(buffer.str()); } -v8::Local Evaluate(const std::string& java_script_code) { +v8::MaybeLocal Evaluate(const std::string& java_script_code) { EscapableHandleScope scope(env->isolate()); TryCatch try_catch(env->isolate()); @@ -5266,16 +5266,10 @@ v8::Local Evaluate(const std::string& java_script_code) { MaybeLocal script = v8::Script::Compile(env->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. + 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,47 +5288,88 @@ 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; - Local value = object->Get(v8_function_name); - if (!value->IsFunction()) { - //throw new Exception(":(("); - // TODO (js): at least return at this point + if (!maybe_function_name.ToLocal(&v8_function_name)) { + // cannot create v8 string. + return MaybeLocal(); + } + + MaybeLocal maybe_value = object->Get(v8_function_name); + Local value; + + 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; + + if (!maybe_arg.ToLocal(&arg)) { + // cannot create v8 string + return MaybeLocal(); + } + + Local root_object; + + if (!GetRootObject().ToLocal(&root_object)) { + // cannot get root object + return MaybeLocal(); + } + + std::vector> args = { arg }; + + 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)); +} + +v8::MaybeLocal GetValue(v8::Local object, const std::string& value_name) { + MaybeLocal maybe_key = v8::String::NewFromUtf8(isolate, value_name.c_str()); + Local key; - 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_key.ToLocal(&key)) { + // cannot create v8::String + return MaybeLocal(); } - return v8::Local::Cast(module); + return object->Get(context, key); } void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv, const std::string & target) { @@ -5382,7 +5417,6 @@ void StopEventLoop() { return; } request_stop = true; - //while (request_stop && _event_loop_running) { } } bool ProcessEvents() { diff --git a/src/node_lib.h b/src/node_lib.h index d5baccbe2be10e..0d7c04c874c0d1 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -46,7 +46,7 @@ namespace node { namespace lib { 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); + NODE_EXTERN v8::MaybeLocal Run(const std::string & path); /********************************************************* * Handle JavaScript events @@ -81,12 +81,12 @@ namespace node { namespace lib { /* Executes a given piece of JavaScript code, using the *running* Node.js engine. */ - NODE_EXTERN v8::Local Evaluate(const std::string & java_script_code); + NODE_EXTERN v8::MaybeLocal Evaluate(const std::string & java_script_code); /* Returns the JavaScript root object for the running application */ - NODE_EXTERN v8::Local GetRootObject(); + NODE_EXTERN v8::MaybeLocal GetRootObject(); /* Registers a C++ module in the *running* Node.js engine. @@ -106,34 +106,34 @@ namespace node { namespace lib { /* Adds a new JavaScript module to the *running* Node.js engine. */ - NODE_EXTERN v8::Local IncludeModule(const std::string & module_name); + NODE_EXTERN v8::MaybeLocal 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); + NODE_EXTERN v8::MaybeLocal GetValue(v8::Local 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 = {}); + NODE_EXTERN v8::MaybeLocal 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); + NODE_EXTERN v8::MaybeLocal 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 = {}); + NODE_EXTERN v8::MaybeLocal Call(v8::Local receiver, v8::Local 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); + NODE_EXTERN v8::MaybeLocal Call(v8::Local receiver, v8::Local function, std::initializer_list> args); }} From ee0195010a4c6edab5ed9d6ea4038390cc018ec4 Mon Sep 17 00:00:00 2001 From: luminosuslight Date: Mon, 29 Jan 2018 10:22:07 +0100 Subject: [PATCH 06/15] Add support for cmd args (#39) * Add support for cmd args * Move setCmdArgs code to ctor * Fix cmd options * Fix visibility and comments in CmdArgs * Improve documentation --- src/node.cc | 92 +++++++++++++++++++++++++++++++++++++++----------- src/node_lib.h | 2 +- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/src/node.cc b/src/node.cc index e6ab66f6d69aa6..384c4fc94e95ab 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4992,9 +4992,73 @@ 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; @@ -5015,8 +5079,8 @@ void deleteCmdArgs() { if (!cmd_args) { return; } - delete[] cmd_args->argv; delete cmd_args; + cmd_args = nullptr; } int _StopEnv() { @@ -5060,15 +5124,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. @@ -5182,7 +5237,7 @@ void _StartEnv(int argc, } // 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 +5245,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 +5257,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(); diff --git a/src/node_lib.h b/src/node_lib.h index 0d7c04c874c0d1..473c5c0c631e3c 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -33,7 +33,7 @@ namespace node { namespace lib { 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"); + NODE_EXTERN void Initialize(const std::string& program_name = "node_lib_executable", const std::vector& node_args = {}); /* From 22a3107449f3b82f26af07bd26040a315521b84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20S=C3=B6chting?= Date: Mon, 29 Jan 2018 12:53:00 +0100 Subject: [PATCH 07/15] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 +``` From e5820663c09f591b8428475fa63bc9d71a9a14ba Mon Sep 17 00:00:00 2001 From: Justus Hildebrand Date: Mon, 29 Jan 2018 17:05:30 +0100 Subject: [PATCH 08/15] early check for uv_loop_alive(), small refactoring fixes #27 --- src/node.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/node.cc b/src/node.cc index 384c4fc94e95ab..701d21cc19caff 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4803,18 +4803,20 @@ 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. From 6854d8f89565244008212e9b086cba3ea862bbb4 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Tue, 30 Jan 2018 11:59:23 +0100 Subject: [PATCH 09/15] changed the way internals are exposed. --- src/node.cc | 17 +++++++++++++++++ src/node_lib.h | 13 +++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/node.cc b/src/node.cc index f54280a80d08d6..aa9bf4032abbad 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5070,6 +5070,23 @@ 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; + +namespace internal { + bool EventLoopIsRunning() { + return _event_loop_running; + } + + v8::Isolate* isolate() { + return _isolate; + } + + Environment* environment() { + return _environment; + } +} namespace deinitialize { diff --git a/src/node_lib.h b/src/node_lib.h index 30711097787c74..33256a55525b7a 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -9,17 +9,14 @@ namespace node { namespace lib { - namespace { // private variables - bool _event_loop_running = false; - v8::Isolate* _isolate = nullptr; - Environment* _environment = nullptr; - } + namespace internal { // private variables - const bool EventLoopIsRunning() { return _event_loop_running; } + bool EventLoopIsRunning(); - const v8::Isolate* isolate() { return _isolate; } + v8::Isolate* isolate(); - const Environment* environment() { return _environment; } + Environment* environment(); + } /********************************************************* * Function types From 807709dd470451ccb4b0412fb322575752688594 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Tue, 30 Jan 2018 12:16:14 +0100 Subject: [PATCH 10/15] included feedback from PR --- src/node_lib.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/node_lib.h b/src/node_lib.h index 033af67db7c412..44dbfaeb3577a8 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -63,7 +63,7 @@ namespace node { namespace lib { * * 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 processes as long as + * 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. @@ -103,7 +103,7 @@ namespace node { namespace lib { * * Issues the Node.js event loop to stop. * The event loop will finish its current execution. - * This means, that the loop is not stopped when this method returns. + * 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(); @@ -165,7 +165,9 @@ namespace node { namespace lib { * 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. + * @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::Local IncludeModule(const std::string & module_name); From c5c73ff95140df9cc57e46d64bada44745f65215 Mon Sep 17 00:00:00 2001 From: Justus Hildebrand Date: Tue, 30 Jan 2018 14:03:40 +0100 Subject: [PATCH 11/15] fix nits --- src/node.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node.cc b/src/node.cc index 701d21cc19caff..b6a01ed9ae26fc 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4800,7 +4800,6 @@ 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())) { @@ -4809,8 +4808,9 @@ inline static bool TickEventLoop(Environment & env) { v8_platform.DrainVMTasks(); - if (uv_loop_alive(env.event_loop())) + if (uv_loop_alive(env.event_loop())) { return true; + } EmitBeforeExit(&env); From 588ab2870feb5529d3f04131d798d6d04efd1890 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Tue, 30 Jan 2018 19:16:50 +0100 Subject: [PATCH 12/15] moved 'EventLoopIsRunning' back to node::lib --- src/node.cc | 8 ++++---- src/node_lib.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/node.cc b/src/node.cc index aa9bf4032abbad..268cd213692cad 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5074,11 +5074,11 @@ bool _event_loop_running = false; v8::Isolate* _isolate = nullptr; Environment* _environment = nullptr; -namespace internal { - bool EventLoopIsRunning() { - return _event_loop_running; - } +bool EventLoopIsRunning() { + return _event_loop_running; +} +namespace internal { v8::Isolate* isolate() { return _isolate; } diff --git a/src/node_lib.h b/src/node_lib.h index 33256a55525b7a..e61078103b039d 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -9,15 +9,15 @@ namespace node { namespace lib { - namespace internal { // private variables - - bool EventLoopIsRunning(); + namespace internal { // internals, made for experienced users v8::Isolate* isolate(); Environment* environment(); } + bool EventLoopIsRunning(); + /********************************************************* * Function types *********************************************************/ From e693ecff8162bd37b3e045a150e8c88de14a3c07 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Mon, 22 Jan 2018 10:45:50 +0100 Subject: [PATCH 13/15] added code docu --- src/node_lib.h | 223 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 158 insertions(+), 65 deletions(-) diff --git a/src/node_lib.h b/src/node_lib.h index e61078103b039d..4b0149cdbd9424 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -16,55 +16,85 @@ namespace node { namespace lib { Environment* environment(); } - bool EventLoopIsRunning(); - - /********************************************************* - * Function types - *********************************************************/ - - void _RegisterModuleCallback(v8::Local exports, + /** + * @brief Internal function to register a native C++ module. + * + * Internally used function to register a new C++ module for Node.js. + * @param exports The exports object, which is exposed to JavaScript. + * @param module The v8 module. + * @param context The current v8 context. + * @param priv Private data for the module. + */ + 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() { return _event_loop_running; } /********************************************************* * 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. - */ + /** + * @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. + */ NODE_EXTERN void Initialize(const std::string& program_name = "node_lib_executable", const std::vector& node_args = {}); - - /* - 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 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(). - */ + /** + * @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 processes 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); @@ -72,33 +102,59 @@ 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 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. - */ + /** + * @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); - /* - Returns the JavaScript root object for the running application - */ + /** + * @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(); - /* - Registers a C++ module in the *running* Node.js engine. - */ + /** + * @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. + */ NODE_EXTERN void RegisterModule(const std::string & name, const addon_context_register_func & callback, void *priv = nullptr, const std::string & target = ""); - /* - Registers a C++ module in the *running* Node.js engine exporting the given set of functions. - */ + /** + * @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. + */ NODE_EXTERN void RegisterModule(const std::string & name, const std::map & module_functions, const std::string & target = ""); @@ -106,37 +162,74 @@ namespace node { namespace lib { * Convenience operations *********************************************************/ - /* - Adds a new JavaScript module to the *running* Node.js engine. - */ + /** + * @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. + * @return The export object of the NPM module. + */ NODE_EXTERN v8::MaybeLocal IncludeModule(const std::string & module_name); - /* - Returns the local value (specified by its name) of the module (defined in the `exports`-object). - */ + /** + * @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); - /* - 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. - */ + /** + * @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 = {}); - /* - 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. - */ + /** + * @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); - /* - 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). - */ + /** + * @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 = {}); - /* - Calls a given function on a given receiver passing the given arguments. - *Important*: The amount of arguments must be known at compile time. - */ + /** + * @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); }} From 0ed0c77569778aa98dd4377b78d2dc07ab6fa05f Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Tue, 30 Jan 2018 12:16:14 +0100 Subject: [PATCH 14/15] included feedback from PR --- src/node_lib.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/node_lib.h b/src/node_lib.h index 4b0149cdbd9424..d5c44aff99ad1b 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -67,7 +67,7 @@ namespace node { namespace lib { * * 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 processes as long as + * 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. @@ -107,7 +107,7 @@ namespace node { namespace lib { * * Issues the Node.js event loop to stop. * The event loop will finish its current execution. - * This means, that the loop is not stopped when this method returns. + * 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(); @@ -168,7 +168,9 @@ namespace node { namespace lib { * 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. + * @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); From 2e6b1ff13235a4ac708c4849cda4ddd5c5015d21 Mon Sep 17 00:00:00 2001 From: Johannes Schneider Date: Wed, 31 Jan 2018 09:51:09 +0100 Subject: [PATCH 15/15] fixed merge problems, added more docu for new methods --- src/node.cc | 32 ++++++++++++++++---------------- src/node_lib.h | 32 ++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/node.cc b/src/node.cc index 5269546703cd76..6ddaf04e564ac8 100644 --- a/src/node.cc +++ b/src/node.cc @@ -5090,6 +5090,22 @@ namespace internal { } } +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 { void deleteCmdArgs() { @@ -5467,22 +5483,6 @@ void RegisterModule(const std::string & name, RegisterModule(name, node::lib::_RegisterModuleCallback, const_cast*>(map_on_heap), target); } -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; -} - void StopEventLoop() { if (!_event_loop_running) { return; diff --git a/src/node_lib.h b/src/node_lib.h index d5c44aff99ad1b..1edc89e68209c7 100644 --- a/src/node_lib.h +++ b/src/node_lib.h @@ -11,31 +11,30 @@ namespace node { namespace lib { 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(); - Environment* environment(); - } - /** - * @brief Internal function to register a native C++ module. + * @brief Returns the `node::Environment` for Node.js. * - * Internally used function to register a new C++ module for Node.js. - * @param exports The exports object, which is exposed to JavaScript. - * @param module The v8 module. - * @param context The current v8 context. - * @param priv Private data for the module. + * 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`. */ - void _RegisterModuleCallback(v8::Local exports, - v8::Local module, - v8::Local context, - void* priv); + Environment* environment(); } /** * @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() { return _event_loop_running; } + bool EventLoopIsRunning(); /********************************************************* * Start Node.js engine @@ -49,6 +48,7 @@ namespace node { namespace lib { * 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 = {}); @@ -143,6 +143,8 @@ namespace node { namespace lib { * @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 = ""); @@ -154,6 +156,8 @@ namespace node { namespace lib { * 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 = "");