JavaScript >> Javascript 文檔 >  >> JavaScript

如何在 Node 中創建 C/C++ 插件

Node.js 之所以偉大有很多原因,其中之一是您可以構建有意義的應用程序的速度。然而,眾所周知,這是以性能為代價的(與本機代碼相比)。為了解決這個問題,您可以編寫代碼以與用 C 或 C++ 編寫的更快的代碼交互。我們需要做的就是讓 Node 知道在哪裡可以找到這段代碼以及如何與之交互。

根據您想要的抽象級別,有幾種方法可以解決此問題。我們將從最低的抽像開始,即 Node Addon。

插件

插件通過提供 Node 和 C/C++ 庫之間的粘合劑來工作。對於典型的 Node 開發人員來說,這可能有點複雜,因為您必須實際編寫 C/C++ 代碼來設置接口。但是,在本文和 Node 文檔之間,您應該能夠讓一些簡單的接口正常工作。

在開始創建插件之前,我們需要完成一些事情。首先,我們需要知道如何編譯(Node 開發人員樂於忘記的)本機代碼。這是使用 node-gyp 完成的。然後,我們將簡要討論 nan,它有助於處理不同的 Node API 版本。

節點-gyp

那裡有許多不同類型的處理器(x86、ARM、PowerPC 等),甚至在編譯代碼時需要處理更多的操作系統。幸運的是,node-gyp 為您處理所有這些。正如他們的 Github 頁面所述,node-gyp 是“用 Node.js 編寫的用於為 Node.js 編譯本機插件模塊的跨平台命令行工具”。本質上,node-gyp 只是 gyp 的一個封裝,由 Chromium 團隊製作。

該項目的自述文件有一些關於如何安裝和使用包的很好的說明,所以你應該閱讀它以獲得更多詳細信息。總之,要使用 node-gyp 您需要執行以下操作。

進入你的項目目錄:

$ cd my_node_addon

使用 configure 生成適當的構建文件 命令,它將創建一個 Makefile (在 Unix 上)或 vcxproj (在 Windows 上):

$ node-gyp configure

最後,構建項目:

$ node-gyp build

這將生成一個 /build 目錄,其中包含已編譯的二進製文件。

即使使用像 ffi 這樣的更高抽象層 包,了解幕後發生的事情仍然很好,所以我建議您花時間了解 node-gyp 的來龍去脈 .

nan (Native Abstractions for Node) 是一個容易被忽視的模塊,但它會為您節省數小時的挫敗感。 Node 版本之間 v0.8 , v0.10 , 和 v0.12 ,使用的 V8 版本經歷了一些大的變化(除了 Node 本身的變化),所以 nan 有助於對您隱藏這些更改,並提供一個漂亮、一致的界面。

這種原生抽象通過在 #include <nan.h> 中提供 C/C++ 對象/函數來工作 頭文件。

要使用它,請安裝 nan 包裝:

$ npm install --save nan

將這些行添加到您的 binding.gyp 文件中:

"include_dirs" : [ 
    "<!(node -e \"require('nan')\")"
]

你已經準備好使用 nan.h 中的方法/函數了 在你的鉤子裡,而不是原來的 #include <node.h> 代碼。我強烈建議您使用 nan .在這種情況下,重新發明輪子沒有多大意義。

創建插件

在開始使用插件之前,請確保您花一些時間熟悉以下庫:

  • V8 JavaScript C++ 庫,用於實際與 JavaScript 交互(如創建函數、調用對像等)。
    • 注意 :node.h 是建議的默認文件,但實際上是 nan.h 應該改用
  • libuv,一個用 C 語言編寫的跨平台異步 I/O 庫。該庫對於在本機中執行任何類型的 I/O(打開文件、寫入網絡、設置計時器等)非常有用庫,您需要使其異步。
  • 內部節點庫。要理解的更重要的對象之一是 node::ObjectWrap ,大多數對像都是從它派生的。

在本節的其餘部分,我將引導您完成一個實際示例。在這種情況下,我們將為 C++ <cmath> 創建一個掛鉤 庫的 pow 功能。因為你應該幾乎總是使用 nan ,這就是我將在整個示例中使用的內容。

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

對於這個例子,在你的插件項目中你應該至少有這些文件:

  • pow.cpp
  • binding.gyp
  • package.json

C++ 文件不需要命名為 pow.cpp ,但名稱通常反映它是一個插件,或者它的特定功能。

// pow.cpp
#include <cmath>
#include <nan.h>

void Pow(const Nan::FunctionCallbackInfo<v8::Value>& info) {

	if (info.Length() < 2) {
		Nan::ThrowTypeError("Wrong number of arguments");
		return;
	}

	if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
		Nan::ThrowTypeError("Both arguments should be numbers");
		return;
	}

	double arg0 = info[0]->NumberValue();
	double arg1 = info[1]->NumberValue();
	v8::Local<v8::Number> num = Nan::New(pow(arg0, arg1));

	info.GetReturnValue().Set(num);
}

