A formatting note – I have tried to clean the code a little to work with this width of page. The original code with less dumbassery is linked in the header file here for reference.
Get the example code
from here : LINK
I know i needed one.
When i had chosen v8 as my weapon of choice i found it to be incredibly easy to grasp.
What i didnt expect is for the simplicity to grab me around the ankles and dangle
me off the edge of confusion cliff.
Before i realised i was scratching my head with a few good c++ books open wondering what
on earth i had done wrong that was giving v8 exceptions from a single line of code.
What i would advise, is try to avoid shortcuts. I was using CProxyV8, i had seen v8Juice and
both of them are really neat, but every single error i ran into was related to me not understanding
what it is v8 was actually asking for and most times it was because of the use of the wrappers.
Finding my own way
I learnt a few things along the way – using v8 from the ground up is actually easier to understand.
The first thing that helps is to look at the v8 namespace comments as they usually
reveal something important, if its related to the JS side, or if its related to the c++ side.Once i saw
that i was attempting to use a JS object as a c++ class, yea things started to make more sense.
What i needed from v8.
Now while the point and line class example is so heavily leaned on, it still didnt really help me to figure out the route i needed to take to expose certain classes to the script environment. The included sample http process and the other samples also , did things in a very straightforward and limited way. Below i will present an empty class from my codebase, with the code i used to expose and gracefully use the script side easily.
I needed to wrap and unwrap a class.
In order to expose a class to JS, i wanted to use an EXISTING C++ class, and just pass a pointer/handle to the scripts. That way i can also control what part of my game/scripts they can actually overwrite and change, and what instances of class they can manipulate. For example, i dont want multiple instances of the core, or any subsystem of the core. I just want a script side global(used loosely here) that the script writer can access (and which also protects him from accidentally overwriting one of my objects).
A template based helper function
I wrote 2 functions that are free to use , and will wrap and unwrap a class accordingly. The usage of the code provided here is shown lower down, so it might not make sense here. Wrapping involves creating a JS object from a c++ class, and maintaining a handle to manipulate it, and unwrapping involves unwrapping an object to a c++ pointer again, normally from the v8arguments handle that a function passes in on being called.
example use :
ExposeClass< somenamespace::someClass >( somev8Context, someClassInstance, v8::String::New(“someclass”), v8::ReadOnly);
-
-
//http://blog.owned.co.za v8 Javascript Helper Function ,
-
//Give this function the c++ object (an instance) to wrap,
-
//and it returns a JS object to expose things under.
-
//Note theres 2 functions here ,
-
//the top one is used by the ExposeClass function
-
-
template<class T>
-
v8::Handle<v8::Object> WrapClass(T* y)
-
{
-
// Handle scope for temporary handles,
-
v8::HandleScope handle_scope;
-
v8::Persistent<v8::ObjectTemplate> class_template_;
-
-
v8::Handle<v8::ObjectTemplate> rt = v8::ObjectTemplate::New();
-
-
//The raw template is the ObjectTemplate
-
// (that can be exposed to script too)
-
//but is maintained internally.
-
rt->SetInternalFieldCount(1);
-
-
//Create the actual template object,
-
class_template_ = v8::Persistent<v8::ObjectTemplate>::New(rt);
-
-
//Create the new handle to return, and set its template type
-
v8::Handle<v8::Object> result = class_template_->NewInstance();
-
v8::Handle<v8::External> class_ptr
-
class_ptr = v8::External::New(static_cast<T*>(y));
-
-
//Point the 0 index Field to the c++ pointer for unwrapping later
-
result->SetInternalField(0, class_ptr);
-
-
//Return the value, releasing the other handles on its way.
-
return handle_scope.Close(result);
-
}
-
-
-
template<class T>
-
v8::Handle<v8::Object> ExposeClass(v8::Persistent<v8::Context> context,
-
T* y,
-
v8::Handle<v8::Value> exposedName,
-
v8::PropertyAttribute props)
-
{
-
v8::HandleScope handle_scope;
-
-
v8::Handle<v8::Object> obj = Core::Script::WrapClass<T>(y);
-
context->Global()->Set(exposedName, obj, props);
-
-
return handle_scope.Close(obj);
-
}
-
And for unwrapping – (usually inside CALLED functions from script.
use : someNamespace::someClass* sc =
UnwrapClass< someNamespace::someClass >( myJSObjToUnwrap );
-
-
//http://blog.owned.co.za v8 Javascript Helper Function ,
-
//Give this function the v8 JS object to unwrap, and the class to unwrap to.
-
-
template<class T>
-
T* UnwrapClass(v8::Handle<v8::Object> obj)
-
{
-
v8::Handle<v8::External> field;
-
field = v8::Handle<v8::External>::Cast(obj->GetInternalField(0));
-
void* ptr = field->Value();
-
return static_cast<T*>(ptr);
-
}
-
-
How i use the above templates
Nobody is claiming magic in the code above, it might even be wrong and or, doing something badly. I just found it to work – any comments and fixes will be noted and adjusted if you comment. Now, the usage, at least MY usage of the above code was to expose c++ classes with existing c++ functions to the script context.
Step 1 – A Class to expose, with some members id like in scripts.
First, i have a c++ class declared, with some static functions that i would like to expose.
-
-
class cCore
-
{
-
-
public:
-
-
//This is a simple example,
-
//do not copy this as a real class.
-
cCore(){};
-
~cCore() {};
-
-
//We need a persistant handle to the core in scripts, a non persistant
-
//v8::Handle<v8::Object> will get lost in the handle scopes,
-
//and throw some weird exceptions to hunt down.
-
//Wrap class and unwrap class return
-
//NON persistant handles, so store your own
-
//persistant ones if you want to
-
//use it outside of the first handle scope.
-
-
//core script object, "core" in scripts.
-
v8::Persistent<v8::Object> cso_core;
-
-
//We expose some functions in our class to the scripts from c++,
-
//By wrapping an object (cso_core above) and the attaching functions
-
//to that object. Those functions, when called from javascript –
-
//end up here.
-
-
//The functions are self explanatory.
-
-
//Print some text to the console/ui console
-
static v8::Handle<v8::Value> xecho(const v8::Arguments& args);
-
//execute a javascript file
-
static v8::Handle<v8::Value> xexec(const v8::Arguments& args);
-
//exit all systems and shutdown
-
static v8::Handle<v8::Value> xexit(const v8::Arguments& args);
-
-
}; //end class declaration
-
-
Step 2 – Creating the Javascript Object , of my class.
-
-
-
//Just for clarity sake, so you can see my workflow.
-
void cScriptSystem::startup()
-
{
-
//Create a script engine for the core
-
v8::HandleScope handle_scope;
-
-
//Create a context for the system to use
-
//(i have just one at this point)
-
coreContext = v8::Context::New(NULL, global);
-
-
//Enter the main context, making it active.
-
v8::Context::Scope context_scope(coreContext);
-
-
exposeCore(); //see below
-
};
-
-
//And the code to expose the class,
-
void cScriptSystem::exposeCore()
-
{
-
v8::HandleScope handlescope;
-
-
//This will be reused to expose multiple
-
//classes, short name for easy code.
-
v8::Handle<v8::Object> cc;
-
-
//get the JS version of the C++ object,
-
//note that core is simply Core::cCore* core;
-
cc = ExposeClass< Core::cCore >(coreContext, core,
-
v8::String::New("core"),
-
v8::ReadOnly);
-
-
//set the persistant handle for use outside of this handlescospe
-
core->cso_core = v8::Persistent<v8::Object>::New(cc);
-
-
//Give the functions to the object.
-
//This makes the functions come out as
-
//core.echo(), core.exec(), core.exit() in scripts.
-
Expose(core->cso_core, v8::String::New("echo") ,
-
v8::InvocationCallback(Core::cCore::xecho));
-
Expose(core->cso_core, v8::String::New("exec") ,
-
v8::InvocationCallback(Core::cCore::xexec));
-
Expose(core->cso_core, v8::String::New("exit") ,
-
v8::InvocationCallback(Core::cCore::xexit));
-
-
};
-
-
Step 3 – Actually handling the function calls on the c++ side.
This is where the unwrapClass comes in… If you noticed the v8::Arguments& args on the functions,
it hands in the class that the function is being called upon. So if i called core.exit() , the object
that it will pass me is the core object (our C++ object, in JS form). See the code for clarity,
-
-
v8::Handle<v8::Value> cCore::xexit(const v8::Arguments& args)
-
{
-
//args.Holder() is the object that the function was invoked upon.
-
//This means it could be exposed to 2 classes,
-
//under different types so be sure
-
//that you dont just randomly unwrap into the wrong container type.
-
-
Core::cCore* core = UnwrapClass<Core::cCore>( args.Holder() );
-
-
//The code inside the normal exit function is identical,
-
//so i can just call the normal c++ code directly.
-
core->startShutdown();
-
-
//This is to return void, basically.
-
return v8::Undefined();
-
}
-
Step 4 – The missing function.
I left out a function along the way, you may have noticed. The function is called Expose,
-
-
void Expose(v8::Handle<v8::Object> intoObject, v8::Handle<v8::Value> namev8String, v8::InvocationCallback funcID)
-
{
-
v8::HandleScope handle_scope;
-
-
v8::Handle<v8::FunctionTemplate> thisFunc;
-
thisFunc = v8::FunctionTemplate::New(funcID);
-
intoObject->Set(namev8String, thisFunc->GetFunction());
-
}
-
Conclusion
I hope this helps clarify the v8 logic, and helps users figure out how easy it actually is to automate things with templates and macros. Theres still many ways to simplify the code above but for simplicity – take it as it is.
In closing, v8 offers some super easy to use casting functions. Ill give some examples as i found these to be really nice but be wary, do exception handling, type checking and proper management of values handed to c++ from v8. It will save you some time looking through v8 exception callstacks if you manually handle possible exceptions.
-
-
//you can now use it std::string(*somestdstring)
-
v8::String::Utf8Value somestdstring ( args[0] );
-
-
//you can now use it *somecstring (same as const char* somecstring;)
-
v8::String::Utf8Value somecstring ( args[1] );
-
-
//normal double,
-
double somedouble = args[2]->ToNumber()->Value();
-
//booleans
-
bool somebool = args[3]->ToBoolean()->BooleanValue();
-
//and integers.
-
int someint = args[4]->ToInt32()->Int32Value();
-
-
//And one last thing, giving javascript meaningful return values.
-
//This should be more than enough to let you figure out how simple
-
//v8 makes things for you on the c++ side.
-
-
return handle_scope.Close(v8::Number::New(somedouble));
-
return handle_scope.Close(v8::Int32::New(someint));
-
return handle_scope.Close(v8::Boolean::New(somebool));
-
-
Out.