Search code examples
c++asynchronousv8embedded-v8libv8

V8 javascript how to make asynchronous calls


I've been trying to figure out how to make asynchronous calls in V8, without luck. The example javascript code I'm trying to have run is:

function test ()
{
    logMessage ('asynchronous call made!');
}
saveFunc(test);

The saveFunc function is supposed to save the test function for use when the C++ code calls it later after the script is ran. Everytime I try this it crashes when I try to execute the function that was saved. What am I doing wrong?

I've copied my entire example code below. Thanks in advance.

Example Code:

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <iostream>
#include <string>

#include "libplatform/libplatform.h"
#include "v8.h"

using namespace v8;

Local<Context> context;
v8::Local<v8::Function> savedFunc;

class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
    virtual void* Allocate(size_t length) {
        void* data = AllocateUninitialized(length);
        return data == NULL ? data : memset(data, 0, length);
    }
    virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
    virtual void Free(void* data, size_t) { free(data); }
};

std::string parseV8Value(v8::Local<v8::Value> str)
{
    if (str.IsEmpty() == true)
        return ("");

    v8::String::Utf8Value newStr(str);

    return (*newStr);
}

void logMessage(const v8::FunctionCallbackInfo<v8::Value> &args)
{
    std::string applicationSource = parseV8Value(args[0]);
    std::cout << applicationSource << "\n";
}

void getInput(const v8::FunctionCallbackInfo<v8::Value> &args)
{
    v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast (args[0]);
    std::string input = "";

    std::cin >> input;

    v8::Local<v8::Value> *args2 = new v8::Local<v8::Value>[1];
    args2[0] = v8::String::NewFromUtf8 (args.GetIsolate (), input.c_str ());

    func->Call (context->Global (), 1, args2);

    delete []args2;
    args2 = NULL;
}

void saveFunc(const v8::FunctionCallbackInfo<v8::Value> &args)
{
    savedFunc = v8::Local<v8::Function>::Cast(args[0]);
}

int main(int argc, char* argv[]) {
    // Initialize V8.
    V8::InitializeICU();
    V8::InitializeExternalStartupData(argv[0]);
    Platform* platform = platform::CreateDefaultPlatform();
    V8::InitializePlatform(platform);
    V8::Initialize();

    // Create a new Isolate and make it the current one.
    ArrayBufferAllocator allocator;
    Isolate::CreateParams create_params;
    create_params.array_buffer_allocator = &allocator;
    Isolate* isolate = Isolate::New(create_params);
    {
        Isolate::Scope isolate_scope(isolate);

        // Create a stack-allocated handle scope.
        HandleScope handle_scope(isolate);

        // Create a new context.
        v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
        global->Set(v8::String::NewFromUtf8(isolate, "logMessage"),
            v8::FunctionTemplate::New(isolate, logMessage));
        global->Set(v8::String::NewFromUtf8(isolate, "getInput"),
            v8::FunctionTemplate::New(isolate, getInput));
        global->Set(v8::String::NewFromUtf8(isolate, "saveFunc"),
            v8::FunctionTemplate::New(isolate, saveFunc));
        context = Context::New(isolate, NULL, global);

        // Enter the context for compiling and running the hello world script.
        Context::Scope context_scope(context);

        // Create a string containing the JavaScript source code.
        Local<String> source =
            String::NewFromUtf8(isolate, 
            "function test (){ logMessage ('asynchronous call made!'); }saveFunc(test);",,
                NewStringType::kNormal).ToLocalChecked();

        // Compile the source code.
        Local<Script> script = Script::Compile(context, source).ToLocalChecked();

        // Run the script to get the result.
        Local<Value> result = script->Run(context).ToLocalChecked();

        v8::Local<v8::Value> *args = new v8::Local<v8::Value>[0];
        savedFunc->Call(context->Global(), 0, args);

        delete []args;
        args = NULL;
    }

    // Dispose the isolate and tear down V8.
    isolate->Dispose();
    V8::Dispose();
    V8::ShutdownPlatform();
    delete platform;
    return 0;
}

