簡單上手nodejs調用c++(c++和js的混合編程)

来源:https://www.cnblogs.com/andrewwang/archive/2018/08/02/9409876.html
-Advertisement-
Play Games

因為項目的原因,最近經常使用node.js搭RESTful介面。 性能還是很不錯啦,感覺比Spring Boot之類的要快。而且在不錯的性能之外,只要程式結構組織好,別讓太多的回調把程式結構搞亂,整體開發效率比Java快的就太多了。 如果想進一步提高效率,使用c++來優化部分模塊是不錯的選擇。尤其可 ...



因為項目的原因,最近經常使用node.js搭RESTful介面。
性能還是很不錯啦,感覺比Spring Boot之類的要快。而且在不錯的性能之外,只要程式結構組織好,別讓太多的回調把程式結構搞亂,整體開發效率比Java快的就太多了。

如果想進一步提高效率,使用c++來優化部分模塊是不錯的選擇。尤其可貴的是nodejs對於同c++的混合編程支持的很好,個人感覺跟寫Python的擴展模塊處於同樣的易用水平。

我們從Hello World開始:
首先要有一個空白的工作目錄,在其中建立一個node包管理文件package.json,內容為:

{
  "name": "test-cpp-module",
  "version": "0.1.0",
  "private": true,
  "gypfile": true
}

隨後在目錄中執行命令:npm install node-addon-api --save安裝nodejs擴展模塊的開發支持包。這裡假設你已經安裝配置好了nodejs和相應的npm包管理工具,還有xcode的相關命令行編譯工具。我們不重覆這些基本工具的安裝配置,需要的話請參考官網相關文檔。
上面命令執行完成,我們就完成了基本開發環境的配置。

c++的模塊由binding.gyp文件描述,並完成自動編譯的相關配置工作,我們新建一個binding.gyp文件,內容為:

{
  "targets": [
    {
      "target_name": "democpp",
      "sources": [
        "democpp.cc"
      ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "dependencies": [
        "<!(node -p \"require('node-addon-api').gyp\")"
      ],
      "cflags!": ["-fno-exceptions"],
      "cflags_cc!": ["-fno-exceptions"],
      "defines": ["NAPI_CPP_EXCEPTIONS"],
      "xcode_settings": {
        "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
      }
    }
  ]
}
  • 文件中首先使用target_name指定了編譯之後模塊的名稱。
  • sources指明c++的源文件,如果有多個文件,需要用逗號隔開,放到同一個數組中。
  • include_dirs是編譯時使用的頭文件引入路徑,這裡使用node -p執行node-addon-api模塊中的預置變數。
  • dependencies是必須的,不要改變。
  • 後面部分,cflags!/cflags_cc!/defines三行指定如果c++程式碰到意外錯誤的時候,由NAPI介面來處理,而不是通常的由c++程式自己處理。這防止因為c++部分程式碰到意外直接就退出了程式,而是由nodejs程式來捕獲處理。如果是在Linux中編譯使用,有這三行就夠了。
  • 但如果是在macOS上編譯使用,則還要需要最後一項xcode-settings設置,意思相同,就是關閉macOS編譯器的意外處理功能。
    最後是c++的源碼,democpp.cc文件:
#include <napi.h>

using namespace Napi;

String Hello(const CallbackInfo& info) {
  return String::New(info.Env(), "world");
}
Napi::Object  Init(Env env, Object exports) {
  exports.Set("hello", Function::New(env, Hello));
  return exports;
}
NODE_API_MODULE(addon, Init)

程式中引入napi.h頭文件,使用Napi的namespace還有最後的NODE_API_MODULE(addon,Init)都是模板化的,照抄過來不用動。
Init函數中,使用exports.Set()引出要暴露給nodejs調用的函數。如果有多個需要引出的函數,就寫多行。
Hello函數是我們主要完成工作的部分,本例中很簡單,只是用字元串的方式返回一個“world”。

以上democpp.cc/binding.gyp/package.json三個文件準備好之後,在命令行執行:npm install,順利的話會得到這樣的輸出信息:

$ npm install

> [email protected] install /home/andrew/Documents/dev/html/nodejs/callcpp
> node-gyp rebuild

  SOLINK_MODULE(target) Release/nothing.node
  CXX(target) Release/obj.target/democpp/democpp.o
  SOLINK_MODULE(target) Release/democpp.node

這表示編譯順利完成了,如果碰到錯誤,可以根據錯誤信息去判斷解決方案。通常都是環境配置缺少相關程式或者上述的三個文件有打字錯誤。
下麵我們驗證一下模塊的編譯結果,在命令行使用nodejs,引入編譯的模塊文件,然後調用hello函數來看看:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function] }
> democpp.hello()
'world'
> 

上面是最簡單的一個範例,下麵我們增加一點難度。在GNU的環境下,通常我們的程式都會包含很多第三方的擴展庫,我們這裡再舉一個調用openssl的例子:
package.json文件不用修改,我們不需要在nodejs層面增加新的依賴包。
編譯帶第三方擴展庫的c++程式,通常需要在編譯時指定額外的頭文件包含路徑和鏈接第三方庫,這些都是在binding.gyp中指定的,這些指定在nodejs自動編譯的時候,會解析並應用在命令行的編譯工具中。

