Skip to main content
Javascript

Writing native Node.js modules

Even though we have time and experience programming in JS, there are times when JavaScript performance is not enough, so we must rely more on native Node.js modules. Read on to find out how to do this.

While native extensions are definitely not a beginner's topic, I would recommend this article to all Node.js developers to gain a little insight into how they work.

Common use cases for native Node.js modules

Knowledge of native modules is useful when you are adding a native extension as a dependency, which you could have already done!

Just do a little look at the list of some popular modules that use native extensions. You are using at least one of them, or am I wrong?

  • https://github.com/wadey/node-microtime
  • https://github.com/node-inspector
  • https://github.com/node-inspector/v8-profiler
  • http://www.nodegit.org/

There are a few reasons why one might consider module creation Node.js native speakers, including but not limited to:

  • Performance Critical Applications: Let's be honest, Node.js is great for performing asynchronous input and output operations, but when it comes to true number computation, it's not such a good choice.
  • Connection to lower level APIs, for example: operating system.
  • Bridging the C or C ++ Libraries and Node.js

What are the native modules?

Node.js plugins are dynamically linked shared objects written in C or C ++, which can be loaded into Node.js using the function require (), and are used as if they were an ordinary Node.js module.

This means that (if done correctly) the quirks of C / C ++ can be hidden from the module consumer. What you will see instead is that your module is a Node.js module, as if there were

Node.js runs on the V8 JavaScript engine, which is a C program on its own. We can write code that interacts directly with this C program in your own language, which is great because we can avoid a lot of expensive serialization and communication costs.