Solution

  • Ok, so you have to use a Persistent handle when saving the function in saveFunc. Also, you have to make sure that when you're accessing context->Global, that you're still within the HandleScope. Here's the corrected code:

    // Copyright 2015 the V8 project authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    #include <iostream>
    #include <string>
    
    #include "libplatform/libplatform.h"
    #include "v8.h"
    
    using namespace v8;
    
    Isolate* isolate = NULL;
    Local<Context> context;
    v8::Persistent<v8::Function> *savedFunc = NULL;
    
    class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
    public:
        virtual void* Allocate(size_t length) {
            void* data = AllocateUninitialized(length);
            return data == NULL ? data : memset(data, 0, length);
        }
        virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
        virtual void Free(void* data, size_t) { free(data); }
    };
    
    std::string parseV8Value(v8::Local<v8::Value> str)
    {
        if (str.IsEmpty() == true)
            return ("");
    
        v8::String::Utf8Value newStr(str);
    
        return (*newStr);
    }
    
    void logMessage(const v8::FunctionCallbackInfo<v8::Value> &args)
    {
        std::string applicationSource = parseV8Value(args[0]);
        std::cout << applicationSource << "\n";
    }
    
    void getInput(const v8::FunctionCallbackInfo<v8::Value> &args)
    {
        v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast (args[0]);
        std::string input = "";
    
        std::cin >> input;
    
        v8::Local<v8::Value> *args2 = new v8::Local<v8::Value>[1];
        args2[0] = v8::String::NewFromUtf8 (args.GetIsolate (), input.c_str ());
    
        func->Call (context->Global (), 1, args2);
    
        delete []args2;
        args2 = NULL;
    }
    
    void saveFunc(const v8::FunctionCallbackInfo<v8::Value> &args)
    {
        v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(args[0]);
        savedFunc = new v8::Persistent<v8::Function>();
        savedFunc->Reset(isolate, func);
    }
    
    int main(int argc, char* argv[]) {
        // Initialize V8.
        V8::InitializeICU();
        V8::InitializeExternalStartupData(argv[0]);
        Platform* platform = platform::CreateDefaultPlatform();
        V8::InitializePlatform(platform);
        V8::Initialize();
    
        // Create a new Isolate and make it the current one.
        ArrayBufferAllocator allocator;
        Isolate::CreateParams create_params;
        create_params.array_buffer_allocator = &allocator;
        isolate = Isolate::New(create_params);
        {
            Isolate::Scope isolate_scope(isolate);
    
            // Create a stack-allocated handle scope.
            HandleScope handle_scope(isolate);
    
            // Create a new context.
            v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
            global->Set(v8::String::NewFromUtf8(isolate, "logMessage"),
                v8::FunctionTemplate::New(isolate, logMessage));
            global->Set(v8::String::NewFromUtf8(isolate, "getInput"),
                v8::FunctionTemplate::New(isolate, getInput));
            global->Set(v8::String::NewFromUtf8(isolate, "saveFunc"),
                v8::FunctionTemplate::New(isolate, saveFunc));
            context = Context::New(isolate, NULL, global);
    
            // Enter the context for compiling and running the hello world script.
            Context::Scope context_scope(context);
    
            // The "asynchronous" javascript call to make
            Local<String> source =
                String::NewFromUtf8(isolate, 
        "function test (){ logMessage ('asynchronous call made!'); }saveFunc(test);",
                    NewStringType::kNormal).ToLocalChecked();
    
            // Compile the source code.
            Local<Script> script = Script::Compile(context, source).ToLocalChecked();
    
            // Run the script to get the result.
            Local<Value> result = script->Run(context).ToLocalChecked();
    
            v8::Local<v8::Value> *args = new v8::Local<v8::Value>[0];
            v8::Local<v8::Value> recv = context->Global();
            v8::Local<v8::Function> func = savedFunc->Get(isolate);
            func->Call (recv, 0, args);
    
            delete savedFunc;
            savedFunc = NULL;
    
            delete []args;
            args = NULL;
        }
    
        // Dispose the isolate and tear down V8.
        isolate->Dispose();
        V8::Dispose();
        V8::ShutdownPlatform();
        delete platform;
        return 0;
    }