nodejs源碼分析之c++層的通用邏輯

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

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

  // 設置對象模板建立的對象的屬性。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中註冊該函數
  target->Set(tcpString, t->GetFunction());

這裏只摘取了部分的代碼 ,由於咱們只關注原理,這裏分別涉及到函數模板對象模板和函數原型等內容。上面的代碼以js來表示以下:c++

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函數。web

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

咱們先分析執行new TCP()的邏輯,而後再分析bind的邏輯,由於這兩個邏輯涉及的機制是其餘c++模塊也會使用到的。由於TCP對應的函數是Initialize函數裏的t->GetFunction()對應的值。因此new TCP()的時候,v8首先會建立一個c++對象(內容由Initialize函數裏定義的那些,也就是文章開頭的那段代碼的定義)。而後執行回調New函數。編程

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

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

  /*
    args.This()爲v8提供的一個c++對象(由Initialize函數定義的模塊建立的)
    調用該c++對象的SetAlignedPointerInInternalField(0,this)關聯this(new TCPWrap()),
    見HandleWrap
  */
  new TCPWrap(env, args.This(), provider);
}

咱們看到New函數的邏輯很簡單。直接調用new TCPWrap,其中第二個入參args.This()就是由Initialize函數定義的函數模板建立出來的對象。咱們繼續看new TCPWrap()。微信

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

構造函數只有一句代碼,該代碼是初始化一個結構體,咱們能夠不關注,咱們須要關注的是父類ConnectionWrap的邏輯。tcp

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

咱們發現ConnectionWrap也沒有什麼邏輯,繼續看LibuvStreamWrap。ide

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

繼續作一些初始化,咱們只關注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());
  // 關聯object和this對象,後續經過unwrap使用
  Wrap(object, this);
  // 入隊
  env->handle_wrap_queue()->PushBack(this);
}

重點來了,就是Wrap函數。oop

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爲拓展的內存大小,根據索引找到對應位置
  int offset = GetHeaderSize() + (kPointerSize * index);
  // 寫對應位置的內存,即保存對應的內容到內存
  WRITE_FIELD(this, offset, value);
}

Wrap函數展開後,作的事情就是把一個值保存到v8 c++對象的內存裏。那保存的這個值是啥呢?咱們看Wrap函數的入參Wrap(object, this)。object是由函數模板建立的對象,this是一個TCPWrap對象。因此Wrap函數作的事情就是把一個TCPWrap對象保存到一個函數模板建立的對象裏。這有啥用呢?咱們繼續分析。這時候new TCP就執行完畢了。咱們看看這時候執行new TCP().bind()函數的邏輯。佈局

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函數的屬主,根據前面的分析咱們知道屬主是Initialize函數定義的函數模板建立出來的對象。這個對象保存了一個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對象了。


本文分享自微信公衆號 - 編程雜技(theanarkh)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索