自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇了解Node-Addon-Api的設(shè)計(jì)和實(shí)現(xiàn)

開發(fā) 前端
開發(fā)Nodej.js Addon的方式經(jīng)過不斷地改進(jìn),已經(jīng)非逐步完善,至少我們不需要在升級(jí)Node.js版本的同時(shí)擔(dān)心Addon用不了或者重新編譯。目前Node.js提供的開發(fā)方式是napi。

[[411323]]

本文轉(zhuǎn)載自微信公眾號(hào)「編程雜技」,作者theanarkh 。轉(zhuǎn)載本文請聯(lián)系編程雜技公眾號(hào)。

開發(fā)Nodej.js Addon的方式經(jīng)過不斷地改進(jìn),已經(jīng)非逐步完善,至少我們不需要在升級(jí)Node.js版本的同時(shí)擔(dān)心Addon用不了或者重新編譯。目前Node.js提供的開發(fā)方式是napi。但是napi用起來非常冗余和麻煩,每一步都需要我們自己去控制,所以又有大佬封裝了面向?qū)ο蟀姹镜腶pi(node-addon-api),使用上方便了很多,本文分析一下node-addon-api的設(shè)計(jì)思想,但不會(huì)分析過多細(xì)節(jié),因?yàn)槲覀兝斫饬嗽O(shè)計(jì)思想后,使用時(shí)去查閱文檔或者看源碼就可以。

我們首先看一下使用napi寫一個(gè)hello world的例子。

  1. #include <assert.h> 
  2. #include <node_api.h> 
  3. static napi_value Method(napi_env env, napi_callback_info info) { 
  4.   napi_status status; 
  5.   napi_value world; 
  6.   status = napi_create_string_utf8(env, "world", 5, &world); 
  7.   assert(status == napi_ok); 
  8.   return world; 
  9.  
  10. #define DECLARE_NAPI_METHOD(name, func)                                        \ 
  11.   { name, 0, func, 0, 0, 0, napi_default, 0 } 
  12.  
  13. static napi_value Init(napi_env env, napi_value exports) { 
  14.   napi_status status; 
  15.   napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method); 
  16.   status = napi_define_properties(env, exports, 1, &desc); 
  17.   assert(status == napi_ok); 
  18.   return exports; 
  19.  
  20. NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) 

