溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

nodejs源碼分析中c++層的通用邏輯是什么

發(fā)布時間:2021-11-25 15:41:00 來源:億速云 閱讀:122 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要講解了“nodejs源碼分析中c++層的通用邏輯是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“nodejs源碼分析中c++層的通用邏輯是什么”吧!

我們知道nodejs分為js、c++、c三層,本文以tcp_wrap.cc為例子分析c++層實現(xiàn)的一些通用邏輯。nodejs的js和c++通信原理q.com/s?__biz=MzUyNDE2OTAwNw==&mid=2247484815&idx=1&sn=525d9909c35eabf3c728b303d27061df&chksm=fa303fcfcd47b6d9604298d0996414a5e16c798c1a2dab4e01989bb41ba9c5372ebc00ca0943&token=162783191&lang=zh_CN#rd)之前已經(jīng)分析過,所以直接從tcp模塊導出的功能開始分析(Initialize函數(shù))。

void TCPWrap::Initialize(Local<Object> target,
                         Local<Value> unused,
                         Local<Context> context) {
                         Environment* env = Environment::GetCurrent(context);
  /*
    new TCP時,v8會新建一個c++對象(根據(jù)InstanceTemplate()模板創(chuàng)建的對象),然后傳進New函數(shù),
    然后執(zhí)行New函數(shù),New函數(shù)的入?yún)rgs的args.This()就是該c++對象
  */
  // 新建一個函數(shù)模板
  Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
  // 設置函數(shù)名稱
  Local<String> tcpString = FIXED_ONE_BYTE_STRING(env->isolate(), "TCP");
  t->SetClassName(tcpString);
  /*
      ObjectTemplateInfo對象的kDataOffset偏移保存了這個字段的值,
      用于聲明ObjectTemplateInfo創(chuàng)建的對象額外申請的內存大小
  */
  t->InstanceTemplate()->SetInternalFieldCount(1);

  // 設置對象模板創(chuàng)建的對象的屬性。ObjectTemplateInfo對象的kPropertyListOffset偏移保存了下面這些值
  t->InstanceTemplate()->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "reading"),
                               Boolean::New(env->isolate(), false));

  // 在t的原型上增加屬性
  env->SetProtoMethod(t, "bind", Bind);
  env->SetProtoMethod(t, "connect", Connect);
  // 在target中注冊該函數(shù)
  target->Set(tcpString, t->GetFunction());
 

這里只摘取了部分的代碼 ,因為我們只關注原理,這里分別涉及到函數(shù)模板對象模板和函數(shù)原型等內容。上面的代碼以js來表示如下:

function TCP() {
    this.reading = false;
    // 對應SetInternalFieldCount(1)
    this.point = null;
    // 對應env->NewFunctionTemplate(New);
    New({
        Holder: this, 
        This: this,
        returnValue: {},
        ...
    });
}
TCP.prototype.bind = Bind;
TCP.prototype.connect = Connect;
 

通過上面的定義,完成了c++模塊功能的導出,借助nodejs的機制,我們就可以在js層調用TCP函數(shù)。

const { TCP, constants: TCPConstants } = process.binding('tcp_wrap');
const instance = new TCP(...);
instance.bind(...);
 

我們先分析執(zhí)行new TCP()的邏輯,然后再分析bind的邏輯,因為這兩個邏輯涉及的機制是其他c++模塊也會使用到的。因為TCP對應的函數(shù)是Initialize函數(shù)里的t->GetFunction()對應的值。所以new TCP()的時候,v8首先會創(chuàng)建一個c++對象(內容由Initialize函數(shù)里定義的那些,也就是文章開頭的那段代碼的定義)。然后執(zhí)行回調New函數(shù)。

// 執(zhí)行new TCP時執(zhí)行
void TCPWrap::New(const FunctionCallbackInfo<Value>& args) {
  // 是否以構造函數(shù)的方式執(zhí)行,即new TCP
  CHECK(args.IsConstructCall());
  CHECK(args[0]->IsInt32());
  Environment* env = Environment::GetCurrent(args);

  // 忽略一些不重要的邏輯

  /*
    args.This()為v8提供的一個c++對象(由Initialize函數(shù)定義的模塊創(chuàng)建的)
    調用該c++對象的SetAlignedPointerInInternalField(0,this)關聯(lián)this(new TCPWrap()),
    見HandleWrap
  */
  new TCPWrap(env, args.This(), provider);
}
 

我們看到New函數(shù)的邏輯很簡單。直接調用new TCPWrap,其中第二個入?yún)rgs.This()就是由Initialize函數(shù)定義的函數(shù)模板創(chuàng)建出來的對象。我們繼續(xù)看new TCPWrap()。

TCPWrap::TCPWrap(Environment* env, 
                Local<Object> object, 
                ProviderType provider)
    : ConnectionWrap(env, object, provider) {
  int r = uv_tcp_init(env->event_loop(), &handle_);
}
 

構造函數(shù)只有一句代碼,該代碼是初始化一個結構體,我們可以不關注,我們需要關注的是父類ConnectionWrap的邏輯。

template <typename WrapType, typename UVType>
ConnectionWrap<WrapType, UVType>::ConnectionWrap(Environment* env,
                                                 Local<Object> object,
                                                 ProviderType provider)
    : LibuvStreamWrap(env,
                      object,
                      reinterpret_cast<uv_stream_t*>(&handle_),
                      provider) {}
 

我們發(fā)現(xiàn)ConnectionWrap也沒有什么邏輯,繼續(xù)看LibuvStreamWrap。

LibuvStreamWrap::LibuvStreamWrap(Environment* env,
                                 Local<Object> object,
                                 uv_stream_t* stream,
                                 AsyncWrap::ProviderType provider)
    : HandleWrap(env,
                 object,
                 reinterpret_cast<uv_handle_t*>(stream),
                 provider),
      StreamBase(env),
      stream_(stream) {
}
 

繼續(xù)做一些初始化,我們只關注HandleWrap

HandleWrap::HandleWrap(Environment* env,
                       Local<Object> object,
                       uv_handle_t* handle,
                       AsyncWrap::ProviderType provider)
    : AsyncWrap(env, object, provider),
      state_(kInitialized),
      handle_(handle) {
  // 把子類對象掛載到handle的data字段上
  handle_->data = this;
  HandleScope scope(env->isolate());
  // 關聯(lián)object和this對象,后續(xù)通過unwrap使用
  Wrap(object, this);
  // 入隊
  env->handle_wrap_queue()->PushBack(this);
}
 

重點來了,就是Wrap函數(shù)。

template <typename TypeName>
void Wrap(v8::Local<v8::Object> object, TypeName* pointer) {
  object->SetAlignedPointerInInternalField(0, pointer);
}

void v8::Object::SetAlignedPointerInInternalField(int index, void* value) {
  i::Handle<i::JSReceiver> obj = Utils::OpenHandle(this);
  i::Handle<i::JSObject>::cast(obj)->SetEmbedderField(
      index, EncodeAlignedAsSmi(value, location));
}

void JSObject::SetEmbedderField(int index, Smi* value) {
  // GetHeaderSize為對象固定布局的大小,kPointerSize * index為拓展的內存大小,根據(jù)索引找到對應位置
  int offset = GetHeaderSize() + (kPointerSize * index);
  // 寫對應位置的內存,即保存對應的內容到內存
  WRITE_FIELD(this, offset, value);
}
 

Wrap函數(shù)展開后,做的事情就是把一個值保存到v8 c++對象的內存里。那保存的這個值是啥呢?我們看Wrap函數(shù)的入?yún)rap(object, this)。object是由函數(shù)模板創(chuàng)建的對象,this是一個TCPWrap對象。所以Wrap函數(shù)做的事情就是把一個TCPWrap對象保存到一個函數(shù)模板創(chuàng)建的對象里。這有啥用呢?我們繼續(xù)分析。這時候new TCP就執(zhí)行完畢了。我們看看這時候執(zhí)行new TCP().bind()函數(shù)的邏輯。

void TCPWrap::Bind(const FunctionCallbackInfo<Value>& args) {
  TCPWrap* wrap;
  // 解包處理
  ASSIGN_OR_RETURN_UNWRAP(&wrap,
                          args.Holder(),
                          args.GetReturnValue().Set(UV_EBADF));
  node::Utf8Value ip_address(args.GetIsolate(), args[0]);
  int port = args[1]->Int32Value();
  sockaddr_in addr;
  int err = uv_ip4_addr(*ip_address, port, &addr);
  if (err == 0) {
    err = uv_tcp_bind(&wrap->handle_,
                      reinterpret_cast<const sockaddr*>(&addr),
                      0);
  }
  args.GetReturnValue().Set(err);
}
 

我們只需關系ASSIGN_OR_RETURN_UNWRAP宏的邏輯。其中args.Holder()表示Bind函數(shù)的屬主,根據(jù)前面的分析我們知道屬主是Initialize函數(shù)定義的函數(shù)模板創(chuàng)建出來的對象。這個對象保存了一個TCPWrap對象。我們展開ASSIGN_OR_RETURN_UNWRAP看看。

#define ASSIGN_OR_RETURN_UNWRAP(ptr, obj, ...)                                \
  do {                                                                        \
    *ptr =                                                                    \
        Unwrap<typename node::remove_reference<decltype(**ptr)>::type>(obj);  \
    if (*ptr == nullptr)                                                      \
      return __VA_ARGS__;                                                     \
  } while (0)


template <typename TypeName>
TypeName* Unwrap(v8::Local<v8::Object> object) {
  // 把調用SetAlignedPointerFromInternalField設置的值取出來
  void* pointer = object->GetAlignedPointerFromInternalField(0);
  return static_cast<TypeName*>(pointer);
}

展開后我們看到,主要的邏輯是把在c++對象中保存的那個TCPWrap對象取出來。然后就可以使用TCPWrap對象了。

感謝各位的閱讀,以上就是“nodejs源碼分析中c++層的通用邏輯是什么”的內容了,經(jīng)過本文的學習后,相信大家對nodejs源碼分析中c++層的通用邏輯是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI