由于项目引入了一个通过C++语言实现的加密算法,需要在Node.js中调用相关端口实现加密,所以尝试实现了下载node.js中引入C++库文件。
此项目中使用的引入方法是直接使用node-gym模块编译一个基于C++的node.js模块,然后在主程序中引入这个模块。
node-gym
node-gyp 是基于 GYP( 全称 Generate Your Projects,是谷歌开发的一套构建系统) 的。它会识别包或者项目中的 binding.gyp文件,这个里面是JSON的文件对工程依赖的各种文件进行了描述(可以理解为一个node版的CMakeList),然后根据该配置文件生成各系统下能进行编译的项目,如 Windows 下生成 Visual Studio 项目文件(*.sln 等),Unix 下生成 Makefile。在生成这些项目文件之后,node-gyp 还能调用各系统的编译工具(如 GCC)来将项目进行编译,得到最后的动态链接库 *.node 文件。
cpp接口文件与gym编写
插件通常都会暴漏某些对象和函数给 Node.js 中的 JavaScript 调用。当 JavaScript 调用函数时,也必须将将传入的参数映射给 C/C++ 代码,在函数调用完成后,还要映射 C/C++ 传回的返回值。
下面代码演示了如何读取 JavaScript 传递来的函数以及如何传输返回值:
C++文件如下:
// AESencrypt.cpp
#include <node.h>
//此处引入要调用的头文件
#include <aes_encrypt.h>
namespace demo {
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;
// 此处是encrypt方法的执行函数,在js中调用的参数通过args传入
void Encrypt(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
// 检查参数个数
if (args.Length() < 3) {
// 如果个数不正确便扔回错误到JavaScript
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong number of arguments")));
return;
}
// 检查参数类型
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong arguments")));
return;
}
//当需要参数为string类型时的示例
v8::String::Utf8Value param(args[2]->ToString());
std::string inputstr= std::string(*param);
char buffer[60];
// 执行主函数
aes_encrypt((char *)inputstr.c_str(),args[0],args[1],buffer);
// 设定返回值 (using the passed in
// FunctionCallbackInfo<Value>&)(返回一个字符串)
args.GetReturnValue().Set(String::NewFromUtf8(isolate, buffer));
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "encrypt", Encrypt);
}
NODE_MODULE(AESencrypt, Init)
} // namespace demo
值得注意的是,开发 Node.js 插件的开发时,必须输出一个初始化函数,模式如下:
void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)
在 NODE_MODULE 的行末没有分号,因为它并不是一次函数调用(详见 node.h
)。module_name
必须二进制文件名相匹配(不包含文件名的 .node 后缀)。通过上述代码,我们在 AESencrypt.cpp
文件中声明了初始化函数是 Init
,插件名称是 encrypt
。
接下来编写gym文件
{
"targets": [
{
"target_name": "AESencrypt",
"sources": [ "AESencrypt.cpp" ],
"include_dirs": ["include"],
"libraries": [ "<(module_root_dir)/obj/lib/libencrypt.a" ],
"cflags!": [ "-fno-exceptions" ],
"cflags": [ "-std=c++11","-D_FILE_OFFSET_BITS=64"],
"cflags_cc!": [ "-fno-exceptions" ]
}
]
}
其中libraries中引用库文件就好,其中一些关于跨系统的兼容可以参考官方文档
接下来执行
node-gyp configure
node-gyp build
编译文件生成一个.node文件
在js代码中
const AES = require('./build/Release/AESencrypt.node');
引用,然后使用AES.encrypt()
调用即可
一些问题总结
- 编译中引用静态库造成的问题
由于编译生成的是动态库,引用的是静态库,在编译时可能弹出
make: Entering directory '/home/work/work/testAES/build'
ACTION Regenerating Makefile
SOLINK_MODULE(target) Release/obj.target/testAES.node
/usr/bin/ld: /home/work/work/testAES/obj/lib/libaes.a(test_aes.c.o): relocation R_X86_64_32S against `.rodata.cst16' can not be used when making a shared object; recompile with -fPIC
/home/work/work/testAES/obj/lib/libaes.a: error adding symbols: Bad value
collect2: error: ld returned 1 exit status
报错。此时需要重新加"-fPic"
参数编译一遍库文件。
在cmake中需要写:
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
- 自动生成
由于最终需要提供给用户使用,模块在不同环境下都需要重新编译一遍,而每次部署都让用户执行一次node-gyp configure build
明显不现实。直接在package.json
里面加上一句
gypfile": true
让每次执行npm install
的时候自动编译一遍,让问题能方便很多
还可以考察的一些模块
- node-ffi: 这个模块可以直接引入C++的库,实现不用操作任何C++代码的C++库文件引入
- node-pre-gym: