成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇了解Node-Addon-Api的設計和實現

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

[[411323]]

本文轉載自微信公眾號「編程雜技」,作者theanarkh 。轉載本文請聯系編程雜技公眾號。

開發Nodej.js Addon的方式經過不斷地改進,已經非逐步完善,至少我們不需要在升級Node.js版本的同時擔心Addon用不了或者重新編譯。目前Node.js提供的開發方式是napi。但是napi用起來非常冗余和麻煩,每一步都需要我們自己去控制,所以又有大佬封裝了面向對象版本的api(node-addon-api),使用上方便了很多,本文分析一下node-addon-api的設計思想,但不會分析過多細節,因為我們理解了設計思想后,使用時去查閱文檔或者看源碼就可以。

我們首先看一下使用napi寫一個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) 

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

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

  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原理相關的文章,這里就不具體分析。最后在加載addon的時候執行__napi_##regfunc函數。并傳入napi_env env, napi_value exports參數。我們知道這是napi規范的參數。接著執行RegisterModule。

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

RegisterModule里最終會執行registerCallback。我們看一下registerCallback變量的類型ModuleRegisterCallback的定義。

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

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

  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就可以訪問對應的屬性了。最后返回exports,exports是Object類型。但根據napi的接口定義。返回的類型應該是napi_value。我們看看node-addon-api是怎么做的。我們回到RegisterModule函數。

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

我們看到registerCallback執行后的返回值會被轉成napi_value類型。那么Object類型是怎么自動轉成napi_value類型的呢?我們一會分析。了解了node-addon-api的使用方式后,我們開始具體分析其中的設計。

我們先看看Env的設計。

  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; 

我們只看核心的設計,忽略一些無關重要的細節。我們看到Env的設計很簡單,就是對napi的napi_env的封裝。接著我們看類型的設計。

  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里的設計。我們看到Value里面只有兩個字段,env和_value。env就是我們剛才提到的Env。_value就是對napi類型的封裝。Value類只是抽象的封裝,不涉及到具體的邏輯。下面我們以自定義的Init函數為例,開始分析具體的邏輯。

  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的實現。

  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的實現很簡單,主要是對napi的封裝。但有些細節還是需要注意的。1 我們看到exports.Set函數的第一個參數是Env類型,但是New函數的第一個參數類型是napi_env,看起來不兼容。這個是如何自動轉換的呢?因為Env類對napi_env類型進行了重載。

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

我們看到當需要napi_env類型的時候,Env會返回_env,_env就是napi_env類型。2 通過napi接口創建了值之后,最后返回的是一個String類型。我們看看String構造函數。

  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) {} 

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

  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對象,也就是一個V8 Object對象。然后通過napi_set_property設置對象的屬性和值。同樣我們發現Set函數的實參是String對象,但是型參是napi_value類型。這個和Env的自動轉換是類似的,String繼承了Value,而Value重載了類型napi_value。

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

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

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

當我們在js層調用hello的時候,不會執行這個函數,而是先執行node-addon-api的代碼,node-addon-api對napi的變量進行封裝后,才會調用Method。所以我們看到Method的入參類型和napi的是不一樣的。最后Method執行完返回的時候,同樣是先回到node-addon-api。node-addon-api把Method的返回值(String對象)轉成napi的格式后(napi_value)再返回到napi(這里比較復雜,目前還沒有深入分析)。

至此我們看到了node-addon-api設計的基本思想如圖所示。

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

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

我們看看實現。

  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); 

如果大量這樣的代碼將會非常麻煩和低效。另外node-addon-api對類型進行了大量的重載,使得變量的類型轉換得以自動進行不需要強制轉換來轉換去。比如我們可以直接執行以下代碼。

  1. int32_t num = Number對象; 

因為Number對int32_t進行了重載。

  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的實現原理和思想,實現的代碼將近萬行,雖然有很多類似的邏輯,但是也有些比較復雜的封裝,有興趣的同學可自行閱讀。.

 

責任編輯:武曉燕 來源: 編程雜技
相關推薦

2021-11-24 08:51:32

Node.js監聽函數

2021-07-03 08:04:10

io_uringNode.js異步IO

2021-12-30 09:38:51

DDoS攻擊防范

2022-10-26 07:39:36

MVCC數據庫RR

2022-12-19 08:14:30

注解開發配置

2021-05-20 06:57:16

RabbitMQ開源消息

2021-08-11 07:02:21

npm包管理器工具

2022-05-06 23:03:48

V8CPUProfiler

2022-05-06 13:30:56

TDD場景代碼

2021-10-28 19:15:02

IPUARM

2021-07-10 09:02:42

編程語言 TypeScript

2021-07-14 10:08:30

責任鏈模式加工鏈

2020-10-09 08:15:11

JsBridge

2023-10-17 08:15:28

API前后端分離

2021-07-02 08:51:28

Vite線程項目

2022-02-18 08:54:21

docker操作系統Linux

2022-05-05 07:40:07

maskCSS

2021-07-16 04:56:03

NodejsAddon

2023-05-12 08:19:12

Netty程序框架

2021-07-28 10:02:54

建造者模式代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产成人av在线播放 | 91激情视频 | 精品亚洲一区二区三区 | 黄色在线| 欧美在线观看黄色 | 女人精96xxx免费网站p | caoporn国产精品免费公开 | 国产一区二区麻豆 | 国产视频精品在线 | 亚洲视频在线观看免费 | 国产午夜精品久久久 | 久久久美女 | 日日天天 | 一级黄色片一级黄色片 | 欧美激情一区二区三区 | 精品视频一区二区三区 | 黄色在线免费观看视频 | 狠狠夜夜 | 欧美最猛黑人 | 91精品综合久久久久久五月天 | 久久久久久久久综合 | 荷兰欧美一级毛片 | www.国产精| 国产伦精品 | 九九一级片 | 在线观看日本高清二区 | 久久成人免费视频 | 国产精品一区一区 | 国产一二三区免费视频 | 欧美日韩高清在线一区 | 日本三级网站在线观看 | 国产精品久久国产精品 | 亚洲第一中文字幕 | 看av片网站 | 天天干天天干 | 波多野结衣av中文字幕 | 欧美激情在线一区二区三区 | 亚洲日本中文 | 91成人小视频 | 国产精品久久久久久影视 | 综合久|