Node.js的C++扩展教程(三)

今天我们来讲一下V8中句柄的概念。

句柄的定义

我们知道Windows下的应用程序,在打开时是一个窗口,这个窗口是有一个句柄的,称作窗口句柄。在Windows API中有 Handle handle = ::FindWindow(NULL, ClassName); 用于查找窗口句柄。但是这个句柄跟我们今天讲的V8中的句柄是两回事。

句柄在V8中是一个很重要的概念,它提供了对于堆内存中JavaScript数据对象的一个引用。在C++中传递参数用于同步数据有两种方式:一个是指针,一个是引用。那么同样是为了找到数据,为什么不用指针,而是使用引用呢?

我们都知道V8中存在垃圾回收机制一说,在垃圾回收的时候会不断的搬运堆数据,从一个地方移动到另一个地方。如果使用指针的话,一旦一个对象被移走,这个指针就变成了野指针。但垃圾回收器更新引用了这些数据库的那些句柄,让其断不了联系。

当一个对象不再被句柄引用时,那么它将很不幸地被认为是垃圾。Chrome V8的垃圾回收机制会不时地对其进行回收。 所以,句柄的引用对于Chrome V8的回收机制是很关键的。

句柄分类

句柄分为以下几类:

  • 本地句柄(v8::Local)
  • 持久句柄(v8::Persistent)
  • 永生句柄(v8::Eternal)
  • 待实本地句柄(MaybeLocal)
  • 其他句柄

其中本地句柄(相当于一个局部变量)和持久句柄(相当于一个New变量)是最常用到的句柄。句柄存在的形式是C++的一个模板类,其需要根据不同的Chrome V8的数据类型进行不同的声明。例如:

  • v8::Local<v8::Number>:本地JavaScript数值类型句柄。
  • v8::Persistent<v8::String>:持久JavaScript字符串类型句柄。

这些句柄都能通过 .方法名 来访问句柄对象的一些方法。而且它还重载了 *->两个操作符。通过 *操作符能得到这个句柄所引用的JavaScript数据对象实体指针,->也一样。举个例子:假设我们有一个字符串本地句柄Local<String> str,那么下面两种调用方式的意义就不一样:

  • str.IsEmpty():句柄对象本身的函数,来判断这个句柄是否是一个空句柄。
  • str->Lenghth():通过->得到String*,而String有一个方法Length可获得字符串长度。所以str->Length()是用于判断这个字符串包含多少个字符。另一种写法是(*str).Length()。

本地句柄

本地句柄(Local)存在于栈内存中,并在对应的析构函数被调用时被删除。这里的析构函数是句柄本身的析构函数,一般在块作用域或者函数作用域的时候被析构。也就是说它的生命周期是由其所在的句柄作用域(Handle Scope)决定的。在C++中,句柄作用域等同于块级作用域、函数作用域或者全局作用域。

1.创建(New)