Also, in a previous blog post we learned about the cost of the Node.js garbage collector. Although garbage collection can be completely avoided if you decide to manage memory yourself (because C and C ++ don't have a concept of GC), it will create memory problems much more easily.

Writing native extensions requires knowledge of one or more of the following topics:

  • Libuv
  • V8
  • Node.js internals

They all have excellent documentation. If you are entering this field, I recommend you read them.

Without further ado, let's start with the strong theme of this post:

Prerequisites

  • For Linux operating system:
    1. Use python (I recommend using v2.7, since v3.xx is not supported)
    2. Make.
    3. Use a suitable C or C ++ compilation toolchain, such as GCC
  • For Mac operating system:
    1. Have Xcode installed: make sure you not only install it, but start it at least once and accept its terms and conditions; otherwise it won't work!
  • For Windows operating system:
    1. Run cmd.exe as administrator and enter the command npm install --global --production windows-build-tools, which will install everything for you.
    2. Another option is to install Visual Studio: (it has all the C / C ++ build tools preconfigured)
    3. Or use the Linux subsystem provided by the latest Windows build. With that, follow the LINUX instructions above.

Creating our native Node.js extension

We are going to create our first file for the native extension. We can use the extension .DC which means it's C with classes, or the extension .cpp which is the default for C ++. The Google Style Guide recommends .DC, so for this tutorial I'll stick with it.

At this point we are going to see the complete file and then explain it line by line.

#include const int maxValue = 10; int numberOfCalls = 0; void WhoAmI (const v8 :: FunctionCallbackInfo & args) {v8 :: Isolate * isolate = args.GetIsolate (); auto message = v8 :: String :: NewFromUtf8 (isolate, "I am Node Hero!"); args.GetReturnValue (). Set (message); } void Increment (const v8 :: FunctionCallbackInfo & args) {v8 :: Isolate * isolate = args.GetIsolate (); if (! args [0] -> IsNumber ()) {isolate-> ThrowException (v8 :: Exception :: TypeError (v8 :: String :: NewFromUtf8 (isolate, "The argument must be a number"))); return; } double argsValue = args [0] -> NumberValue (); if (numberOfCalls + argsValue> maxValue) {isolate-> ThrowException (v8 :: Exception :: Error (v8 :: String :: NewFromUtf8 (isolate, "The counter went through the roof!"))); return; } numberOfCalls + = argsValue; auto currentNumberOfCalls = v8 :: Number :: New (isolate, static_cast (numberOfCalls)); args.GetReturnValue (). Set (currentNumberOfCalls); } void Initialize (v8 :: Local exports) {NODE_SET_METHOD (exports, "whoami", WhoAmI); NODE_SET_METHOD (exports, "increment", Increment); } NODE_MODULE (module_name, Initialize)

Now we are going to see the file line by line

#include

The include in C ++ it's like require ()in JavaScript. It will extract everything from the given file, but instead of linking it directly to the source, in C ++ we have the concept of header files.

Maybe you are interested >>>  Asynchronous Programming Basics

We can declare the exact interface in the header files without implementation and then we can include the implementations by their header file. The C ++ linker will take care of linking these two together. Think of it as a documentation file that describes your content, which can be reused from your code.

void WhoAmI (const v8 :: FunctionCallbackInfo & args) {v8 :: Isolate * isolate = args.GetIsolate (); auto message = v8 :: String :: NewFromUtf8 (isolate, "I am Node Hero!"); args.GetReturnValue (). Set (message); } [php] Since this will be a native extension, the v8 namespace is available for use. Note the v8 :: notation, which is used to access the v8 interface. If you don't want to include v8 :: before using any of the types provided by v8, you can add it using using v8; to the top of the file. Then you can omit all v8 :: namespace specifiers from your types, but this can introduce name collisions in your code, so be careful when using them. To be 100% clear, I'll use the v8 :: notation for all v8 types in the code shown. In our example code, we have access to the arguments with which the function was called (from JavaScript), through the args object that also provides us with all the information related to the call. With v8 :: Isolate * we are accessing the current JavaScript scope for our function. Scopes work the same as in JavaScript - we can assign variables and bind them to the lifetime of that specific code. We don't have to worry about de-allocating these memory chunks, because we allocate them as if we were in JavaScript, and the garbage collector will take care of them automatically. [php] function () {var a = 1; } // wingspan

Via args.GetReturnValue () we access the return value of our function. We can configure it for whatever we want as long as it is from space v8 :: of names.

C ++ has built-in types for storing integers and strings, but JavaScript only understands its own v8 :: object types. As long as we are in the realm of the C ++ world, we can use the ones that are built into C ++, but when dealing with JavaScript objects and interoperability with JavaScript code, we have to transform the C ++ types into other than they understand each other. by the JavaScript context. These are the types that are exposed in v8 :: namespace as v8 :: Stringo v8 :: Object.

void WhoAmI (const v8 :: FunctionCallbackInfo & args) {v8 :: Isolate * isolate = args.GetIsolate (); auto message = v8 :: String :: NewFromUtf8 (isolate, "I am Node Hero!"); args.GetReturnValue (). Set (message); }

Let's look at the second method in our file that increments a counter by a supplied argument to an upper bound of 10.

This function also accepts a JavaScript parameter. When you are accepting JavaScript parameters, you have to be careful because they are loosely written objects. (You are probably used to this in JavaScript by now.)

Array array contains v8 :: Objects, so they are all JavaScript objects, but be careful with these, because in this context we can never be sure what they can contain. We have to explicitly check the types of these objects. Fortunately, there are helper methods added to these classes to determine their type before type conversion.

To maintain compatibility with existing JavaScript code, we should throw an error if the type of arguments is wrong. To throw a type error, we have to create an error object with the constructor
v8 :: Exception :: TypeError (). The next block will launch a TypeError if the first argument is not a number.

if (! args [0] -> IsNumber ()) {isolate-> ThrowException (v8 :: Exception :: TypeError (v8 :: String :: NewFromUtf8 (isolate, "The argument must be a number"))); return; }

In JavaScript that snippet would look like:

If (typeof arguments [0]! == 'number') {throw new TypeError ('The argument must be a number')}

error: Attention: Protected content.