0

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.

  • What do you mean with "compiled JS". JavaScript is an interpreted scripting language, it doesn't get compiled. – Eric Mar 07 '22 at 09:31
  • well from what I can tell v8 makes a tree of functions and variables, and the function that does this is called compile. which is why I said "compiled JS". I really just ment is **v8::Local->Global**, which is the tree. – Luna Le Tuna Mar 07 '22 at 09:41
  • What exactly doesn't work in the 2019 example with your version of V8? V8 hasn't changed much since 2019? Were you able to load the V8 engine and compile the script? Ie get a `v8::Script` reference? – mmomtchev Mar 07 '22 at 10:53
  • For the most part, I wasn't able to get a v8::string into Context->Global->Get; also I couldn't find any documentation that explained what arguments v8::function->call needed and what they did. – Luna Le Tuna Mar 07 '22 at 16:34

1 Answers1

4

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;
}
mmomtchev
  • 2,497
  • 1
  • 8
  • 23
  • 1
    Nice! If you want to improve the sample code, replace most of the `ToLocalChecked()` with proper error handling. – jmrk Mar 07 '22 at 14:56