如何从C++调用JavaScript回调函数

4

我正在尝试从我的函数的另一个点调用V8中的回调。因此,这段代码注册了回调:

        if (args.Length())
        {
            String::Utf8Value event(args[0]->ToString());
            if (event.length())
            {  
                Isolate* isolate = V8Interface::getCurrent()->getIsolate();

                Locker locker(isolate);
                HandleScope scope(isolate);

                callback cb = callback(isolate, Local<Function>::Cast(args[1]));

                 if(!events.count(*event))
                 {
                events[*event] = callbacks({ cb });
                 } 
                 else 
                {
                    events.find(*event)->second.push_back(cb);
                 }
            }
        }

而这个调用它:

 void trigger(std::string event)
    {

        Isolate* isolate = V8Interface::getCurrent()->getIsolate();

        Locker locker(isolate);
        HandleScope scope(isolate);

        if(events.count(event))
        {
            for(callback cb : events.find(event)->second)
            {
                Local<Function> local = Local<Function>::New(isolate, cb);
                local->Call(isolate->GetCurrentContext()->Global(), 0, {});
            }
        }
    }

触发函数可以在我的C++代码的任何位置随时调用。我尝试了一个简单的例子(初始化v8然后调用触发器),结果如下:

#
# Fatal error in C:\OgreSDK\Projects\whitedrop\third_party\io.js\deps\v8\src/api.h, line 386
# CHECK(allow_empty_handle || that != 0) failed
#

这是我的主函数:

Scribe::V8Interface v8Interface = Scribe::V8Interface();
v8Interface.initialize();

for(Whitedrop::WorldEvent* event : Whitedrop::worldEvents)
{
    event->onStart();
}

您可以在此处获取整个源代码:

https://github.com/whitedrop/whitedrop/tree/feature/v8

第386行是:

/**
   * Create a local handle for the content of another handle.
   * The referee is kept alive by the local handle even when
   * the original handle is destroyed/disposed.
   */
  V8_INLINE static Local<T> New(Isolate* isolate, Handle<T> that); // <<<<<<
  V8_INLINE static Local<T> New(Isolate* isolate,
                                const PersistentBase<T>& that);

编辑:我尝试了Ben Noordhuis的方法,如下:

Isolate* isolate = V8Interface::getCurrent()->getIsolate();

Locker locker(isolate);
HandleScope scope(isolate);

callback cb;
cb.Reset(isolate, Local<Function>::Cast(args[1]));

并且要调用:

Isolate* isolate = V8Interface::getCurrent()->getIsolate();

Locker locker(isolate);
HandleScope scope(isolate);

if(events.count(event))
{
    for(callback cb : events.find(event)->second)
    {
        Local<Function> local = Local<Function>::New(isolate, cb);
        local->Call(isolate->GetCurrentContext()->Global(), 0, {});
    }
}

但是仍然出现错误 :'(


那段代码是否在主线程上执行? - mscdex
我想是这样。我会上传主循环。无论如何,我没有做任何实现线程的事情。 - Vinz243
错误提示表明您的隔离区(Isolate)不知道您的持续对象(Persistent)。node是否从一个新线程触发了回调?在您的addWorldEvent()函数中调用v8::Isolate::SetData()以查找,然后在您的trigger()函数中检查数据是否仍然存在。 - Brad Werth
@BradWerth 我没有实现线程,就像我说的那样。setData怎么使用? - Vinz243
节点可能会为您创建线程,并从那里调用设置函数。您可以通过比较在addWorldEvent()函数和trigger()函数中使用的隔离来检测这一点。它们是同一个对象吗?即使它们是同一个对象,我也不确定v8句柄是否相同,因此您可以通过设置数据isolate->SetData((void*)1L)并在trigger中检查(isolate->GetData() == (void *)1L)来进行测试。 - Brad Werth
显示剩余2条评论
2个回答

3
不确定问题的作者是否仍需要答案。无论如何。 在之前关于将本地处理程序转换为持久处理程序的回答中,Node.js C++附加组件文档页面提供了一些示例:https://nodejs.org/api/addons.html#addons_function_factory。 对于持久对象,它们使用静态构造方法作为Persistent<Function>。 当模块被初始化时,构造函数可以通过Reset方法进行初始化,如下所示(实际上我没有在v8文档中找到它的描述-http://izs.me/v8-docs/classv8_1_1Persistent.html):
// some where in external class or as global var
static Persistent<Function> persistentCallback;

// when need to init
persistentCallback.Reset(isolate, f_tmpl->GetFunction());

为了在需要的时候调用此方法,您应该像这样使用本地句柄转换:
Local<Function> f = Local<Function>::New(isoloate,persistentCallback)

请注意,您不能使用Handle指针,因为其::New方法不适用于此类参数。

顺便说一下。我发现在我的代码中不需要使用Persistent处理程序来存储回调方法,如果我已经将其绑定到JS上下文中,则似乎在这种情况下它由GC正确管理。但是,在存储某些受保护数据的模块内部时,没有其他方法可以在C ++函数调用之间共享变量。


0

我认为在将Local<T>存储到events之前,您应该将其转换为Persistent<T>。如果不这样做,v8的GC可能会随时收集函数。

Handle<Function> args0 = Handle<Function>::Cast(args[0]);
Persistent<Function> pfn(args0);
callback cb = callback(isolate, pfn); // use Persistent<T> instead of Local<T>

在您的第二行代码中,我遇到了“error C2664: cannot convert argument 1 from 'v8::Handlev8::Function' to 'const v8::Persistent<v8::Function,v8::NonCopyablePersistentTraits<T>> &”错误。 - Vinz243
Persistent<Function> pfn = Persistent<Function>::New(args0); 这个怎么样?我们可能使用不同的版本,网上的例子似乎根据你的v8版本而有所不同。 - justin.m.chase
OK,v8.h是https://chromium.googlesource.com/v8/v8/+/4.1.0.14/include/v8.h#690,但我得到了“无法将'v8::Primitive *'转换为'v8 :: Object * volatile'”的错误。 - Vinz243
你可能需要更改回调函数的签名,以接受Handle<T>而不是Local<T>,并且在任何想要存储引用的地方将其转换为Persistent<T>。这样做的原因是Local<T>只有对局部作用域中对象的引用。当对象超出范围并被取消引用时,它将进入GC清理对象列表。这可能导致您存储的任何Handle或Local在未来某个时间指向内存中的无效位置。如果您需要进一步帮助,请提供更好的代码片段。 - justin.m.chase
这是链接 https://github.com/whitedrop/whitedrop/blob/feature/v8/scripting/v8/src/Event.cpp 但我不理解修改签名。你的意思是直接修改v8.h文件吗? - Vinz243
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接