c++v8embedded-v8

Call JS functions in v8 from c++


What I want to do is call already compiled functions in JS/v8 from c++. I'm doing this for a game engine I'm writing that uses V8 as the scripting back-end.

This is kinda how a script would be formatted for my engine:

function init(){ //this gets called at the startup of the game
    print("wambo");
}

var time = 0;
function tick(delta){ //this gets called every frame
    time += delta;
    print("pop");
}

I've tried looking though this compiled documentation https://v8docs.nodesource.com/node-16.13/df/d69/classv8_1_1_context.html for a function in v8::Local<v8::Context>->Global that Get functions by name, from the compiled JS, but could not wrap my head around the keys system.

I've tried reading through Calling a v8 javascript function from c++ with an argument but the examples seem outdated for the latest version of v8 in 2022.


Solution

  • I did port the sample from the other answer to the latest V8 version - 10.1.72

    // 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 "libplatform/libplatform.h"
    #include "v8.h"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    const char jsCode[] = "var foo = function(arg) {"
                          "if (Math.random() > 0.5) throw new Error('er');"
                          "return arg + ' with foo from JS';"
                          "}";
    
    int main(int argc, char *argv[]) {
        // Initialize V8.
        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();
    
        // Create a new Isolate and make it the current one.
        v8::Isolate::CreateParams create_params;
        create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
        v8::Isolate *isolate = v8::Isolate::New(create_params);
        {
            v8::Isolate::Scope isolate_scope(isolate);
            v8::HandleScope handle_scope(isolate);
    
            // Create a context and load the JS code into V8
            v8::Local<v8::Context> context = v8::Context::New(isolate);
            v8::Context::Scope context_scope(context);
            v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, jsCode, v8::NewStringType::kNormal).ToLocalChecked();
            v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
            v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
            v8::String::Utf8Value utf8(isolate, result);
    
            // This is the result of the evaluation of the code (probably undefined)
            printf("%s\n", *utf8);
    
            // Take a reference to the created JS function and call it with a single string argument
            v8::Local<v8::Value> foo_value = context->Global()->Get(context, v8::String::NewFromUtf8(isolate, "foo").ToLocalChecked()).ToLocalChecked();
            if (foo_value->IsFunction()) {
                // argument (string)
                v8::Local<v8::Value> foo_arg = v8::String::NewFromUtf8(isolate, "arg from C++").ToLocalChecked();
    
                {
                    // Method 1
                    v8::TryCatch trycatch(isolate);
                    v8::MaybeLocal<v8::Value> foo_ret = foo_value.As<v8::Object>()->CallAsFunction(context, context->Global(), 1, &foo_arg);
                    if (!foo_ret.IsEmpty()) {
                        v8::String::Utf8Value utf8Value(isolate, foo_ret.ToLocalChecked());
                        std::cout << "CallAsFunction result: " << *utf8Value << std::endl;
                    } else {
                        v8::String::Utf8Value utf8Value(isolate, trycatch.Message()->Get());
                        std::cout << "CallAsFunction didn't return a value, exception: " << *utf8Value << std::endl;
                    }
                }
    
                {
                    // Method 2
                    v8::TryCatch trycatch(isolate);
                    v8::Local<v8::Object> foo_object = foo_value.As<v8::Object>();
                    v8::MaybeLocal<v8::Value> foo_result = v8::Function::Cast(*foo_object)->Call(context, context->Global(), 1, &foo_arg);
                    if (!foo_result.IsEmpty()) {
                        std::cout << "Call result: " << *(v8::String::Utf8Value(isolate, foo_result.ToLocalChecked())) << std::endl;
                    } else {
                        v8::String::Utf8Value utf8Value(isolate, trycatch.Message()->Get());
                        std::cout << "CallAsFunction didn't return a value, exception: " << *utf8Value << std::endl;
                    }
                }
            } else {
                std::cerr << "foo is not a function" << std::endl;
            }
        }
    
        // Dispose the isolate and tear down V8.
        isolate->Dispose();
        v8::V8::Dispose();
        v8::V8::ShutdownPlatform();
        delete create_params.array_buffer_allocator;
        return 0;
    }