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