v8. save & call JavaScript function from c++ - c++

I am trying to save JavaScript function in my c++ app and call it from another thread.
But I got "Unhandled exception at 0x0101B5D5 in Console.exe: 0xC0000005: Access violation reading location 0x00000017."
Saving the JavaScript function (in requestCallbacks vector):
Handle<Value> HttpEngine::addListener(const Arguments& args)
{
Locker locker;
HandleScope scope;
HttpEngine* pThis = UnwrapHttpEngine( args.This() );
Persistent<Function> callback = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
pThis->requestCallbacks.push_back(*callback);
return Boolean::New(true);
}
An attempt to call it from another thread:
void HttpEngine::emit()
{
Locker locker;
HandleScope scope;
for (size_t i = 0; i < requestCallbacks.size(); i++)
{
Persistent<Function> func = static_cast<Function*>(requestCallbacks[i]);
Handle<Value> args[1];
args[0] = v8::String::New("http://google.com");
func->Call(Context::GetCurrent()->Global(), 1, args);
}
}
JavaScript code (where the httpEngine is my c++ object in global scope):
httpEngine.addListener(function (url) {
print('on request: ' + url);
});

What's the type of requestCallbacks? Try putting the Persistent<Function> in there, not a raw Function* (using the * operator on a Persistent is both unsupported and most certainly not what you want to do).

Related

Is there a way to identify Persistent<Function> uniqueness?

I am storing js callbacks in vector:
std::vector<std::unique_ptr<Persistent<Function>>> callbacks;
Everything is working fine. The problem is that I do not want to store duplicated callbacks because I do not want to notify the same callback twice. I have to compare them somehow. Here is my full function but that does not work:
void ProcessCallback(const FunctionCallbackInfo<Value>& args)
{
std::string returnInfo;
Isolate* isolate = args.GetIsolate();
Local<Function> notifyFunction = Local<Function>::Cast(args[0]);
auto predicate = [&](const std::unique_ptr<Persistent<Function>>& c)
{
return c->Get(isolate)->StrictEquals(notifyFunction);
};
auto it = std::find_if(callbacks.begin(), callbacks.end(), predicate);
if (it == callbacks.end())
{
returnInfo = "Did not find callback. Adding..." + std::to_string(callbacks.size());
auto persistentCallback = std::make_unique<Persistent<Function>>(isolate, notifyFunction);
callbacks.emplace_back(std::move(persistentCallback));
}
else
{
returnInfo = "Callback already exist in a list.\n";
}
args.GetReturnValue().Set(String::NewFromUtf8(isolate, returnInfo.c_str()).ToLocalChecked());
}
In js:
function callback(msg) {
console.log(msg);
}
let addon = require('./build/Release/addon');
console.log(addon.on(callback));
console.log(addon.on(callback));
Is there something that I can rely on to uniquely identify function that is passed from js? Thanks.

Crash when trying to call node js callback using C++ addon

