Вызов функции Javascript из обратного вызова C++ в V8

Я пытаюсь вызвать зарегистрированную функцию 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. Обратите внимание, что если я вызываю функцию обратного вызова напрямую со ссылкой на Persistent из addEventListener(), она выполняет функцию правильно.

Я предполагаю, что мне нужен шкафчик или изолятор? Также похоже, что uv_queue_work() libuv может решить эту проблему, но, поскольку я не запускаю поток, я не вижу, как вы будете его использовать.


person marchaos    schedule 11.12.2012    source источник


Ответы (3)


Когда вы объявляете Persistent<Function> fn в своем коде, fn является переменной, размещенной в стеке.

fn — это Persistent<Function>, который является классом handle, и он будет содержать указатель на какое-то значение типа 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));

(обратите внимание, что operator* на Persistent<T> возвращает T*, а не более традиционный T&, см. http:/ /bespin.cz/~ondras/html/classv8Persistent<Function> fn1Handle.html)

Вам также придется настроить callback, чтобы учесть тот факт, что context теперь является необработанным указателем на Function, например:

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

Создание Persistent<Function> из необработанного указателя на функцию здесь нормально, потому что мы знаем, что context на самом деле является постоянным объектом.

Я также изменил (*func)->Call(...) на func->Call(...) для краткости; они делают то же самое для ручек V8.

person je4d    schedule 16.12.2012
comment
Спасибо, это упрощает код и устраняет проблему с областью действия, но я надеялся получить некоторую информацию о том, как выполнить обратный вызов основного потока из потока обратного вызова. Я добился этого с помощью функции eio_nop() из библиотеки EIO, но предпочтительнее использовать libuv. Моя проблема в том, что не существует эквивалента libuv для eio_nop. - person marchaos; 19.12.2012
comment
@marchaos Хорошо. Мне было не совсем понятно, что вам нужно на стороне потоков. Насколько я понимаю, вам нужна возможность выполнять JS из обратного вызова в контексте основного потока v8. Я собрал небольшую демонстрацию того, как это сделать с помощью изолятов/блокировщиков (gist.github.com/4341994< /а>). Обратите внимание, что это будет означать, что вам нужно настроить везде, где вы используете V8, чтобы заблокировать изоляцию, прежде чем делать что-либо еще! - person je4d; 20.12.2012
comment
Спасибо. Попробую, но похоже на правильный подход. - person marchaos; 20.12.2012

Я знаю, что этот вопрос немного устарел, но в 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();
person Hoodlum    schedule 17.02.2015

Проблема в том, что в addEventListener Persistent<Function> fn выделяется в стеке, а затем вы берете указатель на него, чтобы использовать его в качестве контекста для обратного вызова.

Но, поскольку fn выделяется в стеке, он исчезает при выходе addEventListener. Итак, с помощью обратного вызова context теперь укажите на какое-то фиктивное значение.

Вы должны выделить немного места в куче и поместить все данные, которые вам нужны, в callback туда.

person Someone    schedule 16.12.2012
comment
Я считаю, что внутренне V8 выделяет все, что Persistent в кучу - оно определенно предназначено для того, чтобы быть там, пока вы явно не избавитесь от него. - person marchaos; 16.12.2012
comment
подтверждать. Постоянные дескрипторы предоставляют ссылку на размещенный в куче объект JavaScript из developers.google.com/v8/embed - person neu-rah; 04.02.2016