Node.js writes three implementations of components

  • 2021-01-11 01:50:11
  • OfStack

The differences between using v8 and using the swig framework are described first:

(1) v8 API method for the official native method, powerful and perfect, the disadvantage is the need to be familiar with v8 API, writing up more trouble, js is strongly related, not easy to support other scripting languages.

(2) the swig for third party support, a powerful component development tools, support for python, lua, a variety of common scripting language generation such as js C + + component packaging code, swig users only need to write C + + code and swig configuration file development C + + components of various scripting languages, don't need to understand various scripting language component development framework, the disadvantage is that do not support javascript callback, documentation and demo code is not perfect, not many users.

1. Pure JS implements Node.js components
(1) Go to the helloworld directory to execute npm init to initialize package.json, all options are ignored, default can be.

(2) Component implementation index.js, for example:


module.exports.Hello = function(name) {
    console.log('Hello ' + name);
}

install./helloworld. helloworld is installed in the node_modules directory.
(4) Wrote the code for using components:


var m = require('helloworld');
m.Hello('zhangsan');
// Output:  Hello zhangsan

2. Use v8 API to implement JS component -- synchronous mode
(1) binding.gyp, eg:


{
 "targets": [
  {
   "target_name": "hello",
   "sources": [ "hello.cpp" ]
  }
 ]
}

hello.cpp, eg:


#include <node.h>

namespace cpphello {
  using v8::FunctionCallbackInfo;
  using v8::Isolate;
  using v8::Local;
  using v8::Object;
  using v8::String;
  using v8::Value;

  void Foo(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello World"));
  }

  void Init(Local<Object> exports) {
    NODE_SET_METHOD(exports, "foo", Foo);
  }

  NODE_MODULE(cpphello, Init)
}

(3) Compile components


node-gyp configure
node-gyp build
./build/Release/ It will be generated in the directory hello.node The module. 

(4) Write test js code


const m = require('./build/Release/hello')
console.log(m.foo()); // The output  Hello World

(5) Add package.json to install eg:


{                                                                                                         
  "name": "hello",
  "version": "1.0.0",
  "description": "", 
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  }, 
  "author": "", 
  "license": "ISC"
}

Install the component into node_modules

Go to the parent directory of the component directory and execute :npm install./helloc // Note: helloc is the component directory
The hello module will be installed in the current directory node_modules. The test code will look like this:


var m = require('hello');
console.log(m.foo());  

3. Use v8 API to implement the JS component -- asynchronous mode
foo() is a synchronous function. In other words, the caller of foo() has to wait for foo() to complete before proceeding. When foo() is a function that has an IO operation, the asynchronous foo() function can reduce blocking waits and improve overall performance.

The asynchronous component implementation only needs to focus on uv_queue_work API, the component implementation, except for the body code hello.cpp and the component consumer code, other parts are the same as demo1 above 3.

hello. cpp:


/*
* Node.js cpp Addons demo: async call and call back.
* gcc 4.8.2
* author:cswuyg
* Date:2016.02.22
* */
#include <iostream>
#include <node.h>
#include <uv.h> 
#include <sstream>
#include <unistd.h>
#include <pthread.h>

namespace cpphello {
  using v8::FunctionCallbackInfo;
  using v8::Function;
  using v8::Isolate;
  using v8::Local;
  using v8::Object;
  using v8::Value;
  using v8::Exception;
  using v8::Persistent;
  using v8::HandleScope;
  using v8::Integer;
  using v8::String;

  // async task
  struct MyTask{
    uv_work_t work;
    int a{0};
    int b{0};
    int output{0};
    unsigned long long work_tid{0};
    unsigned long long main_tid{0};
    Persistent<Function> callback;
  };

  // async function
  void query_async(uv_work_t* work) {
    MyTask* task = (MyTask*)work->data;
    task->output = task->a + task->b;
    task->work_tid = pthread_self();
    usleep(1000 * 1000 * 1); // 1 second
  }

  // async complete callback
  void query_finish(uv_work_t* work, int status) {
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope handle_scope(isolate);
    MyTask* task = (MyTask*)work->data;
    const unsigned int argc = 3;
    std::stringstream stream;
    stream << task->main_tid;
    std::string main_tid_s{stream.str()};
    stream.str("");
    stream << task->work_tid;
    std::string work_tid_s{stream.str()};
    
    Local<Value> argv[argc] = {
      Integer::New(isolate, task->output), 
      String::NewFromUtf8(isolate, main_tid_s.c_str()),
      String::NewFromUtf8(isolate, work_tid_s.c_str())
    };
    Local<Function>::New(isolate, task->callback)->Call(isolate->GetCurrentContext()->Global(), argc, argv);
    task->callback.Reset();
    delete task;
  }

  // async main
  void async_foo(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    HandleScope handle_scope(isolate);
    if (args.Length() != 3) {
      isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments num : 3")));
      return;
    } 
    if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsFunction()) {
      isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "arguments error")));
      return;
    }
    MyTask* my_task = new MyTask;
    my_task->a = args[0]->ToInteger()->Value();
    my_task->b = args[1]->ToInteger()->Value();
    my_task->callback.Reset(isolate, Local<Function>::Cast(args[2]));
    my_task->work.data = my_task;
    my_task->main_tid = pthread_self();
    uv_loop_t *loop = uv_default_loop();
    uv_queue_work(loop, &my_task->work, query_async, query_finish); 
  }

  void Init(Local<Object> exports) {
    NODE_SET_METHOD(exports, "foo", async_foo);
  }

  NODE_MODULE(cpphello, Init)
}

The idea of asynchronous is very simple, implement a work function, a completion function, a structure to carry data transfer across threads, call uv_queue_work. The difficulty is to be familiar with v8 data structure and API.

test.js


// test helloUV module
'use strict';
const m = require('helloUV')

m.foo(1, 2, (a, b, c)=>{
  console.log('finish job:' + a);
  console.log('main thread:' + b);
  console.log('work thread:' + c);
});
/*
output:
finish job:3
main thread:139660941432640
work thread:139660876334848
*/

4. swig-javascript implements Node.js components
Node.js components are written using swig framework

(1) Write the implementation of the component: *.h and *.cpp

eg:


var m = require('helloworld');
m.Hello('zhangsan');
// Output:  Hello zhangsan
0

(2) Write *.i to generate the wrapper cpp file of swig
eg:


var m = require('helloworld');
m.Hello('zhangsan');
// Output:  Hello zhangsan
1

The %apply above indicates int* result, int* xx, std::string* result, std::string* yy, std::string in the code & result is the output description, and this is typemap, which is a substitution.
C++ pointer arguments that return values (as specified by OUTPUT in the *.i file) are treated by JS as return values, or list if there are multiple Pointers.
%template(vectori) vector < int > C++ is a function of type vectori, which is a function of type vector < int > As a parameter or return value, it is needed when writing js code.
(3) Write binding.gyp for compilation using node-gyp
eg: swig-javascript-node-c ++ -DV8_VERSION=0x040599 example.i: swig-javascript-node-c ++ -DV8_VERSION=0x040599 example.i: swig-javascript-node-c ++ -DV8_VERSION=0x040599 example
(5) Compiling & test
The difficulty lies in the use of stl types, custom types, and the lack of official documentation.
swig - javascript std: : vector, std: : string, encapsulation using see: my practice, focusing on *. i file implementation.
5. Other
When using v8 API to implement the Node.js component, you can find similarities with the implementation of the Lua component. Lua has a state machine and Node has Isolate.

To implement object export, Node implements a constructor, adds a "member function" to it, and exports the constructor as the class name. When Lua implements the export of an object, it also needs to implement a factory function to create an object. It also needs to add "member functions" to table. Finally, export the factory function.

js scripts for Node have the new keyword, Lua does not, so Lua only provides object factories for object creation. Node can provide object factories or class encapsulation.

The above is all the content of this article, I hope to help you learn.


Related articles: