1 应用场景

Automation

​ 如果您的c++运行作为一个独立的命令行,您不需要源代码来利用选项1 -automation 选项。您可以使用Node的子进程API 运行您的c++程序。这个选项适用于将任何东西带到web上——如果你只是运行它的话,你的命令行程序写在什么语言上并没有什么区别。如果您正在阅读这篇文章,希望获得C代码、Fortran代码或其他一些语言,那么这个选项值得一读。

​ 自动化选项不仅仅针对那些没有c++代码的人。如果您有c++代码,或者可以很容易地转换成命令行程序,那么这个选项是合理的,如果您可以使用性能,并且您并不想陷入语言集成的麻烦中。

Shared Library / DLL

​ 如果您处理的是c++ dll/lib,或者您有c++源代码,并且可以进行适当的修改,以创建动态库,那么 shared library 方法可能对您很有效。在本章中,我们将详细介绍如何使用外部函数接口模块进行此操作。这个选项可以让您更精确地控制如何将c++集成到节点中,因为对c++例程的调用通常可以直接写到Node.js代码中。虽然这种方法可以使您更接近完整的集成,但是您仍然需要处理类型转换和在调用c++时阻塞。如果您想要更好的集成,这是一个很好的选择,而无需花费大量时间来处理V8。

Node.js Addon

​ 如果您有c++源代码,那么第三个选项是创建一个本机 Node.js模块调用你的c++。虽然这是一个更具挑战性的方法,但是您获得了大量的灵活性和性能。您还可以选择异步调用您的c++,这样您就不会阻塞web应用程序的事件循环,而c++正在处理数字。当我们在本节中介绍这部分内容时,它将主要作为对书中主要章节中已经介绍的材料的回顾。

2 插件实现

主要的方案

当前主流的方法,就是最简单的napi。下面是其发展过程。

  • 使用nodejs和v8原生的API实现C++接口的编译工作.是nodejs和V8引擎原生的API,代码级别的API。会随着版本进行演进,并且十分难搞。
  • 使用第三方的nan库进行API实现。也已经淘汰。
  • 使用N-API实现C++接口的编译工作。经过nodejs官方封装的二进制借口文件,ABI。接口稳定,并且向后兼容。
  • 使用EFF网络中的调用DLL的方法。

3 n-api

背景

  • 以 C 的风格提供稳定 ABI 接口;
  • 消除 Node.js 版本的差异;
  • 消除 JavaScript 引擎的差异(如 Google V8、Microsoft ChakraCore 等)

特性

  • 提供头文件 node_api.h;
  • 任何 N-API 调用都返回一个 napi_status 枚举,来表示这次调用成功与否;
  • N-API 的返回值由于被 napi_status 占坑了,所以真实返回值由传入的参数来继承,如传入一个指针让函数操作;
  • 所有 JavaScript 数据类型都被黑盒类型 napi_value 封装,不再是类似于 v8::Object、v8::Number 等类型;
  • 如果函数调用不成功,可以通过 napi_get_last_error_info 函数来获取最后一次出错的信息。

初始化

  • 引入头文件
1
2
#define NAPI_VERSION 3//NAPI_VERSION for the given release of Node.js.
#include <node_api.h>
1
2
3
4
5
6
7
8
9
10
11
12
13
void Init(napi_env env, napi_value exports, napi_value module, void* priv)
{
napi_status status;

// 用于设置 exports 对象的描述结构体
napi_property_descriptor desc =
{ "echo", 0, Echo, 0, 0, 0, napi_default, 0 };

// 把 "echo" 设置到 exports 去
status = napi_define_properties(env, exports, 1, &desc);
}

NAPI_MODULE(addon, Init)

napi_property_descriptor 是用于设置对象属性的描述结构体,它的声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13


typedef struct {
const char* utf8name;

napi_callback method;
napi_callback getter;
napi_callback setter;
napi_value value;

napi_property_attributes attributes;
void* data;
} napi_property_descriptor;

函数声明

通过 napi_get_cb_info 获取当次函数请求的参数信息,包括参数数量和参数体(参数体以 napi_value 的数组形式体现);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
napi_value Echo(napi_env env, napi_callback_info info)
{
napi_status status;

size_t argc = 1;
napi_value argv[1];
status = napi_get_cb_info(env, info, &argc, argv, 0, 0);
if(status != napi_ok || argc < 1)
{
napi_throw_type_error(env, "Wrong number of arguments");
return 0; // napi_value 实际上是一个指针,返回空指针表示无返回值
}

return argv[0];
}

4 构建

C/C++编译工具

  • Linux
1
gcc编译环境
  • windows
1
2
visual Studio offers all the required compiler tools. \\or
npm install --global windows-build-tools

node-gyp构建工具

  • node-gyp 是基于 GYP4 的。它会识别包或者项目中的 binding.gyp5 文件,然后根据该配置文件生成各系统下能进行编译的项目,如 Windows 下生成 Visual Studio 项目文件(*.sln 等),Unix 下生成 Makefile。在生成这些项目文件之后,node-gyp 还能调用各系统的编译工具(如 GCC)来将项目进行编译,得到最后的动态链接库 *.node 文件。
  • 项目构建的描述文件binding.gpy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"targets": [{
"target_name": "addon1",
"sources": [ "1/addon.cc", "1/myobject.cc" ]
}, {
"target_name": "addon2",
"sources": [ "2/addon.cc", "2/myobject.cc" ]
}, {
"target_name": "addon3",
"sources": [ "3/addon.cc", "3/myobject.cc" ]
}, {
"target_name": "addon4",
"sources": [ "4/addon.cc", "4/myobject.cc" ]
}cmake-jsre:通过当前目录的 binding.gyp 生成项目文件,如 Makefile 等;
$ node-gyp build:将当前项目进行构建编译,前置操作必须先 configure;
$ node-gyp clean:清理生成的构建文件以及输出目录,说白了就是把目录清理了;
$ node-gyp rebuild:相当于依次执行了 clean、configure 和 build;
$ node-gyp install:手动下载当前版本的 Node.js 的头文件和库文件到对应目录。
  • 在我们编译一个 C++ 原生扩展的时候,它会去指定目录下(通常是 ~/.node-gyp 目录下)搜我们当前 Node.js 版本的头文件和静态连接库文件。

cmake-js构建工具

CMake.js is an alternative build system based on CMake.CMake.js is a good choice for projects that already use CMake or for developers affected by limitations in node-gyp.

5 步骤

  1. 首先使用v8编写nodejs支持的C++接口文件。或者使用n-api
  2. 然后编写binding.gyp,使用node-gyp配置并将C++文件编译成nodejs的模块。其中,调用了cmake编译工具进行搬移。
  3. 然后在js文件中引入模块。调用并输出结果。