您好,登錄后才能下訂單哦!
這篇文章主要講解了“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<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函數。
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++層的通用邏輯是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。