Hello world


这个“Hello World”示例是一个简单的插件,用 C++ 编写,这等同于如下的 JavaScript代码:

  1. module.exports.hello = () => 'world';

首先创建 hello.cc 文件:

  1. // hello.cc
  2. #include <node.h>
  3. namespace demo {
  4. using v8::FunctionCallbackInfo;
  5. using v8::Isolate;
  6. using v8::Local;
  7. using v8::Object;
  8. using v8::String;
  9. using v8::Value;
  10. void Method(const FunctionCallbackInfo<Value>& args) {
  11. Isolate* isolate = args.GetIsolate();
  12. args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
  13. }
  14. void init(Local<Object> exports) {
  15. NODE_SET_METHOD(exports, "hello", Method);
  16. }
  17. NODE_MODULE(addon, init)
  18. } // namespace demo

请注意,所有的 Node.js 插件必须导出如下格式的初始化函数:

  1. void Initialize(Local<Object> exports);
  2. NODE_MODULE(module_name, Initialize)

这里在 NODE_MODULE 后面没有分号,以表示它不是函数(详见 node.h)。

module_name 必须匹配最终二进制的文件名(不包括 .node 后缀)。

hello.cc 例子中,那么,init 是初始化函数,并且 addon 是插件模块的名称。

构建

一旦源代码已被写入,它必须被编译成二进制 addon.node 文件。要做到这一点,在项目的顶层使用类 JSON 的格式描述你的模块构建配置,用于创建一个叫做 binding.gyp 的文件。该文件通过 node-gyp 使用 —— 专门写了一个用于编译 Node.js 插件的工具。

  1. {
  2. "targets": [
  3. {
  4. "target_name": "addon",
  5. "sources": ["hello.cc"]
  6. }
  7. ]
  8. }

注意:一个实用的 node-gyp 版本作为 npm 的一部分,通过 Node.js 捆绑和发行。此版本没有做出可以直接供开发者使用的版本,目的只是为了支持使用 npm install 命令编译并安装插件的能力。希望直接使用 node-gyp 的开发者可以使用 npm install -g node-gyp 命令进行安装。查阅 node-gyp 安装说明了解更多详情,包括平台特定的要求。

一旦 binding.gyp 文件被创建,使用 node-gyp configure 为当前平台生成相应项目的构建文件。这会在 build/ 目录下生成一个 Makefile (在 Unix 平台上)或 vcxproj 文件(在 Windows 上)。

下一步,调用 node-gyp build 命令生成 addon.node 的编译文件。这会被放到 build/Release/ 目录下。

当使用 npm install 安装一个 Node.js 插件时,npm 使用自身捆绑的 node-gyp 版本来执行同样的一套动作,为用户的需要的平台产生一个插件的编译版本。

一旦构建完成,二进制插件就可以通过 require() 指向内置的 addon.node 模块在 Node.js 内部使用:

  1. // hello.js
  2. const addon = require('./build/Release/addon');
  3. console.log(addon.hello()); // 'world'

请参阅下面例子获取详细信息或在 https://github.com/arturadib/node-qt 了解关于生产环境中的例子。

因为编译后的二进制插件的确切路径可能取决于它是如何编译的(如,有时它可能在 ./build/Debug/),插件可以使用绑定包加载已编译的模块。

请注意,虽然 bindings 包在如何定位插件模块的实现上更精细,但它本质上是使用类似于一个 try-catch 的模式:

  1. try {
  2. return require('./build/Release/addon.node');
  3. } catch (err) {
  4. return require('./build/Debug/addon.node');
  5. }

链接到 Node.js 自己的依赖

Node.js 使用了一些静态链接库,比如 V8 引擎、libuv 和 OpenSSL。所有的插件都需要链接到 V8,并且也可能链接到任何其他的依赖。通常情况下,只要简单的包含相应的 #include <...> 声明(如,#include <v8.h>),并且 node-gyp 会找到适当的头。不过,也有一些注意事项需要注意:

  • node-gyp 运行时,它会检测 Node.js 的具体发行版本,并下载完整源码包,或只是头。如果下载了全部的源码,插件将会有完整的权限去访问 Node.js 的全套依赖。然而,如果只下载了 Node.js 的头,则仅由 Node.js 导出的标记可用。

  • node-gyp 可以使用指向本地 Node.js 源代码信息的 --nodedir 标识来运行。使用此选项,插件将有机会访问全套依赖。

使用 require() 加载插件

编译后的二进制插件的文件扩展名是 .node(而不是 .dll.so)。require() 函数是被用于查找具有 .node 文件扩展名的文件,并初始化这些作为动态链接库。

当调用 require() 时,.node 拓展名通常是可以省略的,Node.js 仍可以发现并初始化该插件。警告,然而,Node.js 会首先尝试查找并加载模块或碰巧共享相同的基本名称的 JavaScript 文件。例如,如果有一个 addon.js 文件与二进制 addon.node在同一目录下,那么 require(‘addon’) 将优先考虑 addon.js 文件并加载它来代替 addon.node