Construction of front-end node-gyp Native Addon

Thousands and thousands of the front wheels, but still some bottlenecks, the company needs to call its own tcp agreement in the front end, the agreement only c ++ version of the package. Leaders hope can be directly transferred to the module, do not repeat create the wheel.

Honestly I'm still a little impression of C, C Language, after all, is also a certified person .. but it was useless for a long time, watching the C language type definitions lot, let me use this perennial implicit type of jser feel then. this is the most difficult to achieve since I practitioners hello worldproject.

Overall description

Native Addon

A Native Addon is a binary file, which is a low-level language such as C or C ++ implementation in Nodejs environment, we can call like any other module require () import Native Addon

Native Addon .js other end of the same, will expose module.exportsor exportsobjects, which are encapsulated into a file node module also referred to as Native Module (primary module) .

So how Native Addon can load and run applications in js? Js allow Native Addon compatible with the environment and exposed API can be the same as a normal node module to use it?

Here have to say DLL ( Dynamic Linked Library ) DLL, he was used by the standard C or C ++ compiler is made in linux or macOS also referred to as Shared Library . A DLL that can be run in a dynamic program loaded, DLL containing C or C ++ API source code, and can communicate. there are also static and dynamic whether it? - can really have a reference here to see this difference between the two, it simply static faster than dynamic, not static because You need to go look for a dependency file and load, but smaller particle size can be dynamically modified packed files.

In Nodejs, when compiling a DLL time, it will be exported to .node suffix file. You can then require the file js file like the same, but some of the code hints impossible.

How Native Addon is work?

Nodejs is actually a collection of many open-source libraries, you can look at his warehouse , looking deps in package.json in use is the Google open source V8 engine to execute js code, V8 happens to be written in C ++, do not believe you look v8 the warehouse . for as asynchronous IO, cycling events and other low-level features are dependent on Libuv library.

When the installation is finished nodejs, actually it contains a compiled version of the entire Nodejs and its dependent source code installed, so you do not have a dependence and a manually install these, but Nodejs be compiled from the source code for these libraries. so what to do with Native Addon? because Nodejs is a low-level C ++ and C compilers made, so itself has the ability to C and C ++ call each other .

Nodejs can be dynamically loaded C and C ++ DLL files, and use its API js operate the program. These are the basic Native Addon works in Nodejs in.

ABI Application Binary Interface Application Binary Interface

ABI is specific to the application to access compiled | compiled program, with the API (Application Programming Interface) is very similar, just interact with binary files, but also to access a memory address to find Symbols, such as numbers, objects, classes and functions

Then the ABI with Native Addon what does it matter? He is the bridge Native Addon and Nodejs communicate. DDL file is actually to register or access to the value provided by ABI Nodejs, and to execute commands by Nodejs exposed API and libraries .

For example, there is a Native Addon want to add a sayHellomethod to exportsthe subject, he can create a new thread, asynchronous tasks via API access Libuv, and then call the callback function after finished. ABI such Nodejs provided work is complete.

Generally speaking, will be compiled as C or C ++ DLL, some will use is called header header file metadata. Are at .hthe end. Of course, these header files, can be exposed Nodejs and node library lets go Native Addon information referenced header file can reference

A typical use is cited #includefor example #inlude<v8.h>, and then use the statement to write Nodejs executable code. There are four ways to use the header file.

1. Using the core implementation

For example v8.h-> v8 engine, uv.h-..> Libuv library these two files in the installation directory node in but the problem is the dependence between Native Addon and Nodejs too high because these libraries are likely to Nodejs With Node version of the update and change, then every time whether it should go to adaptation to change after change Native Addon? such high maintenance costs. you can look at the node official document for a description of this method, there is a better way below

2. Using Native Abstractions for Node (NAN)

NAN project initially is for internal abstract nodejs and v8 engine implementation. The basic concept is to provide the installation package is a npm, you can through the front of the package management tool yarnor npminstallation, he includes the nan.hheader file, which for nodejs module and v8 . abstracts but NAN has the following disadvantages:

  • Incomplete abstract of the V8 api
  • Nodejs does not provide all the support libraries
  • Nodejs not maintain official library.

So also recommend two ways

3. N-API