new 是句柄的一个静态方法(直接使用类名来使用)。有两种方式:

  • Local<T>::New(Isolate* isolate, Local<T> that),通过另一个本地句柄进行复制构造。
  • Local<T>::New(Isolate* isolate, const PersistentBase<T> &that>:传入Isolate和一个持久句柄。

大多数情况下,我们都是通过Chrome V8中的JavaScript数据类型的一些静态方法来获取一个本地句柄,例如:

Local<Number> Number::New(Isolate* isolate, double value);

2.清除(Clear)

将指定句柄设置为一个空句柄——也就是一个指向null的句柄。

Local<Number> handle = Number::New(isolate, 233);
handle.Clear();

3.是否为空(IsEmpty)

不能使用与null比较的方式来判断,只能通过IsEmpty()函数来判断。

Local<Number> handle = Number::New(isolate, 233);
if(handle.IsEmpty())
{
	...
}

4.数据类型转换(As/Cast)

将某种数据类型的本地句柄转换成另一种类型的本地句柄,就可以使用As或者Cast函数。其中As是成员函数,Cast是静态函数。

比如我们有一个Local<Value>的本地句柄handle,我们就能通过这两个函数进行转换:

Local<Number> ret1 =  Local<Number>::Cast(handle);
Local<Number> ret2 = handle.As<Number>();

持久句柄

持久句柄提供了一个堆内存中声明的JavaScript对象的引用。持久句柄和本地句柄在生命周期上的管理是两种不同的方式。

持久句柄通过SetWeak()的方式让句柄变成一个弱持久句柄。当对一个JavaScript对象的引用只剩下一个弱持久句柄时,Chrome V8的垃圾回收器就会触发一个回调。

1.构造函数

持久句柄一般是通过本地句柄升格而成。所以它的获取方法通常是在构造函数中传入一个本地句柄。

  • Persistent():先构建一个空的持久句柄,后期通过设置的方法将一个本地句柄转成持久句柄。
  • Persistent(Isolate* isolate,Local<T> that):传入Isolate实例以及一个本地句柄,能得到这个本地句柄所引用的V8数据对象的一个持久句柄。
Local<Number> handle = Number::New(isolate, 2333);
Persistent<Number> phandle(isolate, handle);

2.清除(Clear)

和本地句柄使用方法一样。

3.是否为空(IsEmpty)

和本地句柄使用方法一样。

4.设置为弱持久句柄(SetWeak)

将持久句柄降格为一个弱持久句柄。函数原型如下:

template <typename p>
void Persistent<T>::SetWeak(P *parameter, WeakCallbackInfo<P>::Callback callbak, WeakCallbackType type);

参数意思如下:

  • parameter:可以是任意数据类型。
  • callack:一个回调函数。前文说过对于V8数据类型只有一个弱持久句柄的引用时,会触发一个回调函数,这个回调函数就是这里的callback。触发回到函数时,第一个参数parameter会被传入回到函数供你使用。
  • type:回调函数的类型,是一个枚举值。

WeakCallbackInfo<T>::Callback回调函数的原型如下:

typedef void(*Callback)(const WeakCallbackInfo<T> &data);

讲的通俗一点,如果以有一个类Test,要将它作为回调参数的话,回调函数就应该这么写:

void callback(const WeakCallbackInfo<Test>& data)
{
	// 做回调的内容,比如删除掉你的 `Test` 对象指针以免泄漏
	// 因为这个回调函数一旦被触发,就代表再也没有其他本地句柄、持久引用等
	// 任何句柄指向它了。
	Test* test = data.GetParameter();
	delete test;
}

有了这个回调函数后,就能将它传入SetWeak函数中。

// 没有任何其他地方删除它
Test* test = new Test();
//假设persistent_handle是任意一个持久句柄
persistent_handle.SetWeak(test, Callback, WeakCallbackType::kParameter);

这样,在persistent_handle快要被垃圾回收时,就会调用回调函数里的代码,将test对象delete掉。

5.取消弱持久句柄(ClearWeak)

其作用是取消它的弱持久句柄——又变成一般的持久句柄了。

该函数没有参数:

persistent_handle.ClearWeak();

6.标记独立句柄(MarkIndependent)

独立的持久句柄有两个特性:

  • Chrome V8的垃圾回收器可以自由地忽略包含这个句柄的对象组。
  • 独立的持久句柄可以在新生代回收的时候被回收,而非独立句柄则不行。

7.是否为弱的(IsWeak)与是否为独立的(IsIndependent)

bool weak = persistent_handle.IsWeak();
bool independent = persistent_handle.IsIndependent();

永生句柄(Eternal)

我们认为这种句柄在程序的整个声明周期内是不会被删除的。类似C++中的全局变量。

待实本地句柄(MaybeLocal)

待实本地句柄是为了保证程序的健壮性而设计的。在原先的设计中,获取本地句柄时是需要判断句柄是否为空(IsEmpty)后,才能使用。而MaybeLocal就替代了这种检测,所以现在的代码就写成了:

MaybeLocal<String> s = x.ToString();
Local<String> _s = s.ToLocalChecked();
或者
Local<String> _s = x.ToString().ToLocalChecked();

总之:有有效句柄连接的对象实体不会被垃圾回收器进行回收。而失去了所有句柄引用的对象实体会被认为是垃圾,从而在下次垃圾回收的时候被释放。

发布了10 篇原创文章 · 获赞 0 · 访问量 117

猜你喜欢

转载自blog.csdn.net/weixin_42071117/article/details/104806431