Google's v8 documentation describes how to add a global function to a JavaScript context. We can implement a printf-like function quite easily using the new lambda feature from C++11:
Handle<ObjectTemplate> global = ObjectTemplate::New();
global->Set(String::New("print"), FunctionTemplate::New(
[](const v8::Arguments &args) -> v8::Handle<v8::Value>
{
v8::String::AsciiValue ascii(args[0]);
std::cout << *ascii << "\n";
} ));
Persistent<Context> context = Context::New(NULL, global);
This works well for any global JavaScript function that is either stateless or references a global C++ variable (i.e. std::cout
). But what if we want our global JavaScript function to reference a non-global C++ variable? For example, suppose we are creating several different JavaScript contexts each with its own global print
function that uses a different C++ std::ostream
? If v8 function templates used std::function
objects instead of function pointers, the we would do something like this:
Persistent<Context> create_context(std::ostream &out)
{
Handle<ObjectTemplate> global = ObjectTemplate::New();
global->Set(String::New("print"), FunctionTemplate::New(
[&out](const v8::Arguments &args) -> v8::Handle<v8::Value>
{
v8::String::AsciiValue ascii(args[0]);
out << *ascii << "\n";
} ));
return Context::New(NULL, global);
}
Unfortunately, v8 does not seem to support this. I assume (hope?) that v8 has a way of doing something functionally equivalent, but I find myself mystified by the Doxygen for v8::FunctionTemplate
. Would anyone who has attempted something similar be willing to distill the process down into something more understandable? I would also like to learn how to create a global instance of a JavaScript object that is bound to an existing, non-global instance of a C++ object.
In answer to my own question... the key is to realize that v8::Arguments is not simply an array of arguments. It also contains the exceedingly useful Callee()
and Data()
methods. If the function is a method of a JavaScript object then Callee()
can, I think, be used to get ahold of whatever instance of that object the method was called on. Useful state information could then be stored in the object instance. You can also supply a data handle, which may point to any C++ object through void*
, when adding a function template to an object. This function-specific data handle may then be accessed through the Data()
method.
Below is a reasonably complete example of what I was trying to do in the question using v8::Arguments::Data()
. Hopefully this will be useful to anyone who wants to do something similar. If you have an alternative strategy you like (and I am certain there is more than one way of doing this), please feel free to add it in another answer!
#include <iostream>
#include <ostream>
#include <v8.h>
// add print() function to an object template
void add_print(v8::Handle<v8::ObjectTemplate>& ot, std::ostream* out)
{
// add function template to ot
ot->Set(v8::String::New("print"), v8::FunctionTemplate::New(
// parameter 1 is the function callback (implemented here as a lambda)
[](const v8::Arguments& args)->v8::Handle<v8::Value>
{
// recover our pointer to an std::ostream from the
// function template's data handle
v8::Handle<v8::External> data = v8::Handle<v8::External>::Cast(args.Data());
std::ostream* out = static_cast<std::ostream*>(data->Value());
// verify that we have the correct number of function arguments
if ( args.Length() != 1 )
return v8::ThrowException(v8::String::New("Too many arguments to print()."));
// print the ascii representation of the argument to the output stream
v8::String::AsciiValue ascii(args[0]);
*out << *ascii << "\n";
// like 'return void;' only in JavaScript
return v8::Undefined();
},
// parameter 2 is the data handle with the pointer to an std::ostream
v8::External::New(out)
));
}
int main()
{
// create a stack-allocated handle scope
v8::HandleScope handle_scope;
// create a global template
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
// add a print() function using std::cout to the global template
add_print(global, &std::cout);
// create a context
v8::Persistent<v8::Context> context = v8::Context::New(nullptr, global);
// enter the created context
v8::Context::Scope context_scope(context);
// create a string containing the JavaScript source code
v8::Local<v8::String> source = v8::String::New("print('1 + 1 = ' + (1 + 1));");
// compile the source code
v8::Local<v8::Script> script = v8::Script::Compile(source);
// run the script
script->Run();
// dispose of the persistent context
context.Dispose();
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With