N-API similar NAN project, but is maintained by the official nodejs, since there is no need to install external dependencies to import header file. Reliable and provides an abstraction layer
was exposing node_api.hheader, the internal implementation nodejs abstract and packages, every Nodejs update, N-API will be simultaneously optimized to ensure the reliability of ABI
here are all N-API interface documentation, and here is the official description of the ABI stability of the N-API

N-API suitable for both C and C ++, C ++ but more simple to use the API, then, node-addon-api is made.

4. Module node-addon-api

With these two, he has his own header file napi.hthat contains all of the C ++ package of N-API, and the like N-API is maintained by the official, click here to see the warehouse because he uses in comparison to other more simple, so during the C ++ API package when the preferred method.

I began to realize Hello World

Preparing the Environment

Global need to install yarn global add node-gyp, because also on Python, (GYP stands for Generate Your Project, is written in Python using a tool ). Specific python path to develop environmental and reference documentation .

After the installation is completed with a C or C ++ compiler to generate the template code Native Addon DLL or the CLI, the operation after the meal fierce as a tiger, will generate a .nodefile, but this template is how to generate it? This is the following binding.gypfile

binding.gyp

binding.gypContains the name of the module, which files should be compiled and so on. Template contains the necessary build instructions file based on a different platform or architecture (32 or 64), but also it provides the necessary header or source file to compile C or C ++, similar to JSON format details can click to see .

Setting project

After installation dependent, the real beginning of our hello world project, the overall structure of the project file:

├── binding.gyp
├── index.js
├── package.json
├── src
│   ├── greeting.cpp
│   ├── greeting.h
│   └── index.cpp
└── yarn.lock

Installation depends

Native Module with normal node modules or other packages as NPM. First yarn init -yinitialize the project, and then install Addon-API-node yarn add node-addon-api.

Create a C ++ example

Create a file greeting.h

#include <string>
std::string helloUser(std::string name);

Create a file greeting.cpp

#include <iostream>
#include <string>
#include "greeting.h"

std::string helloUser(std::string name) {
    return "Hello " + name + "!";
}

Creating index.cpp file, which contains napi.h

#include <napi.h>
#include <string>
#include "greeting.h"

// 定义一个返回类型为 Napi String 的 greetHello 函数, 注意此处的 info
Napi::String greetHello(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  std::string result = helloUser('Lorry');
  return Napi::String::New(env, result);
}

// 设置类似于 exports = {key:value}的模块导出
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(
    Napi::String::New(env, "greetHello"), // key
    Napi::Function::New(env, greetHello)  // value
  );

  return exports;
}

NODE_API_MODULE(greet, Init)

Note that you see a lot of Napi :: this writing, in fact, this is between js and C ++ data format bridge, both sides see the definition of data types to understand.
Here experienced the following process:

  1. Import napi.hheader file, he would say the following to resolve the specified path binding.gyp
  2. Import string standard headers and greeting.hcustom headers. Note the use of "" and the difference between <>, and "" will find the current path, please see
  3. :: Use Napi are beginning to use the node-addon-apiheader files. Napi is a namespace. Because macro does not support namespaces, so NODE_API_MODULEthe absence
  4. NODE_API_MODULEIs a node-api(N-API) encapsulated NAPI_MODULEmacro function provided ( macro ) which will be used in js requireinvoked when introduced into a Native Addon.
  5. The first parameter to a unique value used to register into the node in the export representing the module name. The best and binding.gypbe consistent in target_name, but here is the use of a tag label format rather than a string of
  6. The second parameter is a function of C ++, he will be called when the Nodejs begin registering this method. Respectively will pass envand exportsparameters
  7. envValue is a Napi::envtype, comprising the environment (Environment) at the registration module, this is used when the N-API operation Napi::String::Newrepresents the creation of a new Napi::Stringtype of value. Such a helloUser will std:stringconvertedNapi::String
  8. exportsIs a module.exportslow-level API, he was the Napi::Objecttype, can be used Setto add attributes method, reference documents , the function must return aexports

Create a binding.gypfile

