2012-12-11 46 views
13

Tôi đang cố gắng gọi hàm JS đã đăng ký khi gọi lại C++, nhưng tôi nhận được một segfault cho những gì tôi giả định là một vấn đề phạm vi.Gọi hàm Javascript từ cuộc gọi lại C++ trong 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); 
} 

Khi xảy ra sự kiện, phương pháp sau được gọi. Tôi giả định rằng điều này có thể xảy ra trên một chủ đề khác mà V8 đang thực hiện 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()); 
} 

Điều này gây ra một lỗi phân khúc: 11. Lưu ý rằng nếu tôi gọi hàm callback trực tiếp với một tham chiếu đến dai dẳng từ addEventListener(), nó thực hiện các chức năng một cách chính xác.

Tôi giả định rằng tôi cần một Locker hoặc Isolate? Nó cũng có vẻ như của libuv uv_queue_work() có thể có thể giải quyết điều này, nhưng kể từ khi tôi không bắt đầu thread, tôi không thể nhìn thấy cách bạn sẽ sử dụng nó.

Trả lời

17

Khi bạn khai báo Persistent<Function> fn trong mã của mình, fn là biến được phân bổ theo stack.

fn là một Persistent<Function>, mà là một xử lý lớp, và nó sẽ chứa một con trỏ đến một số giá trị đống phân bổ kiểu Function, nhưng fn chính nó là trên stack.

này có nghĩa là khi bạn gọi registerListener(&callback, &fn), &fn là lấy địa chỉ của tay cầm (loại Persistent<Function>), không phải là địa chỉ của Function trên heap. Khi chức năng của bạn thoát, tay cầm sẽ bị phá hủy nhưng bản thân số Function sẽ vẫn còn trên heap.

Vì vậy, khi một sửa chữa, tôi khuyên bạn nên đi qua các địa chỉ của Function thay vì địa chỉ của tay cầm, như thế này:

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

(lưu ý rằng operator* trên Persistent<T> trả về một T* chứ không phải là truyền thống hơn T&, cf http://bespin.cz/~ondras/html/classv8_1_1Handle.html)

Bạn cũng sẽ phải điều chỉnh callback để giải thích cho thực tế là context bây giờ là một con trỏ liệu đến một Function, như thế này:

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

Tạo một Persistent<Function> từ một con trỏ hàm liệu đây là OK vì chúng ta biết rằng context thực sự là một đối tượng dai dẳng.

Tôi cũng đã thay đổi (*func)->Call(...) thành func->Call(...) cho ngắn gọn; họ làm điều tương tự cho V8 xử lý.

+0

Cảm ơn, điều này đơn giản hóa mã và khắc phục vấn đề phạm vi, nhưng tôi đã hy vọng một số thông tin cách gọi lại chủ đề chính từ chuỗi gọi lại. Tôi đã đạt được điều này bằng cách sử dụng chức năng eio_nop() từ thư viện EIO, nhưng cách ưu tiên là sử dụng libuv. Vấn đề của tôi là dường như không có một sự tương đương libuv của eio_nop. – marchaos

+1

@marchaos Ok. Tôi đã không hoàn toàn rõ ràng những gì bạn đã sau khi bên luồng. Theo tôi hiểu nó, những gì bạn đang sau là có thể thực hiện JS từ callback trong bối cảnh chủ đề v8 chính. Tôi đã cùng nhau đưa ra một bản demo nhỏ về cách làm điều này với các cô lập/tủ khóa (https://gist.github.com/4341994). Lưu ý rằng điều này có nghĩa là bạn phải điều chỉnh ở mọi nơi bạn sử dụng V8 để khóa cô lập trước khi thực hiện bất kỳ điều gì khác! – je4d

+0

Cảm ơn. Sẽ cung cấp cho điều này một đi, nhưng trông giống như cách tiếp cận đúng. – marchaos

2

Vấn đề là trong addEventListener, Persistent<Function> fn được cấp phát trên ngăn xếp và sau đó bạn sẽ đưa con trỏ đến đó để sử dụng làm ngữ cảnh cho cuộc gọi lại.

Nhưng, bởi vì fn được cấp phát trên ngăn xếp, nó biến mất khi addEventListener thoát. Vì vậy, với việc gọi lại context bây giờ trỏ đến một số giá trị không có thật.

Bạn nên phân bổ một số khoảng trống và đặt tất cả dữ liệu bạn cần trong callback tại đó.

+1

Tôi tin rằng trong nội bộ cơ V8 phân bổ bất cứ điều gì dai dẳng đến đống - nó chắc chắn được thiết kế để có mặt ở đó cho đến khi bạn dứt khoát vứt bỏ nó. – marchaos

+0

xác nhận. "Xử lý liên tục cung cấp tham chiếu đến đối tượng JavaScript được phân bổ heap" từ https://developers.google.com/v8/embed –

13

Tôi biết câu hỏi này hơi cũ, nhưng đã có một cập nhật khá lớn trong các nút từ v0.10 đến v0.12. V8 đã thay đổi hành vi của v8 :: Dai dẳng. v8 :: Không còn kế thừa từ v8 :: Xử lý. Tôi đã cập nhật một số mã và thấy rằng những điều sau đây đã làm việc ...

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

Tôi tin rằng mục tiêu của bản cập nhật này là làm cho việc rò rỉ bộ nhớ bị khó hơn. Trong nút v0.10, bạn đã có thể làm một cái gì đó như sau ...

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(); 
Các vấn đề liên quan