【问题标题】:Calling Javascript function from a C++ callback in V8从 V8 中的 C++ 回调调用 Javascript 函数
【发布时间】:2012-11-29 09:25:03
【问题描述】:

我试图在调用 c++ 回调时调用已注册的 JS 函数,但我得到了一个段错误,因为我认为这是一个范围问题。

 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() 也可以解决这个问题,但是由于我没有启动线程,所以我看不到您将如何使用它。

【问题讨论】:

    标签: c++ multithreading v8 libuv


    【解决方案1】:

    当您在代码中声明 Persistent&lt;Function&gt; fn 时,fn 是堆栈分配的变量。

    fn 是一个Persistent&lt;Function&gt;,它是一个句柄 类,它将包含一个指向Function 类型的堆分配值的指针,但fn 本身是开启的堆栈。

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

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

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

    (注意Persistent&lt;T&gt; 上的operator* 返回T*,而不是更传统的T&amp;,参见http://bespin.cz/~ondras/html/classv8_1_1Handle.html

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

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

    在这里从原始函数指针创建Persistent&lt;Function&gt; 是可以的,因为我们知道context 实际上是一个持久对象。

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

    【讨论】:

    • 谢谢,这确实简化了代码并修复了范围问题,但我希望获得一些关于如何从回调线程回调到主线程的信息。我已经使用 EIO 库中的 eio_nop() 函数实现了这一点,但首选方法是使用 libuv。我的问题是似乎没有 eio_nop 的 libuv 等价物。
    • @marchaos 好的。我不完全清楚你在线程方面追求什么。据我了解,您所追求的是能够从主 v8 线程上下文中的回调执行 JS。我已经整理了一个关于如何使用隔离器/储物柜 (gist.github.com/4341994) 执行此操作的小演示。请注意,这意味着您必须在使用 V8 的所有位置进行调整以锁定隔离,然后再执行其他任何操作!
    • 谢谢。会试一试,但看起来是正确的方法。
    【解决方案2】:

    我知道这个问题有点老了,但是 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();
    

    【讨论】:

      【解决方案3】:

      问题在于,在 addEventListener 中,Persistent&lt;Function&gt; fn 分配在堆栈上,然后您将指向该指针的指针用作回调的上下文。

      但是,因为fn是在栈上分配的,所以当addEventListener退出时它就消失了。因此,使用回调 context 现在指向一些虚假值。

      你应该分配一些堆空间,把你需要的所有数据放在callback那里。

      【讨论】:

      • 我相信 V8 内部会分配任何持久到堆的东西 - 它肯定被设计为在那里,直到你明确地处理它。
      • 确认。 “持久句柄提供对堆分配的 JavaScript 对象的引用”来自 developers.google.com/v8/embed
      猜你喜欢
      • 1970-01-01
      • 2018-01-11
      • 2017-03-20
      • 1970-01-01
      • 2023-03-28
      • 1970-01-01
      • 2012-06-23
      • 1970-01-01
      相关资源
      最近更新 更多