Node组件


Node为开发者提供了两种方式来对其进行扩展:一种是通过纯JavaScript,一种是通过C/C++。在Node中使用JavaScript来编写模块是非常容易,也是最常用的方式,但是在一些场景中JavaScript的执行性能可能达不到要求(比如大量的位运算),Node提供了C++的模块扩展接口,以提高执行的性能。

纯JS组件

所有的Node.js's built-in modules在Electron中都可用,并且所有的node的第三方NPM组件也可以放心使用。

Node自带的API文档可查看这里,内建的模块包括crypto用于加解密算法;fs模块用于操作系统的文件操作;http和https用于创建http/https服务端或者客户端请求;net模块是底层的tcp通信操作。

目前有两种使用方式:解构使用和传统使用

解构任务

由于目前TitanOne所使用Electron的版本支持ES6,所以可以使用destructuring assignment来让使用内置模块更简单:

  1. const{app, BrowserWindow} = require('electron');

传统样式

在版本v0.35.0之前,所有的内置模块都需要按造require('module-name')形式来使用,虽然它有很多弊端,我们仍然在老的应用中友好的支持它。

  1. const app = require('electron').app;
  2. const BrowserWindow = require('electron').BrowserWindow;

C/C++ Addon

Node支持C/C++开发的Addon组件,用C++编写的模块和用JavaScript编写的模块在使用方式上并无区别,都是通过require(…)来进行调用,区别在于C++模块是系统编译好的二进制模块(在*nix下是.so,在win下是dll),并且扩展名为.node。在require.node模块的时候,系统通过dlopen函数来加载模块,不需要像JavaScript模块那样再进行编译,而是直接加载运行,这加快了执行的速度。更多内容可以参考官方文档:Node‘s Addon

Electron 同样也支持Node的Addon模块,但由于和官方的 Node 相比使用了不同的 V8 引擎,如果要使用C/C++的原生组件就需要重新编译Addon模块。

Node的组成部分

写一个Node的Addon是相当的复杂,需要有C/C++知识,同时要懂JavaScript的引擎V8的接口,同时需要知道Node的异步事件引擎LibUV

首先我们必须得了解Node的组成,Node其实本质就是具有IO操作功能,并通过JS来暴露API的应用程序,在Node中最重要的就是V8和LibUV,下面详细说明了Node的各个组成部分与功能:

  • V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript 的关键,它为 Javascript 提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。

  • Libuv:它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。

  • C-ares:提供了异步处理 DNS 相关的能力。

  • http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。

 Node组件  - 图1

开发环境

开发环境的搭建主要要安装编译工具和C++的IDE,更多内容参考官方文档

  • 要写C++模块,必须先搭建一个环境,node-gyp命令是必须,所以我们得先安装node-gyp
  1. npm install -g node-gyp

开发Addon

Create an empty C/C++ fil创建一个C或者C++的文件,如:binding.cc

然后创建一个binding.gyp文件,在binding.gyp文件中对应的生成的目标node文件名和源码的文件名。

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

binding.ccbinding.gyp放置在同一个目录中,利用IDE对binding.cc进行开发。

 Node组件  - 图2

我们测试编写了一个最简单的函数hello通过V8暴露出来,函数输出“world"字符串。

  1. #include <node.h>
  2. #include <v8.h>
  3. using namespace v8;
  4. void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
  5. v8::Isolate* isolate = args.GetIsolate();
  6. v8::HandleScope scope(isolate);
  7. args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world"));
  8. }
  9. void init(v8::Local<v8::Object> target) {
  10. NODE_SET_METHOD(target, "hello", Method);
  11. }
  12. NODE_MODULE(binding, init);

编译Addon

首先调用命令来生成生成了Visual Studio的工程文件,具体的命令参数参考Node-gyp Command

  1. node-gyp configure

生成的工程文件在build文件夹中,文件列表如下:

 Node组件  - 图3

最后调用命令来生成.node的二进制文件,完成编译工作。

  1. node-gyp build

build文件夹下面生成Release文件夹,文件夹中有我们需要的目标文件addon.node,如下所示:

 Node组件  - 图4

使用C/C++ Addon

我们创建一个测试的js文件来测试上面编译生成的addon.node组件,测试test.js如下:

  1. 'use strict';
  2. var addon = require('./build/Release/addon');
  3. console.log('addon.hello() =', binding.hello());

然后使用node来执行测试,测试命令:

  1. node test.js

测试结果:

  1. addon.hello() = world

如何编译为Electron组件?

在上面介绍了如何开发Node的C/C++的Addon组件,但是由于Electron使用的Node版本可能跟你本机编译的Node版本不一致,所以需要再次编译才能在Electron上使用,可以在这里查看 Electron 内置的 Node 版本,或者在Electron的chrome调试器使用 process来查看。

 Node组件  - 图5

我们目前使用的Electron版本为1.2.5,所附带的Node版本为6.1.0,架构为32位的应用程序,得到这些信息以后就可以用node -gyp再次编译上面的源码,重新生成的addon.node才可以被Electron使用。

  1. node-gyp rebuild --target=1.2.5 --arch=ia32 --dist-url=https://atom.io/download/atom-shell

利用上面测test.js在Electron上面测试,在控制台正确输出了结果,证明C/C++能够在Electron中正常使用:

 Node组件  - 图6

Node-FFI组件

node-ffi 是能够被Node.js加载的组件,并且能够用纯JavaScript来对dll进行调用,它是的原生的动态库与Node.js的粘合剂。

如果原来的模块是一个DLL,并且已经有了接口,我们就可以利用FFI组件来调用该DLL,只需要告诉FFI如何来调用该DLL的导出接口,而不需要对DLL重新封装成Node组件。

安装Node-FFI

首先在目录下安装ffi组件

  1. npm install ffi

在ffi的子目录下存在多个原生C/C++的addon,需要用下面命令来重新编译生成。

  1. node-gyp rebuild --target=1.2.5 --arch=ia32 --dist-url=https://atom.io/download/atom-shell

测试使用

测试的dll,导出函数为C类型的标准导出,计算输入参数的阶乘,代码如下:

  1. extern "C" __declspec(dllexport) uint64_t factorial(int max) {
  2. int i = max;
  3. uint64_t result = 1;
  4. while (i >= 2) {
  5. result *= i--;
  6. }
  7. return result;
  8. }

测试的js代码如下,在里面要注明导出函数和返回值类型和输入参数类型:

  1. var ffi = require('ffi');
  2. var libfactorial =ffi.Library(__dirname+'/factorial.dll',{
  3. 'factorial':['uint64',['int']]//dll的导出函数名称:[函数返回类型,[输入参数类型]]
  4. });
  5. var output = libfactorial.factorial(parseInt('5'));
  6. alert('Your output:'+output);

测试结果界面,通过ffi调用dll里面的阶乘算法,计算出来结果。

 Node组件  - 图7