Calling on V8 experts. I am embedding V8 in a project of mine and I am running into issues modularizing my code. The simplest example of this is compiling and running a small script that prints "Hello World!" from a C++ function. The working version is as follows:
void testV8(const v8::FunctionCallbackInfo<v8::Value>& args) {
printf("Hello World!\n");
}
void working() {
v8::Isolate* isolate = nullptr;
std::string code = "testV8();";
{
// Basic initialization
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
v8::Isolate::CreateParams initOptions;
initOptions.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
isolate = v8::Isolate::New(initOptions);
isolate->Enter();
// Create context
v8::HandleScope handleScope(isolate);
auto global = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
// Enter context
v8::Context::Scope contextScope(context);
// Bind function
v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(isolate, &testV8);
auto name = v8::String::NewFromUtf8(isolate, "testV8").ToLocalChecked();
context->Global()->Set(context, name, ft->GetFunction(context).ToLocalChecked());
// Run script
auto execCode = v8::String::NewFromUtf8(isolate, code.c_str()).ToLocalChecked();
v8::Local<v8::Script> script;
if (v8::Script::Compile(context, execCode).ToLocal(&script)) {
v8::Local<v8::Value> result;
script->Run(context).ToLocal(&result);
}
}
}
When I try to split the code up into manageable scopes (mimicking wrapper classes I want to build later), I get a crash upon script compilation:
void testV8(const v8::FunctionCallbackInfo<v8::Value>& args) {
printf("Hello World!\n");
}
void failing() {
v8::Isolate* isolate = nullptr;
std::string code = "testV8();";
v8::Persistent<v8::Context> persistentContext;
{
// Basic initialization
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
v8::Isolate::CreateParams initOptions;
initOptions.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
isolate = v8::Isolate::New(initOptions);
isolate->Enter();
// Create context
v8::HandleScope handleScope(isolate);
auto global = v8::ObjectTemplate::New(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
// Save context
persistentContext.Reset(isolate, context);
}
{
// Rebuild scopes and enter context
v8::Locker locker(isolate);
v8::HandleScope handleScope(isolate);
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate, persistentContext);
v8::Context::Scope contextScope(context);
// Bind function
v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New(isolate, &testV8);
auto name = v8::String::NewFromUtf8(isolate, "testV8").ToLocalChecked();
context->Global()->Set(context, name, ft->GetFunction(context).ToLocalChecked());
}
{
// Rebuild scopes and enter context
v8::Locker locker(isolate);
v8::HandleScope handleScope(isolate);
v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate, persistentContext);
v8::Context::Scope contextScope(context);
// Run script
auto execCode = v8::String::NewFromUtf8(isolate, code.c_str()).ToLocalChecked();
v8::Local<v8::Script> script;
if (v8::Script::Compile(context, execCode).ToLocal(&script)) {
v8::Local<v8::Value> result;
script->Run(context).ToLocal(&result);
}
}
}
Building on xCode and running on this code on a Macbook. I can't tell if I'm doing anything wrong or if there is some bug with the contexts. Research online leads me to believe that using persistent contexts this way is fine to keep the same context alive between scopes. What's going wrong here?
The problem is that the unique_ptr for the platform is getting destroyed once the scope ends. Moving it into the parent scope fixes it.
Related
I need a way to invoke JS callbacks from a C library that uses contexts.
Here's an example:
const ctx1 = mylib_init();
mylib_set_event_callback(ctx1, () => {
console.log("EVENT");
});
Napi::FunctionReference cb;
bool done = false; // Used to prevent crash on multithreading.
// TSFN would obviously be used; this is just to shorten it.
extern "C" void onEvent(mylib_t* handle, void* userdata) {
if (cb != nullptr && !done) {
done = true;
cb.Call({});
}
}
Napi::Value MyWrapper::SetEventCallback(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::Object global = env.Global();
// info[0] = mylib_t* previously converted to a BigInt.
// info[1] = JS callback
mylib_t* handle = convertJSBigIntToHandle(info[0]);
r_cb = Napi::Persistent(info[1].As<Napi::Function>());
const auto ret = mylib_set_callback(handle, onEvent, nullptr);
return env.Null();
}
This works (the JS callback is run), but the problem is that the callback is global.
If I have ctx2 and call mylib_set_event_callback again, it will overwrite the callback from ctx1.
How can I convert this so that callbacks for ctx1 and ctx2 will both be called?
Your callback is global because it is in a global variable.
You should place the Napi::FunctionReference in the mylib_t structure.
If you cannot modify that structure, I see that you can pass a a context pointer in userdata - you are passing nullptr. Create dynamically a Napi::FunctionReference with new and pass this pointer so that you can have it in onEvent.
You should also properly the reference when the structure is destroyed or the function is replaced.
This seems like a problem that requires some JS expertize that I'm apparently not in posses.
I'm writing a scripting module for an app. The scripting and the app are in Javascript.
It will be used by developers to provide scripting extensions to various modules that run on demand or when triggered by something.
I've got a request to alter the way it works in order to simplify the coding of the scripts.
And I'm kinda stuck since I don't know how to proxy local variables inside the script into an external object.
This is a sample of what works currently:
// this is part of the app developer API
function compileScript(userScript, argSignature) {
let _scriptFunc_ = null;
/* scripting API - there are available in scripts */
const print = function(message, window) {
const msg = document.createElement("span")
msg.innerText = message;
document.getElementById("output").append(msg)
document.getElementById("output").append(document.createElement("br"))
};
/* end Scripting API section */
try {
const scriptSource = `
(async () => {
try {
${userScript}
} catch (err) {
//_errEmit.fire(err)
}
})()
`; // wrap the execution in async so they can use await in their userScript
// argument signatures are defined by the module that "compiles" the script
// they call it with those and use them inside the userScript
eval("_scriptFunc_ = function(" + argSignature + ") {\n" + scriptSource + "\n}");
}
catch (err) {
//EvtScriptEmitEvalError.fire(err); // script "compilation" exception
return null;
}
return _scriptFunc_.bind(this);
}
// USAGE
// have some context with "variables" inside accessible by the script
// changes to this might be propagated elsewhere if it has property getters/setter
let scriptData = {
something: 10
};
// define the script
let userScript = `
for (let i = 0; i < count; i++) {
this.something++; // this changes scriptData
print(this.something)
}
`
// "compile" and run
const script = compileScript.call(scriptData, userScript, "count")
script(5) // output: 11,12,13,14,15
console.log(scriptData.something) // 15
<pre id="output">
</pre>
Note the usage of "this." inside the script to refer to scriptData members.
The request they have is to access the properties of the scriptData object inside the script by simply referring to its properties as if they were variables inside the script.
This is how they would want to write it (note there is no "this." before something):
let userScript = `
for (let i = 0; i < count; i++) {
something++; // this changes scriptData
print(something)
}
`
They are fine with possible name collisions between parameters and members of scriptData, it is developer work to set that up correctly.
My problem, tough, is that I don't have any idea how to modify "compileScript" in order to inject members of scriptData as plain variables inside the script is such a way that they proxy to the scriptData object.
It is easy to define a function in the scope of compileScript like "print", but I have no ideas on how to do this concept of "proxy variables".
"with" is not available in strict mode which the app runs in.
JS Proxy class does not seem useful.
Deconstructing scriptData into variables can be done but those variables are no longer going to the scriptData object, they are local.
Defining property getters/setters is available only for objects, not for the compileScript function...
I cannot modify the scriptData object, the user passes it as is. I can only tweak the generation of the script so that it behaves as required.
It should also work in a web worker (so no global scope / window), since a script could be triggered at the completion of a web worker.
Any ideas?
You're looking for the with statement. It's the only way to make variable assignments become interceptable as property assignments on your scriptData object (apart from going the full way to transpiling the script code with something like babel).
If writable variables are not necessary, you could also use this technique to inject values into the scope of a function. I would recommend to use it anyway, instead of eval.
/* scripting API - there are available in scripts */
const api = {
print(message, window) {
const msg = document.createElement("p");
msg.textContent = message;
document.getElementById("output").append(msg);
},
};
/* end Scripting API section */
// this is part of the app developer API
function compileScript(context, userScript, parameters) {
const scriptSource = `
const { ${Object.keys(api).join(', ')} } = api;
with (this) {
return async (${parameters.join(', ')}) => {
"use strict";
try {
${userScript}
} catch (err) {
//_errEmit.fire(err)
}
};
}
`;
try {
return new Function('api', scriptSource).call(context, api);
} catch (err) {
console.warn(err); // script "compilation" exception
return null;
}
}
let scriptData = {
something: 10
};
// define the script
let userScript = `
for (let i = 0; i < count; i++) {
something++; // this changes scriptData
print(something)
}
`;
// "compile" and run
const script = compileScript(scriptData, userScript, ["count"])
script(5) // output: 11,12,13,14,15
console.log(scriptData.something) // 15
<pre id="output">
</pre>
I'm trying to embed an custom function to my project, that uses the V8 engine, and apparently I can't make it working. I've used code, that I've found, but it seems to be outdated, or I just do it wrong. My point is to include a custom javascript file. My current code (for testing) is this :
HandleScope handle_scope(isolate);
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
global->Set(v8::String::NewFromUtf8(isolate, "test", v8::NewStringType::kNormal).ToLocalChecked(),
v8::FunctionTemplate::New(isolate, test));
Handle<Context> context = Context::New(isolate);
Persistent<Context> persistent_context(isolate, context);
Context::Scope context_scope(context);
const char* data = "test";
Handle<String> source = String::NewFromUtf8(isolate, data);
Handle<Script> script = Script::Compile(source);
if (!script.IsEmpty())
Handle<Value> result = script->Run();
Test Code (obviously just for testing):
void test(const v8::FunctionCallbackInfo<v8::Value>& args) {
MessageBoxA(NULL,"test", "", 0);
}
But the engine returns this error :
Uncaught ReferenceError: test is not defined
So my question is if I even do it correct, I would be able to make the including myself I hope, but I just can't get the function to get executed.
Here's some code from a project that works:
Isolate::Scope iscope( isolate_ );
HandleScope hs( isolate_ );
Local<Object> test = Object::New(isolate_);
test->Set(String::NewFromUtf8(isolate_, "javaScriptFunctionName"), Function::New(isolate_, CPP_FN_CALLBACK));
global_template->Set(String::NewFromUtf8(isolate_, "test"), test);
That will result in an object for window.test with a function called window.test.javaScriptFunctionName()
I am facing a strange case of closure. In the following code snippet, defaultConfig is maintained as a closure variable even though it has no use left once test has been initialized. To insure that config contains only copies of defaultConfig's members, I have used slice to process its elements, but it still remains in memory, which can be verified by placing a breakpoint inside render. Also, it can't be found in closure if it has not been touched or used at all. I am unable to find any reason. I am using Chrome.
function test() {
"use strict";
var defaultConfig = ["as", "lx", "ms", "sw"];
return view;
function view(config) {
if (!config) {
config = [];
for (var attr in defaultConfig) {
config.push(defaultConfig[attr].slice(0,1));
}
}
this.render = function() {
console.log(config);
}
}
}
I have a C++ method (which role is killing some processes). She needs 2 parameters : a hostname and a port.
On the other hand, I am developing a web-application (using Nodejs and AngularJS), running on Google Chrome.
When I click on a button through the browser, I would like to be able to call the C++ function, through my app.js file.
I haven't found how to "bind" javascript with C++.
EDIT : I think this link could be very useful
How can I use a C++ library from node.js?
You can use Google's V8. V8 is Google's open source JavaScript engine.
V8 can run standalone, or can be embedded into any C++ application.
http://code.google.com/p/v8/
Following example from github demonstrates, binding a C++ class with Google V8.
v8_wrap_class.cpp - Author is nicholas
/*
* v8_wrap_class.cpp
*
* Created on: 14/01/2013
* Author: nicholas
* License: public domain
*/
#include <v8.h>
#include <cstdio>
#include <string>
#include <stdexcept>
#include <memory>
using namespace v8;
/*
var Simple = function(v)
{
this.value = v;
}
Simple.prototype.func = function()
{
alert(this.value);
}
var obj = new Simple(4);
obj.func();
*/
struct Simple
{
double value;
Simple(double v)
: value(v)
{
fprintf(stderr, "Simple::ctor\n");
}
void func()
{
fprintf(stderr, "Simple::func(%f)\n", value);
}
~Simple()
{
fprintf(stderr, "Simple::dtor\n");
}
};
namespace js
{
/*
* Retrieve the c++ object pointer from the js object
*/
template <typename T>
T* unwrap(const Arguments& args)
{
auto self = args.Holder();
auto wrap = Local<External>::Cast(self->GetInternalField(0));
return static_cast<T*>(wrap->Value());
}
/*
* Construct a new c++ object and wrap it in a js object
*/
template <typename T, typename... Args>
Persistent<Object> make_object(Handle<Object> object, Args&&... args)
{
auto x = new T(std::forward<Args>(args)...);
auto obj = Persistent<Object>::New(object);
obj->SetInternalField(0, External::New(x));
obj.MakeWeak(x, [](Persistent<Value> obj, void* data)
{
auto x = static_cast<T*>(data);
delete x;
obj.Dispose();
obj.Clear();
});
return obj;
}
}
void bind_Simple(Local<Object> global)
{
// Name the class in js
auto name = String::NewSymbol("Simple");
auto tpl = FunctionTemplate::New([&](const Arguments& args) -> Handle<Value>
{
if (!args.IsConstructCall())
return ThrowException(String::New("Cannot call constructor as function"));
HandleScope scope;
// Read and pass constructor arguments
js::make_object<Simple>(args.This(), args[0]->NumberValue());
return args.This();
});
tpl->SetClassName(name);
tpl->InstanceTemplate()->SetInternalFieldCount(1);
auto prototype = tpl->PrototypeTemplate();
// Add object properties to the prototype
// Methods, Properties, etc.
prototype->Set(String::New("func"), FunctionTemplate::New([](const Arguments& args) -> Handle<Value>
{
auto s = js::unwrap<Simple>(args);
s->func();
return {};
})->GetFunction());
auto constructor = Persistent<Function>::New(tpl->GetFunction());
global->Set(name, constructor);
}
int main()
{
std::string js_source = R"(
for(var i = 0; i < 1000; ++i)
{
var s = new Simple(4);
s.value = 5;
s.func();
}
)";
/*
* This code is mostly uninteresting.
* Just run the vm with the script provided.
*/
{
HandleScope handle_scope;
Handle<ObjectTemplate> global_template = ObjectTemplate::New();
Persistent<Context> context = Context::New(0, global_template);
Context::Scope context_scope(context);
auto global = context->Global();
// Wrap the class and bind to the global scope.
bind_Simple(global);
{
HandleScope handle_scope;
TryCatch trycatch;
Local<String> source = String::New(js_source.c_str(), js_source.size());
Local<Script> script = Script::Compile(source);
if (script.IsEmpty())
{
Handle<Value> exception = trycatch.Exception();
String::AsciiValue exception_str(exception);
throw std::runtime_error(*exception_str);
}
Local<Value> result = script->Run();
if (result.IsEmpty())
{
Local<Value> exception = trycatch.Exception();
String::AsciiValue exception_str(exception);
throw std::runtime_error(*exception_str);
}
}
context.Dispose();
context.Clear();
}
// Run the GC until there is nothing to reclaim.
while (!V8::IdleNotification())
;
return 0;
}
This answer gives four appraoches to using C++ in javascript. The methods shown try to keep the original C++ in a standard and simple C++ implementation.
Two methods using WASM and two using SWIG and JRPC are used to give examples and ideas on executing C++ in javascript. In detail, this answer gives one example for executing in the browser and one for executing in nodejs using WASM. The end of this answer also lists two other ways to execute C++ in javascript, one of which calls nodejs from the browser which is slightly more complex but possible using jrpc-oo.
If you want to execute in the browser or nodejs, then you can compile your C++ to WASM and load that module in the browser or nodejs, executing the C++ there. This WASM repo exemplifies how to do that. I will expand the key code in the WASM repo here.
Create some C++ code and declare your WASM binding, for example (from the files include/Test.H and src/Test.C) :
class Test {
public:
void sayHello(){
printf("Hi, my name is test\n");
}
};
#include <emscripten/bind.h>
EMSCRIPTEN_BINDINGS(Test_ex) {
emscripten::class_<Test>("Test")
.function("sayHello", &Test::sayHello)
;
}
Compile that down and you can now run that in nodejs (from the file nodejs/WASMTestNode.js) :
libWASM = require('./libwasmNode.js');
libWASM().then((mod)=>{
libWASM = mod;
let test = new libWASM.Test;
test.sayHello();
});
In the browser you can use the WASM code as well. To do that firs import the WASM library (from the file webcomponent/libWASM.js) :
import modProm from './libwasm.js';
The create your webcomponent and compile your WASM then execute the C++ method Test::sayHello (from the file webcomponent/libWASM.js) :
import { LitElement } from 'lit';
export class LibWASM extends LitElement {
constructor() {
super();
modProm().then((mod)=>{
this.libWASM = mod; // for rendered wasm that delay
this.WASMReady();
})
}
WASMReady(){
console.log('LibWASM.libWASM module compiled and ready to go.')
let test = new this.libWASM.Test;
test.sayHello();
}
}
This code example is implemented in the reference repo.
Alternatively a third way to do this is to only use C++, SWIG and nodejs from this reference repo.
If you want to execute nodejs from the browser or use a a different method of integrating C++ into nodejs, you can also look at this SWIG and jrpc-oo reference for doing the same thing, but not only in nodejs, also from the browser calling nodejs.
There are also other ways to execute C++ form javascript, however the methods demonstrated in this answer are reasonably straightforward and either rely on WASM binding or SWIG abstraction which leaves your original code as standard C++.