{
  "targets": [
    {
      "target_name": "democpp",
      "sources": [
        "democpp.cc"
      ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "libraries": [ 
        '-lssl -lcrypto',
      ],
      "dependencies": [
        "<!(node -p \"require('node-addon-api').gyp\")"
      ],
      "cflags!": ["-fno-exceptions"],
      "cflags_cc!": ["-fno-exceptions"],
      "defines": ["NAPI_CPP_EXCEPTIONS"],
      "xcode_settings": {
        "GCC_ENABLE_CPP_EXCEPTIONS": "YES"
      }
    }
  ]
}

在macOS和常用linux版本中,openssl的頭文件會自動安裝在系統的頭文件路徑中,比如/usr/local/include,所以這裡頭文件的引入路徑並沒有增加。如果使用了自己安裝的擴展庫,需要在include_dirs一節增加新的頭文件包含路徑。
接著增加了libraries一節,指定了Openssl擴展庫的鏈接參數-lssl -lcrypto,這個是必須的。
最後是修改democpp.cc文件,添加一個使用openssl中的md5演算法對字元串進行md5編碼的函數:

#include <napi.h>
#include <openssl/md5.h>

using namespace Napi;

void openssl_md5(const char *data, int size, unsigned char *buf){
    MD5_CTX c;
    MD5_Init(&c);
    MD5_Update(&c,data,size);
    MD5_Final(buf,&c);
}

String GetMD5(const CallbackInfo& info) {
  Env env = info.Env();
  std::string password = info[0].As<String>().Utf8Value();
  //printf("md5 in str:%s %ld\n",password.c_str(),password.size());
  unsigned char hash[16];
  memset(hash,0,16);
  openssl_md5(password.c_str(),password.size(),hash);
  char tmp[3];
  char md5str[33]={};
  int i;
    for (i = 0; i < 16; i++){
      sprintf(tmp,"%02x",hash[i]);
      strcat(md5str,tmp);
    }
  return String::New(env, md5str,32);
}

String Hello(const CallbackInfo& info) {
  return String::New(info.Env(), "world");
}
Napi::Object  Init(Env env, Object exports) {
  exports.Set("hello", Function::New(env, Hello));
  exports.Set("md5", Function::New(env, GetMD5));
  return exports;
}
NODE_API_MODULE(addon, Init)

為了工作方便,源碼中增加了一個沒有引出的openssl_md5函數,僅供程式內部使用。因為沒有引出,nodejs並不知道這個函數的存在。
從nodejs傳遞參數給c++的函數,是使用info[0].As<String>().Utf8Value()這樣的形式。返回值到nodejs在hello函數中就已經看過了。
各項修改完成,同樣回到命令行使用npm install重新編譯。編譯的過程和信息略,我們直接看調用的測試:

> $ node
> democpp=require("./build/Release/democpp.node")
{ hello: [Function], md5: [Function] }
> democpp.hello()
'world'
> democpp.md5("abc")
'900150983cd24fb0d6963f7d28e17f72'
> 

想驗證一下計算的正確性?可以直接執行openssl試試:

$ echo -n "abc" | openssl md5
900150983cd24fb0d6963f7d28e17f72

嗯,無懸念的相同。

參考文檔

https://github.com/kriasoft/nodejs-api-starter
https://github.com/nodejs/node-addon-api/blob/master/doc/node-gyp.md


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大數據分析如今已不能再稱之為新技術。大多數移動應用程式開發人員已經明白,他們需要挖掘他們的數據來積極獲取日常的見解。許多大型應用程式開發企業已經意識到,要在市場上不斷地發展和更新,必須採用大數據技術。亞馬遜,微軟,甲骨文等大型跨國公司已經採用了大數據解決方案來拓展業務,希望為消費者提供最好的服務。科 ...
  • 本文導讀:刪除表中的數據的方法有delete,truncate, 其中TRUNCATE TABLE用於刪除表中的所有行,而不記錄單個行刪除操作。TRUNCATE TABLE 與沒有 WHERE 子句的 DELETE 語句類似;但是,TRUNCATE TABLE 速度更快,使用的系統資源和事務日誌資源 ...
  • Preface MasterHA is a tool which can be used in MySQL HA architecture.I'm gonna implement it and have some tests depend on it later. Framework Hostnam ...
  • 一,效果圖。 二,代碼。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>html基礎</title> </head> <body> <!--HTML標題--> <h1>這是h1標題</h1> <h2>這是h2標題</h2> < ...
  • 目標 BOM編程 window和document對象 window對象的屬性和方法 document對象的屬性和方法 JavaScript中對象的分類 1. 瀏覽器對象:window對象 2. window對象,這個對象的屬性和方法通常被統稱為BOM(Browser Object Model,瀏覽器 ...
  • 來自:http://www.dabaoku.com/texiao/caidan/201007156435.shtml# 大寶庫 css已經改動,但是js似懂非懂,明白大概原理但是實際操作為啥這麼寫還要仔細研究,先拿過來留作自己日常參考(如果有侵權請告知刪除) ...
  • 一、問題 近日,寫了一個ASP.Net項目,但是bootstrap-table在別人的電腦上顯示不出來,在自己的電腦上能顯示,有些瀏覽器也是能顯示,但部分瀏覽器就是顯示不出來。找了很多原因,最後有個老師和我說是內核版本的問題。 核心:雙核瀏覽器雙核主要是谷歌和IE,但如果某些瀏覽器其中有個內核的版本 ...
  • 開發者的javascript造詣取決於對【動態】和【非同步】這兩個詞的理解水平。 [TOC] 一.this是什麼 是javascript關鍵字之一,是javascript能夠實現 面向對象編程 的核心概念。用得好能讓代碼優雅高端,風騷飄逸,用不好也絕對是坑人坑己利器。我們常常會在一些資料中看到對 的描 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...