如何写一个简单的node.js C 扩展
node 是由 c 編寫的,核心的 node 模塊也都是由 c 代碼來實現,所以同樣 node 也開放了讓使用者編寫 c 擴展來實現一些操作的窗口。
如果大家對于 require 函數的描述還有印象的話,就會記得如果不寫文件后綴,它是有一個特定的匹配規則的:
LOAD_AS_FILE(X)1.?If?X?is?a?file,?load?X?as?its?file?extension?format.?STOP2.?If?X.js?is?a?file,?load?X.js?as?javascript?text.?STOP3.?If?X.json?is?a?file,?parse?X.json?to?a?javascript?object.?STOP4.?If?X.node?is?a?file,?load?X.node?as?binary?addon.?STOP可以看到,最后會匹配一個?.node,而后邊的描述也表示該后綴的文件為一個二進制的資源。
而這個?.node?文件一般就會是我們所編譯好的 c 擴展了。
為什么要寫 c 擴展
可以簡單理解為,如果想基于 node 寫一些代碼,做一些事情,那么有這么幾種選擇:
1. 寫一段 JS 代碼,然后 require 執行
2. 寫一段 c 代碼,編譯后 require 執行
3. 打開 node 源碼,把你想要的代碼寫進去,然后重新編譯
日常的開發其實只用第一項就夠了,我們用自己熟悉的語言,寫一段熟悉的代碼,然后發布在 NPM 之類的平臺上,其他有相同需求的人就可以下載我們上傳的包,然后在TA的項目中使用。
但有的時候可能純粹寫 JS 滿足不了我們的需求,也許是工期趕不上,也許是執行效率不讓人滿意,也有可能是語言限制。
所以我們會采用直接編寫一些 c 代碼,來創建一個 c 擴展讓 node 來加載并執行。
況且如果已經有了 c 版本的輪子,我們通過擴展的方式來調用執行而不是自己從頭實現一套,也是避免重復造輪子的方法。
一個簡單的例子,如果大家接觸過 webpack 并且用過 sass 的話,那么在安裝的過程中很可能會遇到各種各樣的報錯問題,也許會看到 gyp 的關鍵字,其實原因就是 sass 內部有使用一些 c 擴展來輔助完成一些操作,而 gyp 就是用來編譯 c 擴展的一種工具。
當然,上邊也提到了還有第三種操作方法,我們可以直接魔改 node 源碼,但是如果你只是想要寫一些原生 JS 實現起來沒有那么美好的模塊,那么是沒有必要去魔改源碼的,畢竟改完了以后還要編譯,如果其他人需要用你的邏輯,還需要安裝你所編譯好的特殊版本。
這樣的操作時很不易于傳播的,大家不會想使用 sass 就需要安裝一個 sass 版本的 node 吧。
就像為了看星戰還要專門下載一個優酷- -。
簡單總結一下,寫 c 的擴展大概有這么幾個好處:
1. 可以復用 node 的模塊管理機制
2. 有比 JS 更高效的執行效率
3. 有更多的 c 版本的輪子可以拿來用
怎么去寫一個簡單的擴展
node 從問世到現在已經走過了 11 年,通過早期的資料、博客等各種信息渠道可以看到之前開發一個 c 擴展并不是很容易,但經過了這么些年迭代,各種大佬們的努力,我們再去編寫一個 c 擴展已經是比較輕松的事情了。
這里直入正題,放出今天比較關鍵的一個工具:node-addon-api module
以及這里是官方提供的各種簡單 demo 來讓大家熟悉這是一個什么樣的工具:node-addon-examples。
需要注意的一點是, demo 目錄下會分為三個子目錄,在 readme 中也有寫,分別是三種不同的 c 擴展的寫法(基于不同的工具)。
我們本次介紹的是在 node-addon-api 目錄下的,算是三種里邊最為易用的一種了。
首先是我們比較熟悉的 package.json 文件,我們需要依賴兩個組件來完成開發,分別是 bindings 和 node-addon-api。
然后我們還需要簡單了解一下 gyp 的用法,因為編譯一個 c 擴展需要用到它。
就像 helloworld 示例中的 binding.gyp 文件示例:
{??"targets":?[????{??????//?導出的文件名??????"target_name":?"hello",??????//?編譯標識的定義?禁用異常機制(注意感嘆號表示排除過濾)??????"cflags!":?[?"-fno-exceptions"?],??????//?c ?編譯標識的定義?禁用異常機制(注意感嘆號表示排除過濾,也就是?c ?編譯器會去除該標識)??????"cflags_cc!":?[?"-fno-exceptions"?],??????//?源碼入口文件??????"sources":?[?"hello.cc"?],??????//?源碼包含的目錄??????"include_dirs":?[????????//?這里表示一段?shell?的運行,用來獲取?node-addon-api?的一些參數,有興趣的老鐵可以自行?node?-p?"require('node-addon-api').include"?來看效果????????"
gyp 的語法挺多的,這次并不是單獨針對 gyp 的一次記錄,所以就不過多的介紹。
從最簡單的數字相加來實現
然后我們來實現一個簡單的創建一個函數,讓兩個參數相加,并返回結果。
源碼位置:https://github.com/Jiasm/node...我們需要這樣的一個 binding.gyp 文件:
{??"targets":?[????{??????"target_name":?"add",??????"cflags!":?[?"-fno-exceptions"?],??????"cflags_cc!":?[?"-fno-exceptions"?],??????"sources":?[?"add.cc"?],??????"include_dirs":?[????????"
然后我們在項目根目錄創建 package.json 文件,并安裝 bindings 和 node-addon-api 兩個依賴。
接下來就是去編寫我們的 c 代碼了:
#include // 定義 Add 函數Napi::Value Add(const Napi::CallbackInfo& info) { ?Napi::Env env = info.Env();// 接收第一個參數 ?double arg0 = info[0].As<:number>().DoubleValue(); ?// 接收第二個參數 ?double arg1 = info[1].As<:number>().DoubleValue(); ?// 將兩個參數相加并返回 ?Napi::Number num = Napi::Number::New(env, arg0 arg1);return num;} // 入口函數,用于注冊我們的函數、對象等等Napi::object Init(Napi::Env env, Napi::object exports) { ?// 將一個名為 add 的函數掛載到 exports 上 ?exports.Set(Napi::String::New(env, "add"), Napi::Function::New(env, Add)); ?return exports;} // 固定的宏使用NODE_API_MODULE(addon, Init)在 c 代碼完成以后就是需要用到 node-gyp 的時候了,建議全局安裝 node-gyp,避免一個項目中出現多個 node_modules 目錄的時候使用 npx 會出現一些不可預料的問題:
>?npm?i?-g?node-gyp#?生成構建文件>?node-gyp?configure#?構建>?node-gyp?build這時候你會發現項目目錄下已經生成了一個名為 add.node 的文件,就是我們在 binding.gyp 里邊的 target_name 所設置的值了。
總結
以上是生活随笔為你收集整理的如何写一个简单的node.js C 扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑usb接口全部失灵(电脑usb接口全
- 下一篇: 晚安好梦什么意思 晚安好梦的意思