由于項目引入了一個通過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: