Node.js has the best effect in writing backends using JavaScript, and it is worth trying more. However, if you need some functions that cannot be used directly or even modules that cannot be implemented at all, can you introduce such achievements from the C/C++ library? The answer is yes. All you have to do is write a plugin and use other code base resources in your JavaScript code. Let’s start today’s inquiry journey together.
introduce
As Node.js says in the official documentation, plug-ins are shared objects that link dynamically, which can connect JavaScript code with C/C++ libraries. This means we can reference anything from the C/C++ library and incorporate it into Node.js by creating plugins.
As an example, we will create an encapsulation for the standard std::string object.
Preparation
Before we start writing, we need to make sure that we have prepared all the materials needed for subsequent module compilation. Everyone needs node-gyp and all dependencies. You can use the following command to install node-gyp:
npm install -g node-gyp
In terms of dependencies, we need to prepare the following projects for Unix systems: • Python (requires version 2.7, 3.x cannot work properly)
• make
• A C++ compiler toolchain (such as gpp or g++)
For example, on Ubuntu, you can use the following command to install all the above projects (Python 2.7 should have been pre-installed):
sudo apt-get install build-essentials
In the Windows system environment, what you need is:
• Python (version 2.7.3, 3.x cannot work normally)
• Microsoft Visual Studio C++ 2010 (for Windows XP/Vista)
• Microsoft Visual Studio C++ 2012 for Windows Desktop (for Windows 7/8)
To emphasize, the Express version of Visual Studio can also work normally.
binding.gyp file
This file is used by node-gyp and is designed to generate the appropriate build file for our plugin. You can click here to view the .gyp file description document provided by Wikipedia, but the example we want to use today is very simple, so you just need to use the following code:
{ "targets": [ { "target_name": "stdstring", "sources": [ "addon.cc", "stdstring.cc" ] } ] }where target_name can be set to anything you like. The sources array contains all the source files that the plug-in needs to use. In our example, addon.cc is also included, which is used to accommodate the code necessary to compile plugins and stdstring.cc, plus our encapsulation class.
STDStringWrapper class
The first step is to define our own class in the stdstring.h file. If you are familiar with C++ programming, you will definitely not be unfamiliar with the following two lines of code.
#ifndef STDSTRING_H #define STDSTRING_H
This belongs to the standard include guard. Next, we need to include the following two headers in the include category:
#include
#include
The first one is aimed at the std::string class, while the second include acts on all Node and V8 related content.
After this step is completed, we can declare our class:
class STDStringWrapper : public node::ObjectWrap {
For all classes we intend to include in the plugin, we must extend the node::ObjectWrap class.
Now we can start defining the private property of this class:
private: std::string* s_; explicit STDStringWrapper(std::string s = ""); ~STDStringWrapper();
In addition to constructors and analytical functions, we also need to define a pointer for std::string. This is the core of this technology and can be used to connect the C/C++ code base to Node - we define a private pointer for the C/C++ class and will use this pointer to implement operations in all subsequent methods.
Now we declare the constructor static property, which will provide functions for the class we created in V8:
static v8::Persistent constructor;
Interested friends can click here to refer to the template description plan for more details.
Now we also need a New method, which will be assigned to the constructor mentioned above, and V8 will initialize our class:
static v8::Handle New(const v8::Arguments& args);
Every function acting on V8 should follow the following requirements: It will accept references to v8::Arguments objects and return a v8::Handle>v8::Value>—This is exactly how V8 deals with weak-type JavaScript when using strong-type C++ encoding.
After this, we need to insert two other methods into the object's prototype:
static v8::Handle add(const v8::Arguments& args); static v8::Handle toString(const v8::Arguments& args);
where the toString() method allows us to get the value of s_ instead of the value of [Object object] when using it with a normal JavaScript string.
Finally, we will introduce the initialization method (this method will be called by V8 and assigned to the constructor function) and close include guard:
public: static void Init(v8::Handle exports); }; #endif
The role of the exports object in the JavaScript module is equivalent to module.exports.
stdstring.cc file, constructor and parsing function
Now create the stdstring.cc file. We first need to include our header:
#include "stdstring.h"
The following defines the property for the constructor (because it belongs to a static function):
v8::Persistent STDStringWrapper::constructor;
This constructor serving the class will assign the s_ attribute:
STDStringWrapper::STDStringWrapper(std::string s) { s_ = new std::string(s); }And the parsing function will delete it to avoid memory overflow:
STDStringWrapper::~STDStringWrapper() { delete s_; }In addition, you must delete all the content assigned with new, because every time such situation may cause an exception, please remember the above operations or use a shared pointer.
Init method
This method will be called by V8 and is intended to initialize our class (assign the constructor, and place all the content we intend to use in JavaScript in the exports object):
void STDStringWrapper::Init(v8::Handle exports) {
First, we need to create a function template for our New method:
v8::Local tpl = v8::FunctionTemplate::New(New);
This is a bit similar to the new Function in JavaScript - it allows us to prepare our own JavaScript classes.
Now we can set a name for the function according to actual needs (if you miss this step, the constructor will be in anonymous state, that is, the name is function someName() {} or function () {}):
tpl->SetClassName(v8::String::NewSymbol("STDString"));
We use v8::String::NewSymbol() to create a special type string for property names - which saves a little time for the engine operation.
After this, we need to set how many fields our class instance contains:
tpl->InstanceTemplate()->SetInternalFieldCount(2);
We have two methods - add() and toString(), so we set the number to 2. Now we can add our own methods to the function prototype:
tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("add"), v8::FunctionTemplate::New(add)->GetFunction());
tpl->PrototypeTemplate()->Set(v8::String::NewSymbol("toString"), v8::FunctionTemplate::New(toString)->GetFunction());
This part of the code looks quite large, but as long as you carefully observe, you will find the rules: we use tpl->PrototypeTemplate()->Set() to add each method. We also use v8::String::NewSymbol() to provide them with names and FunctionTemplate.
Finally, we can place the constructor in the exports object within our constructor class properties:
constructor = v8::Persistent::New(tpl->GetFunction()); exports->Set(v8::String::NewSymbol("STDString"), constructor); }New method
Now what we have to do is define a method that works the same as JavaScript Object.prototype.constructor:
v8::Handle STDStringWrapper::New(const v8::Arguments& args) {We first need to create a scope for it:
v8::HandleScope scope;
After this, we can use the .IsConstructCall() method of the args object to check whether the constructor can be called using the new keyword:
if (args.IsConstructCall()) {If you can, we first pass the parameter to std::string as follows:
v8::String::Utf8Value str(args[0]->ToString()); std::string s(*str);
...so we can pass it into the constructor of our encapsulated class:
STDStringWrapper* obj = new STDStringWrapper(s);
After this, we can use the .Wrap() method of the object we created earlier (inherited from node::ObjectWrap) to assign it to this variable:
obj->Wrap(args.This());
Finally, we can return this newly created object:
return args.This();
If the function cannot be called with new, we can also call the constructor directly. Next, what we want to do is set a constant for the parameter count:
} else { const int argc = 1;Now we need to create an array using our own parameters:
v8::Local argv[argc] = { args[0] };Then pass the result of the constructor->NewInstance method to scope.Close so that the object can play a role later (scope.Close basically allows everyone to maintain it by moving the object processing handle to a higher range - this is also how the function takes effect):
return scope.Close(constructor->NewInstance(argc, argv)); } }
add method
Now let's create the add method, which is intended to allow everyone to add content to the object's internal std::string:
v8::Handle STDStringWrapper::add(const v8::Arguments& args) {First, we need to create a range for our function and convert the parameter into std::string as before:
v8::HandleScope scope; v8::String::Utf8Value str(args[0]->ToString()); std::string s(*str);
Now we need to unpack the object. We have also performed this reverse encapsulation operation before - this time we are going to get a pointer to the object from this variable.
STDStringWrapper* obj = ObjectWrap::Unwrap(args.This());
Then we can access the s_ attribute and use its .append() method:
obj->s_->append(s);
Finally, we return the current value of the s_ attribute (need to use scope.Close again):
return scope.Close(v8::String::New(obj->s_->c_str()));
Since the v8::String::New() method can only accept char pointer as a value, we need to use obj->s_->c_str() to get it.
At this time, a build directory should be created in your plug-in folder.
test
Now we can test our plug-ins. Create a test.js file and necessary compilation libraries in our plug-in directory (you can directly skip the .node extension):
var addon = require('./build/Release/addon');Next, create a new instance for our object:
var test = new addon.STDString('test');Next, do it, such as adding or converting it into a string:
test.add('!'); console.log('test/'s contents: %s', test);After running, you should see the following execution results in the console:
in conclusion
I hope that after reading this tutorial, you can dispel your concerns and regard creating and testing customized Node.js plug-ins based on C/C++ libraries as a very difficult task. You can use this technology to easily introduce almost any C/C++ library into Node.js. If you want, you can also add more functions to the plug-in according to actual needs. std::string provides a lot of methods, and we can use them as exercise materials.
Practical links
Interested friends can check the following links for more resources and details related to Node.js plug-in development, V8 and C event loop libraries.
• Node.js plugin documentation
• V8 documentation
• libuv (C event loop library), from GitHub
English: http://code.tutsplus.com/tutorials/writing-nodejs-addons--cms-21771