I am trying to call some native api from node js and perform long running task. I am using libuv for that perposes, store js callback to perform some meaningful task in separate thread and call callback later. This is what I've done so far:
task.cpp
struct Work {
uv_work_t request;
Local<Function> callback;
};
static void WorkAsyncComplete(uv_work_t* req, int status)
{
Isolate* isolate = Isolate::GetCurrent();
v8::HandleScope handleScope(isolate);
Work* work = static_cast<Work*>(req->data);
const unsigned argc = 1;
Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "Hello world").ToLocalChecked() };
Local<Function> callback = Local<Function>::New(isolate, work->callback);
callback->Call(isolate->GetCurrentContext(), Null(isolate), argc, argv);
work->callback.Clear();
delete work;
}
static void WorkAsync(uv_work_t* req)
{
Work* work = static_cast<Work*>(req->data);
using namespace std::chrono_literals;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
void RunCallback(const FunctionCallbackInfo<Value>& args)
{
Isolate* isolate = args.GetIsolate();
Work* work = new Work();
work->request.data = work;
Local<Function> callback = Local<Function>::Cast(args[1]);
work->callback.New(isolate, callback);
uv_queue_work(uv_default_loop(), &work->request, WorkAsync, WorkAsyncComplete);
args.GetReturnValue().Set(Undefined(isolate));
}
void Init(Local<Object> exports, Local<Object> module)
{
//isolate = exports->GetIsolate();
NODE_SET_METHOD(module, "exports", RunCallback);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
task.js:
let task = require('./build/Release/task');
task((msg) => {
console.log(msg);
}
But I got crash in WorkAsyncComplete() when calling callback. Here is the error detail:
Debugger attached.
FATAL ERROR: v8::Function::Call Function to be called is a null pointer
1: 00007FF6579E03DF napi_wrap+109311
2: 00007FF657985096 v8::internal::OrderedHashTable<v8::internal::OrderedHashSet,1>::NumberOfElementsOffset+33302
3: 00007FF657985E66 node::OnFatalError+294
4: 00007FF65822A9BD v8::Function::Call+573
5: 00007FFA62FD1172 load_exe_hook+258
6: 00007FF657A37D90 uv_timer_stop+560
7: 00007FF657A37E67 uv_timer_stop+775
8: 00007FF657A3469B uv_async_send+331
9: 00007FF657A33E2C uv_loop_init+1292
10: 00007FF657A33FCA uv_run+202
11: 00007FF6579400A5 v8::internal::OrderedHashTable<v8::internal::OrderedHashSet,1>::NumberOfBucketsOffset+9365
12: 00007FF6579B3867 node::Start+311
13: 00007FF65781686C RC4_options+339820
14: 00007FF6587B523C v8::internal::compiler::RepresentationChanger::Uint32OverflowOperatorFor+153532
15: 00007FFA761A7C24 BaseThreadInitThunk+20
16: 00007FFA7694D4D1 RtlUserThreadStart+33
Please advice how to fix this issue. Thanks in advance.
Looks like the issue is that you're using a v8::Local<...> in a long-lived object. That doesn't work because the lifetime of a Local is tied to the surrounding HandleScope: the Local becomes invalid when the HandleScope dies. See https://v8.dev/docs/embed#handles-and-garbage-collection for details. The solution is to use a v8::Persistent<...> instead of a Local.
For the record, I'd also like to point out that while you can do C++ work in the background task, you won't be able to extend this example so that it takes a JavaScript function to execute concurrently. That would be multi-threaded programming, which JavaScript as a language doesn't support, and hence V8 as an implementation doesn't support either.

Store JS Object and return him - Nodejs addon

I am only trying to build a addon that only receives any object and return them back.
Server:
var is_object = addon.return_object(previous_object);
Addon:
void return_object(const FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
Local<Object> obj = Object::"From args"; //The missing part
args->GetReturnValue().Set(obj);
}
The method is:
Local<Object> obj = args[arg_value]->ToObject();

winrt c++/cx concurrency access violation exception

What I'm trying to do is check for the existence of a file in the local folder and then copy it there if it isn't found (the file was previously added to the project as an asset).
Here is the code:
Windows::Storage::StorageFile^ MainPage::GetCustomFileAsync(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
auto localTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
StorageFile^ retVal = nullptr;
localTask.then([&](StorageFile^ t){
retVal = t;
}).then([](concurrency::task<void> t)
{
try
{
t.get();
OutputDebugString(L"Found\n");
}
catch (Platform::COMException^ e)
{
OutputDebugString(e->Message->Data());
}
}).wait();
return retVal;
}
StorageFile^ fileVar;
if ((fileVar = this->GetCustomFileAsync("somefile.txt")) == nullptr)
{
String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
concurrency::create_task(Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)).then([](StorageFolder^ folder){
return (folder->GetFileAsync("somefile.txt"));
}).then([](StorageFile^ file){
return (file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder));
}).then([&](StorageFile^ file){
fileVar = file;
OutputDebugString(file->DisplayName->Data());
});
}
What happens is that I get an access violation exception at the point where "file" is being assigned to "fileVar" (because of cross-thread access perhaps?). How to fix this?
Edit: I can't do all the processing there because the file will be accessed many times. In short I need to know when it has been successfully copied and get a handle to it. Here is the code that works
Windows::Storage::StorageFile^ GetFile(Platform::String^ fileName)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
using Windows::Foundation::AsyncOperationCompletedHandler;
using Windows::Foundation::AsyncStatus;
using Windows::Foundation::IAsyncOperation;
using Platform::String;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
bool completed = false;
StorageFile^ retVal = nullptr;
localFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<StorageFile^>^ fileOperation, AsyncStatus status)
{
if (status == AsyncStatus::Error)
{
String^ path = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
Windows::Storage::StorageFolder::GetFolderFromPathAsync(path)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFolder^>(
[&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFolder^>^ folderOperation, AsyncStatus status)->void{
auto assetFolder = folderOperation->GetResults();
assetFolder->GetFileAsync(fileName)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void{
auto file = fileOperation->GetResults();
file->CopyAsync(Windows::Storage::ApplicationData::Current->LocalFolder)->Completed = ref new AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>
([&completed, &retVal, &fileName](IAsyncOperation<Windows::Storage::StorageFile^>^ fileOperation, AsyncStatus status)->void {
retVal = fileOperation->GetResults();
completed = true;
});
});
});
}
else
{
retVal = fileOperation->GetResults();
completed = true;
}
});
while (completed == false);
return retVal;
}
Rather than passing a delegate as an argument and returning void, make your method return task<StorageFile^> and then the caller can do a .then() to continue working once the operation has succeeded.
Or if this is exposed as a public WinRT method (not an internal / private C++ method) then use IAsyncOperation<StorageFile^>^ as the return type, and wrap the whole thing in create_async():
IAsyncOperation<StorageFile^>^ DoStuff(params)
{
return concurrency::create_async([params]
{
// function body goes here
});
}
Here's a solution I put together. Two things that are important to know:
When executing an asynchronous operation using concurrency::create_task the async operation(s) can still be executing when the parent function returns. So the captured variables MUST outlive the context of the parent function. Which obviously won't happen if they are being passed by reference. It took a while to realize this.
WinRT imposes certain restrictions on the concurrency runtime. Calling concurrency::task::get() or concurrency::task::wait() will throw an exception in an STA thread, unless the call is in a task continuation.
More information in this post:
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/ae54980b-41ce-4337-a059-2213b549be4b/concurrencyinvalidoperation-when-calling-tasktget?forum=winappswithnativecode
In that case how to know when the function has finished doing it's job? I opted to pass in a callback (AKA delegate).
delegate void FileOperation(Windows::Storage::StorageFile^ file);
void GetFileConcurrency(Platform::String^ fileName, FileOperation^ fileOp)
{
using Windows::Storage::StorageFile;
using Windows::Storage::StorageFolder;
using Platform::String;
auto localFolder = Windows::Storage::ApplicationData::Current->LocalFolder;
String^ assetFolderPath = Windows::ApplicationModel::Package::Current->InstalledLocation->Path + "\\Assets";
auto localFolderTask = concurrency::create_task(localFolder->GetFileAsync(fileName));
localFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](concurrency::task<StorageFile^> theTask){
try
{
StorageFile^ theFile = theTask.get();
fileOp(theFile);
}
catch (Platform::Exception^ e)
{
OutputDebugString(e->Message->Data());
auto assetFolderTask = concurrency::create_task(StorageFolder::GetFolderFromPathAsync(assetFolderPath));
assetFolderTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFolder^ assetFolder){
auto assetFileTask = concurrency::create_task(assetFolder->GetFileAsync(fileName));
assetFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
auto copyFileTask = concurrency::create_task(file->CopyAsync(localFolder));
copyFileTask.then([localFolder, assetFolderPath, fileName, fileOp](StorageFile^ file){
OutputDebugString(file->Path->Data());
fileOp(file);
});
});
});
}
});
}

How to call a v8::Function with custom modified v8::Arguments?

I want to call JSON.stringify method from the C++ side with modified arguments, but any solution that came to my mind results in a weird segfault with all frames being at "??".
I want to do the following:
api::Console is a custom console implementation for debugging purposes and has therefore static methods like api::Console::handleLog or api::Console::handleDebug.
For handleDebug which is passed correctly to the console's ObjectTemplate, the following doesn't work, v8gl::context is the current execution context and is correctly usable in other api implementations:
v8::Handle<v8::Value> Console::handleDebug(const v8::Arguments& args) {
if (args.Length() < 1) {
return v8::Undefined();
}
v8::HandleScope scope;
v8::Handle<v8::Object> global = v8gl::context->Global();
v8::Handle<v8::Object> JSON = global->Get(v8::String::New("JSON"))->ToObject();
v8::Handle<v8::Function> JSON_stringify = v8::Handle<v8::Function>::Cast(JSON->Get(v8::String::New("stringify")));
for (signed a = 0; a < args.Length(); a++) {
for (int m = 0; m < consoleMargin; m++) {
fprintf(stdout, "\t");
}
v8::Handle<v8::Value> passargs[1];
// alternative try was:
// passargs[0] = v8::String::New("{foo:'bar'}");
passargs[0] = v8::String::New(*v8::String::Utf8Value(args[a]->ToString()));
v8::String::Utf8Value value(JSON_stringify->Call(JSON, 1, passargs));
char* message = *value;
fprintf(stdout, "%s\n", message);
}
return scope.Close(v8::Undefined());
}
The backtrace in gdb is somehow weird and I have no idea why:
(gdb) backtrace
#0 0x00000000004a0880 in v8::Context::Global() ()
#1 0x00000000004128ea in api::Console::handleDebug(v8::Arguments const&) ()
#2 0x00000000004b9eab in v8::internal::Builtin_HandleApiCall(v8::internal::(anonymous namespace)::BuiltinArguments<(v8::internal::BuiltinExtraArguments)1>, v8::internal::Isolate*) ()
#3 0x000004cd67f0618e in ?? ()
#4 0x000004cd67f12998 in ?? ()
#5 0x000004cd67f060e1 in ?? ()
# (... etc ...)
So my question is the following:
How do I cast correctly from the Local value"v8::Arguments& args" to "v8::Handle<v8::Value>*" for usage with the Call() method of v8::Function?
If I want to use the args[a] directly in the loop, compiler errors are thrown for the differing signature of v8::Function::Call, which is correct due to args being a Local Value. The signature of v8::Function::Call is the following:
v8::Local<v8::Value> v8::Function::Call(v8::Handle<v8::Object>, int, v8::Handle <v8::Value>*)
// Edit: Updated passargs[false index]
You have error there:
v8::Handle<v8::Value> passargs[1];
passargs[1/* SHOULD BE 0!!!*/] =
v8::String::New(*v8::String::Utf8Value(args[a]->ToString()));
So you try to access element out of array bounds.
BTW: v8::Local<> is inherited from v8::Handle<> so you don't need any magic to convert Local to Handle.
Edit: Most of v8 features require not only v8::HandleScope but also v8::Context::Scope seems you need to create context scope too.
You can get valid context form args:
Local<Object> self = args.Holder();
Persistent<Context> context(self->CreationContext());
Then create handle scope and context scope:
Context::Scope work_in_context_scope(context);
HandleScope work_in_this_function_scope;
Then do you job.