在V8中从C++回调调用Javascript函数

Calling Javascript function from a C++ callback in V8

本文关键字:调用 Javascript 函数 回调 C++ V8 中从      更新时间:2023-10-16

当调用c++回调时,我试图调用一个注册的JS函数,但由于我认为这是一个范围问题,我得到了一个segfault。

Handle<Value> addEventListener( const Arguments& args ) {
HandleScope scope;
if (!args[0]->IsFunction()) {
return ThrowException(Exception::TypeError(String::New("Wrong arguments")));
}
Persistent<Function> fn = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
Local<Number> num = Number::New(registerListener(&callback, &fn));
scope.Close(num);
}

当事件发生时,将调用以下方法。我假设这可能发生在V8正在执行JS的另一个线程上。

void callback(int event, void* context ) {
HandleScope scope;
Local<Value> args[] = { Local<Value>::New(Number::New(event)) };
Persistent<Function> *func = static_cast<Persistent<Function> *>(context);
(* func)->Call((* func), 1, args);
scope.Close(Undefined());
}

这会导致分段故障:11。请注意,如果我通过引用addEventListener()中的Persistent直接调用回调函数,它将正确执行该函数。

我想我需要一个储物柜还是隔离?看起来libuv的uv_queue_work()可能也能解决这个问题,但由于我没有启动线程,我看不出你会如何使用它

当您在代码中声明Persistent<Function> fn时,fn是堆栈分配的变量。

fn是一个Persistent<Function>,它是一个句柄类,它将包含一个指向某个类型为Function的堆分配值的指针,但fn本身在堆栈上。

这意味着,当调用registerListener(&callback, &fn)时,&fn获取句柄的地址(类型为Persistent<Function>),而不是堆上Function的地址。当函数退出时,句柄将被销毁,但Function本身将保留在堆上。

因此,作为修复方法,我建议传递Function的地址,而不是句柄的地址,如下所示:

Persistent<Function> fn = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
Local<Number> num = Number::New(registerListener(&callback, *fn));

(注意,Persistent<T>上的operator*返回T*,而不是更传统的T&,c.f。http://bespin.cz/~ondras/html/classv8_1Handle.html)

您还必须调整callback,以考虑到context现在是指向Function的原始指针,如下所示:

Persistent<Function> func = static_cast<Function*>(context);
func->Call((* func), 1, args);

从这里的原始Function指针创建Persistent<Function>是可以的,因为我们知道context实际上是一个持久对象。

为了简洁起见,我还将(*func)->Call(...)更改为func->Call(...);它们对V8手柄做同样的事情。

我知道这个问题有点老了,但nodejs v0.10到v0.12进行了相当大的更新。V8更改了V8::Persistent的行为。v8::Persistent不再从v8::Handle继承。我正在更新一些代码,发现以下代码有效。。。

void resize(const v8::FunctionCallbackInfo<Value> &args) {
Isolate *isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Persistent<Function> callback;
callback.Reset(isolate, args[0].As<Function>())
const unsigned argc = 2;
Local<Value> argv[argc] = { Null(isolate), String::NewFromUtf8(isolate, "success") };
Local<Function>::New(isolate, work->callback)->Call(isolate->GetCurrentContext()->Global(), argc, argv);
callback.Reset();
}

我相信这次更新的目的是让暴露内存泄漏变得更加困难。在节点v0.10中,您可以执行以下操作。。。

v8::Local<v8::Value> value = /* ... */;
v8::Persistent<v8::Value> persistent = v8::Persistent<v8::Value>::New(value);
// ...
v8::Local<v8::Value> value_again = *persistent;
// ...
persistent.Dispose();
persistent.Clear();

问题是在addEventListener中,Persistent<Function> fn在堆栈上分配,然后您将指向它的指针用作回调的上下文。

但是,因为fn是在堆栈上分配的,所以当addEventListener退出时,它就会消失。因此,在回调中context现在指向一些伪值。

您应该分配一些堆空间,并将所需的所有数据放在那里的callback中。