Node引入C++庫文件

由于項目引入了一個通過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:
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容