Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a v8 javascript function from c++ with an argument

I am working with c++ and v8, and have run into the following challenge: I want to be able to define a function in javascript using v8, then call the function later on through c++. Additionally, I want to be able to pass an argument to the javascript function from c++. I think the following sample source code would explain it best. Check towards the end of the sample code to see what I am trying to accomplish.

#include <v8.h>
#include <iostream>
#include <string>
#include <array>

using namespace v8;

int main(int argc, char* argv[]) {

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

    // Create a new context.
    Persistent<Context> context = Context::New();
    Context::Scope context_scope(context);
    Handle<String> source;
    Handle<Script> script;
    Handle<Value> result;

    // Create a string containing the JavaScript source code.
    source = String::New("function test_function(test_arg) { var match = 0;if(test_arg[0] == test_arg[1]) { match = 1; }");

    // Compile the source code.
    script = Script::Compile(source);

    // What I want to be able to do (this part isn't valid code..
    // it just represents what I would like to do.
    // An array is defined in c++ called pass_arg,
    // then passed to the javascript function test_function() as an argument
    std::array< std::string, 2 > pass_arg = {"value1", "value2"};
    int result = script->callFunction("test_function", pass_arg);

}

Any tips?

UPDATE:

Based on the advice given, I have been able to put together the following code. It has been tested and works:

#include <v8.h>
#include <iostream>
#include <string>

using namespace v8;

int main(int argc, char* argv[]) {

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

// Create a new context.
Persistent<Context> context = Context::New();

//context->AllowCodeGenerationFromStrings(true);

// Enter the created context for compiling and
// running the hello world script.
Context::Scope context_scope(context);
Handle<String> source;
Handle<Script> script;
Handle<Value> result;


// Create a string containing the JavaScript source code.
source = String::New("function test_function() { var match = 0;if(arguments[0] == arguments[1]) { match = 1; } return match; }");

// Compile the source code.
script = Script::Compile(source);

// Run the script to get the result.
result = script->Run();
// Dispose the persistent context.
context.Dispose();

// Convert the result to an ASCII string and print it.
//String::AsciiValue ascii(result);
//printf("%s\n", *ascii);

Handle<v8::Object> global = context->Global();
Handle<v8::Value> value = global->Get(String::New("test_function"));
Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(value);
Handle<Value> args[2];
Handle<Value> js_result;
int final_result;

args[0] = v8::String::New("1");
args[1] = v8::String::New("1");

js_result = func->Call(global, 2, args);
String::AsciiValue ascii(js_result);

final_result = atoi(*ascii);

if(final_result == 1) {

    std::cout << "Matched\n";

} else {

    std::cout << "NOT Matched\n";

}

return 0;

}
like image 956
user396404 Avatar asked Jul 08 '12 21:07

user396404


3 Answers

I haven't tested this, but it's possible that something like this will work:

// ...define and compile "test_function"

Handle<v8::Object> global = context->Global();
Handle<v8::Value> value = global->Get(String::New("test_function")); 

if (value->IsFunction()) {
    Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(value);
    Handle<Value> args[2];
    args[0] = v8::String::New("value1");
    args[1] = v8::String::New("value2");

    Handle<Value> js_result = func->Call(global, 2, args);

    if (js_result->IsInt32()) {
        int32_t result = js_result->ToInt32().Value();
        // do something with the result
    }
}

Edit:

It looks like your javascript function expects a single argument (consisting of an array of two values), but it kinda looks like we're calling func by passing in two arguments.

To test this hypothesis, you could change your javascript function to take two arguments and compare them, e.g.:

function test_function(test_arg1, test_arg2) { 
  var match = 0; 
  if (test_arg1 == test_arg2) { 
    match = 1; 
  } else { 
    match = 0; 
  } 
  return match; 
}
like image 169
Nate Kohl Avatar answered Oct 18 '22 06:10

Nate Kohl


For new version of v8, you can use v8::Object::CallAsFunction or v8::Function::Call to call a javascript function. Here is an example for recent version (7.4.x)

#include <iostream>
#include <libplatform/libplatform.h>
#include <v8.h>

int main(int argc, char* argv[])
{
    v8::V8::InitializeICUDefaultLocation(argv[0]);
    v8::V8::InitializeExternalStartupData(argv[0]);
    std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
    v8::V8::InitializePlatform(platform.get());
    v8::V8::Initialize();

    v8::Isolate::CreateParams createParams;
    createParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
    v8::Isolate* isolate = v8::Isolate::New(createParams);

    std::cout << v8::V8::GetVersion() << std::endl;

    {
        v8::Isolate::Scope isolate_scope(isolate);
        v8::HandleScope handle_scope(isolate);

        v8::Local<v8::Context> context = v8::Context::New(isolate);
        v8::Context::Scope context_scope(context);

        v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, "var foo=function(){return 'foo get called';}");
        v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();

        v8::TryCatch tryCatch(isolate);
        v8::MaybeLocal<v8::Value> result = script->Run(context);
        if (result.IsEmpty()) {
            v8::String::Utf8Value e(isolate, tryCatch.Exception());
            std::cerr << "Exception: " << *e << std::endl;
        } else {
            v8::String::Utf8Value r(isolate, result.ToLocalChecked());
            std::cout << *r << std::endl;
        }

        v8::Local<v8::Value> foo_value = context->Global()->Get(v8::String::NewFromUtf8(isolate, "foo"));
        if (foo_value->IsFunction()) {
            v8::Local<v8::Value> foo_ret = foo_value->ToObject(isolate)->CallAsFunction(context, context->Global(), 0, nullptr).ToLocalChecked();
            v8::String::Utf8Value utf8Value(isolate, foo_ret);
            std::cout << "CallAsFunction result: " << *utf8Value << std::endl;

            v8::Local<v8::Object> foo_object = foo_value->ToObject(isolate);
            v8::Local<v8::Value> foo_result = v8::Function::Cast(*foo_object)->Call(context, context->Global(), 0, nullptr).ToLocalChecked();
            std::cout << "Call result: "  << *(v8::String::Utf8Value(isolate, foo_result)) << std::endl;
        } else {
            std::cerr << "foo is not a function" << std::endl;
        }
    }

    isolate->Dispose();
    v8::V8::Dispose();
    v8::V8::ShutdownPlatform();
    delete createParams.array_buffer_allocator;

    return EXIT_SUCCESS;
}
like image 25
alijandro Avatar answered Oct 18 '22 05:10

alijandro


Another simpler method is as follows:

Handle<String> code = String::New(
  "(function(arg) {\n\
     console.log(arg);\n\
    })");
Handle<Value> result = Script::Compile(code)->Run();
Handle<Function> function = Handle<Function>::Cast(result);

Local<Value> args[] = { String::New("testing!") };
func->Call(Context::GetCurrent()->Global(), 1, args);

Essentially compile some code that returns an anonymous function, then call that with whatever arguments you want to pass.

like image 2
jkp Avatar answered Oct 18 '22 07:10

jkp