{
  "targets": [
    {
      "target_name": "greet",               // 定义文件名
      "cflags!": [ "-fno-exceptions" ],     // 不要报错
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [                          // 包含的待编译为 DLL 的文件们
        "./src/greeting.cpp",
        "./src/index.cpp"
      ],
      "include_dirs": [                     // 包含的头文件路径, 让 sources 中的文件可以找到头文件
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      'defines': [ 
        'NAPI_DISABLE_CPP_EXCEPTIONS'       // 去掉所有报错
      ],
    }
  ]
}

Generate a template file

In binding.gypuse at the same level directory

node-gyp configure

It will generate a build folder contains the following files:

./build
├── Makefile            // 包含如何构建 native 源代码到 DLL 的指令, 并且兼容 Nodejs 的运行时
├── binding.Makefile    // 生成文件的配置
├── config.gypi         // 包含编译时的配置列表
├── greet.target.mk     // 这个 greet 就是之前配置的 target_name 和 NODE_API_MODULE 的第一个参数
└── gyp-mac-tool        // mac 下打包的python 工具

Build and compile

node-gyp build

We will build a .nodefile

./build
├── Makefile
├── Release
│   ├── greet.node              // 这个就是编译出来的node文件, 可直接被 js require 引用
│   └── obj.target
│       └── greet
│           └── src
│               ├── greeting.o
│               └── index.o
├── binding.Makefile
├── config.gypi
├── greet.target.mk
└── gyp-mac-tool

Come this far you will find the .nodefile can not be opened, because he would not give the reading, is a binary file. This time you can try to wave

// index.js
const addon = require('./build/Release/greet.node')
console.log(addon.greetHello())

Directly node index.jsrun the code you will find print out Hello Lorry !, it is the content helloUser inside. Really not easy.

Only this it? Not enough

Parameter passing

The code is written dead Lorry, if I were Mike, Jane, John Doe king five it? And can not function parameter passing is not a good function

Thus said before the info on the function, details, refer to as the info [] operator overloading , can enable access to the class C ++ The following is an array. index.cppFile greetHellomodification function of:

Napi::String greetHello(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  std::string user = (std::string) info[0].ToString();
  std::string result = helloUser(user);
  return Napi::String::New(env, result);
}

Then use

node-gyp rebuild

Referenced in the file modification index.js

const addon = require('./build/Release/greet.node')
console.log(addon.greetHello('张三')) // Hello 张三!

At this point, finally achieved a relatively complete our hello world. Do not worry, there are goods

If you want to be like other packages can still publish it, just like the normal operating procedures of npm package almost in. package.jsonSpecified in the main field index.js, and then modify the index.jscontent as follows:

const addon = require('./build/Release/greet.node')
module.exports = addon.greetHello

Re-use yarn packcan be a packaged .tgz, can be introduced in other projects. Are there any? A little bit

About bale of cross-platform

Usually when the release of the module, not the buildfolders into account, but the .nodefile is on the inside. And .nodeto say before the file, depending on the system architecture and, if you are using macOS packaged .nodecertainly not be used on the windows so how to achieve compatibility it? Yes, every time reconfiguring build it again when the user in accordance with the corresponding hardware installation, which is to use node-gyp rebuild, npm or yarn found in the installation-dependent process binding.gyp, then automatically installed locally node-gyp, so rebuildto success.

However, remember? Processes other than node-gyp there are other preconditions, which is why often being given node-gyp will appear when you install some libraries, such as python version? Node version? May lead to users install the module crazy so there is a way: package a .node file for each platform architecture , which can be achieved by installing distinguished install script pacakge.json, there is a third-party packages node-pre-gypcan be automated.
If you do not want to use node-pre-gyp in less complex configuration, you can also try prebuild-installthe wheel

But there is a problem, how do we achieve a package files on different platforms and architectures? Should I buy a variety of hardware to package? Unrealistic. All right, there are the wheels prebuild, you can set different platforms, architecture and even node versions can be specified.

PS: There is also a vscode pit, using the C ++ extension time code hints always remind me #include <napi.h>find the file, but the packaging is no problem, speculation is not capable of recognizing binding.gyp editor in head file search path to find a lot of places do not have the solution. Finally, turn the document found this plug-in can be configured clang.cxxflags, and thus I try to add a header file to specify the path of -I${workspaceRoot}/node_modules/node-addon-apino problem, you can enjoy the code hints, or really very easy to write good too !!

Guess you like

Origin www.cnblogs.com/BigJ/p/Cxx2JS.html