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来让使用内置模块更简单:
const{app, BrowserWindow} = require('electron');
传统样式
在版本v0.35.0之前,所有的内置模块都需要按造require('module-name')
形式来使用,虽然它有很多弊端,我们仍然在老的应用中友好的支持它。
const app = require('electron').app;
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、数据压缩等其他的能力。
开发环境
开发环境的搭建主要要安装编译工具和C++的IDE,更多内容参考官方文档:
- 要写C++模块,必须先搭建一个环境,node-gyp命令是必须,所以我们得先安装node-gyp
npm install -g node-gyp
- 安装Visual Studio 2015
- 安装Python 2.7(v3.x.x不支持)
开发Addon
Create an empty C/C++ fil创建一个C或者C++的文件,如:binding.cc
然后创建一个binding.gyp文件,在binding.gyp文件中对应的生成的目标node文件名和源码的文件名。
{
"targets": [
{
"target_name": "addon",
"sources": [ "binding.cc" ]
}
]
}
将binding.cc和binding.gyp放置在同一个目录中,利用IDE对binding.cc进行开发。
我们测试编写了一个最简单的函数hello通过V8暴露出来,函数输出“world"字符串。
#include <node.h>
#include <v8.h>
using namespace v8;
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate);
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world"));
}
void init(v8::Local<v8::Object> target) {
NODE_SET_METHOD(target, "hello", Method);
}
NODE_MODULE(binding, init);
编译Addon
首先调用命令来生成生成了Visual Studio的工程文件,具体的命令参数参考Node-gyp Command:
node-gyp configure
生成的工程文件在build文件夹中,文件列表如下:
最后调用命令来生成.node的二进制文件,完成编译工作。
node-gyp build
在build文件夹下面生成Release文件夹,文件夹中有我们需要的目标文件addon.node,如下所示:
使用C/C++ Addon
我们创建一个测试的js文件来测试上面编译生成的addon.node组件,测试test.js如下:
'use strict';
var addon = require('./build/Release/addon');
console.log('addon.hello() =', binding.hello());
然后使用node来执行测试,测试命令:
node test.js
测试结果:
addon.hello() = world
如何编译为Electron组件?
在上面介绍了如何开发Node的C/C++的Addon组件,但是由于Electron使用的Node版本可能跟你本机编译的Node版本不一致,所以需要再次编译才能在Electron上使用,可以在这里查看 Electron 内置的 Node 版本,或者在Electron的chrome调试器使用 process来查看。
我们目前使用的Electron版本为1.2.5,所附带的Node版本为6.1.0,架构为32位的应用程序,得到这些信息以后就可以用node -gyp再次编译上面的源码,重新生成的addon.node才可以被Electron使用。
node-gyp rebuild --target=1.2.5 --arch=ia32 --dist-url=https://atom.io/download/atom-shell
利用上面测test.js在Electron上面测试,在控制台正确输出了结果,证明C/C++能够在Electron中正常使用:
Node-FFI组件
node-ffi 是能够被Node.js加载的组件,并且能够用纯JavaScript来对dll进行调用,它是的原生的动态库与Node.js的粘合剂。
如果原来的模块是一个DLL,并且已经有了接口,我们就可以利用FFI组件来调用该DLL,只需要告诉FFI如何来调用该DLL的导出接口,而不需要对DLL重新封装成Node组件。
安装Node-FFI
首先在目录下安装ffi组件
npm install ffi
在ffi的子目录下存在多个原生C/C++的addon,需要用下面命令来重新编译生成。
node-gyp rebuild --target=1.2.5 --arch=ia32 --dist-url=https://atom.io/download/atom-shell
测试使用
测试的dll,导出函数为C类型的标准导出,计算输入参数的阶乘,代码如下:
extern "C" __declspec(dllexport) uint64_t factorial(int max) {
int i = max;
uint64_t result = 1;
while (i >= 2) {
result *= i--;
}
return result;
}
测试的js代码如下,在里面要注明导出函数和返回值类型和输入参数类型:
var ffi = require('ffi');
var libfactorial =ffi.Library(__dirname+'/factorial.dll',{
'factorial':['uint64',['int']]//dll的导出函数名称:[函数返回类型,[输入参数类型]]
});
var output = libfactorial.factorial(parseInt('5'));
alert('Your output:'+output);
测试结果界面,通过ffi调用dll里面的阶乘算法,计算出来结果。