2012-12-11 8 views
13

Sto provando a chiamare una funzione JS registrata quando viene chiamato un callback C++, ma sto ottenendo un segfault per quello che presumo sia un problema di scoping.Chiamare la funzione Javascript da un callback C++ in V8

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); 
} 

Quando si verifica un evento, viene chiamato il seguente metodo. Presumo che ciò avvenga probabilmente su un altro thread a cui V8 sta eseguendo 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()); 
} 

Questo provoca un errore di segmentazione: 11. Si noti che se chiamo la funzione di richiamata direttamente con un riferimento persistente da addEventListener(), esegue la funzione correttamente.

Suppongo che io abbia bisogno di un armadietto o di un isolamento? Sembra anche che uv_queue_work() di libuv sia in grado di risolverlo, ma poiché non avvio il thread, non riesco a vedere come lo useresti.

risposta

17

Quando si dichiara Persistent<Function> fn nel codice, fn è una variabile allocata allo stack.

fn è un Persistent<Function>, che è un maniglia classe, e conterrà un puntatore a un certo valore heap allocata di tipo Function, ma fn stesso è in pila.

Ciò significa che quando si chiama registerListener(&callback, &fn), &fn sta prendendo l'indirizzo del manico (tipo Persistent<Function>), non l'indirizzo del Function sul mucchio. Quando la funzione termina, l'handle verrà distrutto ma lo stesso Function rimarrà nell'heap.

Così come una correzione, suggerisco passando l'indirizzo della Function anziché l'indirizzo del manico, in questo modo:

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

(notare che operator* su un Persistent<T> restituisce un T* piuttosto che il più convenzionale T&, CF http://bespin.cz/~ondras/html/classv8_1_1Handle.html)

dovrete anche per regolare callback per tenere conto del fatto che context ora è un puntatore ad una cruda Function, in questo modo:

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

Creazione di un Persistent<Function> da un puntatore a funzione grezzo qui è OK perché sappiamo che context è in realtà un oggetto persistente.

Ho anche modificato (*func)->Call(...) a func->Call(...) per brevità; fanno la stessa cosa per le maniglie V8.

+0

Grazie, questo semplifica il codice e corregge il problema dell'ambito, ma speravo in alcune informazioni come su come richiamare il thread principale dal thread di callback. Ho ottenuto questo risultato utilizzando la funzione eio_nop() dalla libreria EIO, ma il modo preferito è usare libuv. Il mio problema è che non sembra esserci un equivoco di libua di eio_nop. – marchaos

+1

@marchaos Ok. Non ero del tutto chiaro cosa stavi cercando sul lato del threading. A quanto ho capito, quello che stai cercando è in grado di eseguire JS dal callback nel contesto del thread principale v8. Ho messo insieme una piccola demo su come farlo con isolates/lockers (https://gist.github.com/4341994). Nota che questo significa che devi regolare ovunque usi V8 per bloccare l'isolato prima di fare qualsiasi altra cosa! – je4d

+0

Grazie. Darà questo, ma sembra l'approccio giusto. – marchaos

2

Il problema è che in addEventListener, 2 viene allocato nello stack e quindi si sta prendendo il puntatore su quello da utilizzare come contesto per il callback.

Tuttavia, poiché fn è allocato nello stack, scompare quando si chiude addEventListener. Quindi, con la callback context ora punta a qualche valore fasullo.

È necessario allocare un po 'di spazio heap e inserire tutti i dati necessari in callback lì.

+1

Credo internamente V8 allocazioni nulla persistente al mucchio - è certamente progettato per essere lì fino a quando non esplicitamente disporne. – marchaos

+0

confermare. "Le maniglie persistenti forniscono un riferimento a un oggetto JavaScript con heap" da https://developers.google.com/v8/embed –

13

So che questa domanda è un po 'vecchia, ma c'è stato un aggiornamento piuttosto importante in nodejs dalla v0.10 alla v0.12. V8 ha cambiato il comportamento di v8 :: Persistent. v8 :: Persistent non eredita più da v8 :: Handle. Stavo aggiornando po 'di codice e ha scoperto che i seguenti funzionato ...

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(); 
    } 

credo che l'obiettivo di questo aggiornamento è stato quello di rendere più difficile per esporre le perdite di memoria. Nel nodo v0.10, che avrebbe fatto qualcosa di simile a quanto segue ...

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();