void Init(v8::Local<v8::Object> exports) {
	exports->Set(Nan::New("pow").ToLocalChecked(),
				 Nan::New<v8::FunctionTemplate>(Pow)->GetFunction());
}

NODE_MODULE(pow, Init)

注意沒有分號 (; ) 在 NODE_MODULE 的末尾 .這是有意完成的,因為 NODE_MODULE 實際上不是一個函數 - 它是一個宏。

對於那些有一段時間(或曾經)沒有寫過任何 C++ 的人來說,上面的代碼一開始可能看起來有點令人生畏,但它確實並不難理解。 Pow 函數是代碼的核心,我們檢查傳遞的參數數量、參數類型、調用原生 pow 函數,並將結果返回給 Node 應用程序。 info object 包含我們需要知道的調用的所有內容,包括參數(及其類型)和返回結果的位置。

Init 函數大多只是關聯 Pow 具有
“pow”名稱和NODE_MODULE的函數 宏實際上處理向 Node 註冊插件。

package.json 文件與普通的 Node 模塊沒有太大區別。雖然它似乎不是必需的,但大多數插件模塊都有 "gypfile": true 在其中設置,但沒有它,構建過程似乎仍然可以正常工作。這是我用於此示例的內容:

{
  "name": "addon-hook",
  "version": "0.0.0",
  "description": "Node.js Addon Example",
  "main": "index.js",
  "dependencies": {
    "nan": "^2.0.0"
  },
  "scripts": {
    "test": "node index.js"
  }
}

接下來,需要將此代碼構建到“pow.node”文件中,該文件是插件的二進製文件。為此,您需要告訴 node-gyp 它需要編譯哪些文件以及二進製文件的結果文件名。雖然您可以使用許多其他選項/配置與 node-gyp ,對於這個例子,我們不需要很多。 binding.gyp 文件可以很簡單:

{
	"targets": [
		{
			"target_name": "pow",
			"sources": [ "pow.cpp" ],
			"include_dirs": [
				"<!(node -e \"require('nan')\")"
			]
		}
	]
}

現在,使用 node-gyp ,為給定平台生成合適的項目構建文件:

$ node-gyp configure

最後,構建項目:

$ node-gyp build

這應該會導致 pow.node 正在創建的文件,該文件將駐留在 build/Release/ 目錄。要在您的應用程序代碼中使用此掛鉤,只需 requirepow.node 文件(沒有“.node”擴展名):

var addon = require('./build/Release/pow');

console.log(addon.pow(4, 2));		// Prints '16'

節點外部函數接口

注意 :ffi 包以前稱為 node-ffi .請務必添加較新的 ffi 為您的依賴項命名以避免在 npm install 期間出現很多混淆 :)

雖然 Node 提供的插件功能為您提供了所需的所有靈活性,但並非所有開發人員/項目都需要它。在許多情況下,像 ffi 這樣的抽象 會做的很好,通常需要很少甚至不需要 C/C++ 編程。

ffi 只加載動態庫,這可能會限制某些人,但它也使掛鉤更容易設置。

var ffi = require('ffi');

var libm = ffi.Library('libm', {
	'pow': [ 'double', [ 'double', 'double' ] ]
});

console.log(libm.pow(4, 2));	// 16

上面的代碼通過指定要加載的庫 (libm) 來工作,特別是從該庫 (pow) 加載哪些方法。 [ 'double', [ 'double', 'double' ] ] 行告訴 ffi 方法的返回類型和參數是什麼,在本例中是兩個double 參數和一個 double 回來了。

結論

雖然一開始可能看起來很嚇人,但在您有機會自己完成一個像這樣的小例子之後,創建一個插件真的不是太糟糕。如果可能的話,我建議掛鉤到一個動態庫,以使創建界面和加載代碼更加容易,儘管對於許多項目來說這可能是不可能的或最佳選擇。

是否有任何您想查看綁定的庫示例?請在評論中告訴我們!


Tutorial JavaScript 教程
  1. Angular 10 升級報告

  2. 在原生 javascript 中創建一個可拖動的 div

  3. html 5元素上的自定義表單驗證功能

  4. 使用 Vue.js 和 Firebase 雲函數實現無服務器

  5. 我編寫樣式化組件的 3 種不同方法

  6. HTML5 和 CSS3:探索移動可能性——倫敦 Ajax 移動活動上的演示

  7. 不要優化您的 React 應用程序,而是使用 Preact

  1. 使用單例變體的抽象和類型安全

  2. 學習折疊 JS 數組

  3. 作為即將到來的 Web 開發人員,您應該在 UDEMY 上查看的導師

  4. 隨著時間的推移,正確的想法會變成錯誤的想法

  5. 使用 HTML5 約束 API 進行表單驗證

  6. HTML5 視頻 RTSP 直播流

  7. 我如何在網站導航中處理鍵盤可訪問性

  1. 將 DApp 部署到 Heroku:部分 (4/4)

  2. .Net Core 基於策略的授權與 Auth0

  3. [第 3 部分] 使用 GraphQL、Typescript 和 React 創建 Twitter 克隆(用戶註冊)

  4. React.js 方式:入門教程