温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

nodejs源码分析中c++层的通用逻辑是什么

发布时间:2021-11-25 15:41:00 阅读:130 作者:iii 栏目:大数据
C++开发者专用服务器限时活动,0元免费领,库存有限,领完即止! 点击查看>>

这篇文章主要讲解了“nodejs源码分析中c++层的通用逻辑是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“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函数)。

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来表示如下:

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函数。

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的逻辑。

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。

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<Objectobject,                       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函数。

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对象了。

感谢各位的阅读,以上就是“nodejs源码分析中c++层的通用逻辑是什么”的内容了,经过本文的学习后,相信大家对nodejs源码分析中c++层的通用逻辑是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

原文链接:https://my.oschina.net/u/4217331/blog/4411898

AI

开发者交流群×