接著我們看一下node-addon-api版的寫法。

  1. #include <napi.h> 
  2.  
  3. Napi::String Method(const Napi::CallbackInfo& info) { 
  4.   Napi::Env env = info.Env(); 
  5.   return Napi::String::New(env, "world"); 
  6.  
  7. Napi::Object Init(Napi::Env env, Napi::Object exports) { 
  8.   exports.Set(Napi::String::New(env, "hello"), 
  9.               Napi::Function::New(env, Method)); 
  10.   return exports; 
  11.  
  12. NODE_API_MODULE(hello, Init) 

我們看到,代碼簡潔了很多,有點(diǎn)寫js的感覺了。

下面我們看看這些簡潔背后的設(shè)計(jì)。我們從模塊定義開始分析。

  1. NODE_API_MODULE(hello, Init) 

NODE_API_MODULE是node-addon-api定義的宏。

  1. #define NODE_API_MODULE(modname, regfunc)                                      \ 
  2.   static napi_value __napi_##regfunc(napi_env env, napi_value exports) {       \ 
  3.     return Napi::RegisterModule(env, exports, regfunc);                        \ 
  4.   }                                                                            \ 
  5.   NAPI_MODULE(modname, __napi_##regfunc) 

我們看到NODE_API_MODULE是對NAPI_MODULE的封裝,NAPI_MODULE的分析可以參考之前napi原理相關(guān)的文章,這里就不具體分析。最后在加載addon的時(shí)候執(zhí)行__napi_##regfunc函數(shù)。并傳入napi_env env, napi_value exports參數(shù)。我們知道這是napi規(guī)范的參數(shù)。接著執(zhí)行RegisterModule。

  1. inline napi_value RegisterModule(napi_env env, 
  2.                                  napi_value exports, 
  3.                                  ModuleRegisterCallback registerCallback) { 
  4.   // details::WrapCallback里會(huì)執(zhí)行l(wèi)amda函數(shù)并返回lamda的返回值                       
  5.   return details::WrapCallback([&] { 
  6.     return napi_value(registerCallback(Napi::Env(env), 
  7.                                        Napi::Object(env, exports))); 
  8.   }); 

RegisterModule里最終會(huì)執(zhí)行registerCallback。我們看一下registerCallback變量的類型ModuleRegisterCallback的定義。

  1. typedef Object (*ModuleRegisterCallback)(Env env, Object exports); 

所以registerCallback的參數(shù)是Env和Object對象。這兩個(gè)類不是Node.js也不是V8定義的,而是node-addon-api。我們一會(huì)再分析,我們先知道他是兩個(gè)對象就好。這里registerCallback的值是我們定義的Init函數(shù)。

  1. Napi::Object Init(Napi::Env env, Napi::Object exports) { 
  2.   exports.Set(Napi::String::New(env, "hello"), 
  3.               Napi::Function::New(env, Method)); 
  4.   return exports; 

通過Set方法給exports定義屬性,我們在js就可以訪問對應(yīng)的屬性了。最后返回exports,exports是Object類型。但根據(jù)napi的接口定義。返回的類型應(yīng)該是napi_value。我們看看node-addon-api是怎么做的。我們回到RegisterModule函數(shù)。

  1. return napi_value(registerCallback(Napi::Env(env),  Napi::Object(env, exports))); 

我們看到registerCallback執(zhí)行后的返回值會(huì)被轉(zhuǎn)成napi_value類型。那么Object類型是怎么自動(dòng)轉(zhuǎn)成napi_value類型的呢?我們一會(huì)分析。了解了node-addon-api的使用方式后,我們開始具體分析其中的設(shè)計(jì)。

我們先看看Env的設(shè)計(jì)。

  1. class Env { 
  2.   public
  3.     Env(napi_env env); 
  4.     operator napi_env() const; 
  5.  
  6.   private: 
  7.     napi_env _env; 
  8. }; 
  9.  
  10. inline Env::Env(napi_env env) : _env(env) {} 
  11.  
  12. // 類型重載 
  13. inline Env::operator napi_env() const { 
  14.   return _env; 

我們只看核心的設(shè)計(jì),忽略一些無關(guān)重要的細(xì)節(jié)。我們看到Env的設(shè)計(jì)很簡單,就是對napi的napi_env的封裝。接著我們看類型的設(shè)計(jì)。

  1. class Value { 
  2.   public
  3.     Value();      
  4.     Value(napi_env env,  napi_value value);   
  5.     operator napi_value() const; 
  6.     Napi::Env Env() const; 
  7.  
  8.   protected: 
  9.     napi_env _env; 
  10.     napi_value _value; 
  11. }; 

Value是node-addon-api的類型基類,類似V8里的設(shè)計(jì)。我們看到Value里面只有兩個(gè)字段,env和_value。env就是我們剛才提到的Env。_value就是對napi類型的封裝。Value類只是抽象的封裝,不涉及到具體的邏輯。下面我們以自定義的Init函數(shù)為例,開始分析具體的邏輯。

  1. Napi::Object Init(Napi::Env env, Napi::Object exports) { 
  2.   exports.Set(Napi::String::New(env, "hello"),  
  3.               Napi::Function::New(env, Method) 
  4.              ); 
  5.   return exports; 

我們先看看String::New的實(shí)現(xiàn)。

  1. class Name : public Value { 
  2.   public
  3.     Name();                      
  4.     Name(napi_env env, napi_value value);  
  5. }; 
  6.  
  7. class String : public Name { 
  8.   public
  9.    static String New(napi_env env, const char* value); 
  10. }; 
  11.  
  12. inline String String::New(napi_env env, const char* val) { 
  13.       napi_value value; 
  14.       napi_status status = napi_create_string_utf8(env, val, std::strlen(val), &value); 
  15.       NAPI_THROW_IF_FAILED(env, status, String()); 
  16.       return String(env, value); 

我們看到New的實(shí)現(xiàn)很簡單,主要是對napi的封裝。但有些細(xì)節(jié)還是需要注意的。1 我們看到exports.Set函數(shù)的第一個(gè)參數(shù)是Env類型,但是New函數(shù)的第一個(gè)參數(shù)類型是napi_env,看起來不兼容。這個(gè)是如何自動(dòng)轉(zhuǎn)換的呢?因?yàn)镋nv類對napi_env類型進(jìn)行了重載。

  1. inline Env::operator napi_env() const { 
  2.   return _env; 

我們看到當(dāng)需要napi_env類型的時(shí)候,Env會(huì)返回_env,_env就是napi_env類型。2 通過napi接口創(chuàng)建了值之后,最后返回的是一個(gè)String類型。我們看看String構(gòu)造函數(shù)。

  1. inline String::String(napi_env env, napi_value value) : Name(env, value) {} 
  2. inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} 

最后調(diào)用Value構(gòu)造函數(shù)保存了napi返回的值。并且給調(diào)用方返回了一個(gè)String對象。我們看看exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, Method))的時(shí)候是如何使用這個(gè)String對象的。exports是一個(gè)Object。Object和String的實(shí)現(xiàn)是類似的,他們都是繼承Value類,在內(nèi)部封裝了napi_env和napi_value變量。所以我們看看Object::Set的實(shí)現(xiàn)。

  1. template <typename ValueType> 
  2. inline bool Object::Set(napi_value key, const ValueType& value) { 
  3.   napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); 
  4.   NAPI_THROW_IF_FAILED(_env, status, false); 
  5.   return true

_value的值是Object封裝的napi_value對象,也就是一個(gè)V8 Object對象。然后通過napi_set_property設(shè)置對象的屬性和值。同樣我們發(fā)現(xiàn)Set函數(shù)的實(shí)參是String對象,但是型參是napi_value類型。這個(gè)和Env的自動(dòng)轉(zhuǎn)換是類似的,String繼承了Value,而Value重載了類型napi_value。

  1. inline Value::operator napi_value() const { 
  2.   return _value; 

即返回了封裝的napi_value變量。我們通過Set設(shè)置了一個(gè)屬性hello,值是一個(gè)函數(shù)。

  1. Napi::String Method(const Napi::CallbackInfo& info) { 
  2.   Napi::Env env = info.Env(); 
  3.   return Napi::String::New(env, "world"); 

當(dāng)我們在js層調(diào)用hello的時(shí)候,不會(huì)執(zhí)行這個(gè)函數(shù),而是先執(zhí)行node-addon-api的代碼,node-addon-api對napi的變量進(jìn)行封裝后,才會(huì)調(diào)用Method。所以我們看到Method的入?yún)㈩愋秃蚽api的是不一樣的。最后Method執(zhí)行完返回的時(shí)候,同樣是先回到node-addon-api。node-addon-api把Method的返回值(String對象)轉(zhuǎn)成napi的格式后(napi_value)再返回到napi(這里比較復(fù)雜,目前還沒有深入分析)。

至此我們看到了node-addon-api設(shè)計(jì)的基本思想如圖所示。

大致的思想就是node-addon-api為我們封裝了一層,當(dāng)napi調(diào)用我們定義的內(nèi)容時(shí),會(huì)先經(jīng)過node-addon-api。node-addon-api封裝napi的入?yún)⒑笤僬{(diào)用我們自定義的內(nèi)容。同樣,我們返回內(nèi)容給napi時(shí),也會(huì)經(jīng)過node-addon-api的封裝再回到napi。比如我們在addon里創(chuàng)建一個(gè)數(shù)字時(shí), 我們會(huì)執(zhí)行Number New(napi_env env, double value);New會(huì)調(diào)用napi的napi_create_double創(chuàng)建一個(gè)napi_value變量。接著把napi_value的值封裝到Number,最后返回一個(gè)Number給我們,后續(xù)我們調(diào)用Number的其他方法時(shí),node-addon-api會(huì)從Number對象中拿到保存napi_value的值,再調(diào)用napi的api。這樣我們只需要面對node-addon-api提供的接口而不需要理解napi。另外node-addon-api還做了一些運(yùn)算符重載使得我們寫代碼更容易。比如對Object []的重載。

  1. Value operator []( const char* utf8name) const; 

我們看看實(shí)現(xiàn)。

  1. inline Value Object::operator [](const char* utf8name) const { 
  2.   return Get(utf8name); 
  3.  
  4. inline Value Object::Get(const char* utf8name) const { 
  5.   napi_value result; 
  6.   napi_status status = napi_get_named_property(_env, _value, utf8name, &result); 
  7.   NAPI_THROW_IF_FAILED(_env, status, Value()); 
  8.   return Value(_env, result); 

這樣我們就可以通過obj['name']這種方式訪問對象了。否則我們還需要像下面的方式訪問。

  1. napi_value value; 
  2. napi_status status = napi_get_named_property(_env, _value, key, &value); 

如果大量這樣的代碼將會(huì)非常麻煩和低效。另外node-addon-api對類型進(jìn)行了大量的重載,使得變量的類型轉(zhuǎn)換得以自動(dòng)進(jìn)行不需要強(qiáng)制轉(zhuǎn)換來轉(zhuǎn)換去。比如我們可以直接執(zhí)行以下代碼。

  1. int32_t num = Number對象; 

因?yàn)镹umber對int32_t進(jìn)行了重載。

  1. inline Number::operator int32_t() const { 
  2.   return Int32Value(); 
  3.  
  4. inline int32_t Number::Int32Value() const { 
  5.   int32_t result; 
  6.   napi_status status = napi_get_value_int32(_env, _value, &result); 
  7.   NAPI_THROW_IF_FAILED(_env, status, 0); 
  8.   return result; 

后記:本文大致分析了node-addon-api的實(shí)現(xiàn)原理和思想,實(shí)現(xiàn)的代碼將近萬行,雖然有很多類似的邏輯,但是也有些比較復(fù)雜的封裝,有興趣的同學(xué)可自行閱讀。.

 

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2021-11-24 08:51:32

Node.js監(jiān)聽函數(shù)

2021-07-03 08:04:10

io_uringNode.js異步IO

2021-12-30 09:38:51

DDoS攻擊防范

2022-12-19 08:14:30

注解開發(fā)配置

2022-10-26 07:39:36

MVCC數(shù)據(jù)庫RR

2021-05-20 06:57:16

RabbitMQ開源消息

2022-05-06 23:03:48

V8CPUProfiler

2021-08-11 07:02:21

npm包管理器工具

2022-05-06 13:30:56

TDD場景代碼

2023-10-17 08:15:28

API前后端分離

2021-07-14 10:08:30

責(zé)任鏈模式加工鏈

2021-07-10 09:02:42

編程語言 TypeScript

2021-10-28 19:15:02

IPUARM

2020-10-09 08:15:11

JsBridge

2021-07-16 04:56:03

NodejsAddon

2021-07-02 08:51:28

Vite線程項(xiàng)目

2022-02-18 08:54:21

docker操作系統(tǒng)Linux

2022-05-05 07:40:07

maskCSS

2023-05-12 08:19:12

Netty程序框架

2021-07-14 08:24:23

TCPIP 通信協(xié)議
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)