Calling a v8 javascript function from c++ with an argument
Asked Answered
A

3

38

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;

}
Agrapha answered 8/7, 2012 at 21:53 Comment(3)
I assume that IsInt32 returns true, but Int32Value returns 0?Justajustemilieu
Check out my edit -- maybe we're not passing enough parameters...Justajustemilieu
You have a mistake in your code: you dispose the current context and after you are using it. You must put the dispose line at the end of your program.Wixted
J
20

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; 
}
Justajustemilieu answered 8/7, 2012 at 23:58 Comment(5)
It seems to be working. I'm having trouble using js_result though. The part where it says if(js_result.IsInt32) is giving the following error during compile time: error: ‘class v8::Handle<v8::Value>’ has no member named ‘Int32’|Agrapha
@user396404: maybe try js_result->IsInt32() instead?Justajustemilieu
That did work. The code compiles, but it is not returning a value of 1 even if the values match :/Agrapha
Thanks for all the help. It turns out I had to reference the variables using the arguments array, instead of test_arg1 and test_arg2. For anyone reading this in the future, the part where it says Call(global, 2, args), the 2 represents the length of the args array. I posted the revised and working code in my original answer. Thanks for all the help!Agrapha
Any chance for an updated version of this code? It doesn't appear to compile with the V8 in 2018.Erotic
K
12

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;
}
Kawasaki answered 4/6, 2019 at 12:24 Comment(0)
S
2

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.

Sexual answered 4/6, 2013 at 17:50 Comment(2)
v8::ScriptCompiler::CompileFunctionInContext does the "wrap your code in a function" bit for you.Microdont
Any chance for an updated version of this code? It doesn't appear to compile with the V8 in 2018.Erotic

© 2022 - 2024 — McMap. All rights reserved.