-
Notifications
You must be signed in to change notification settings - Fork 63
ClassMapper
ClassMapper<T>
is a class template that helps creating mapping between C++ classes and exposed Javascript classes.
It provides a way to map constructor, methods, getter and setter in a 1:1 manner between the two.
class MyClass : public ClassMapper<MyClass>
{
public:
static void RegisterObject(JSContext *cx);
static MyClass *Constructor(JSContext *cx, JS::CallArgs &args,
JS::HandleObject obj);
static JSFunctionSpec *ListMethods();
static JSPropertySpec *ListProperties();
virtual ~MyClass() {}
MyClass() {}
protected:
/*
Declare a JS method
*/
NIDIUM_DECL_JSCALL(myMethod);
/*
Declare a JS property
*/
NIDIUM_DECL_JSGETTER(myProperty);
};
void MyClass::RegisterObject(JSContext *cx)
{
/**
Expose |MyClass| to the global scope
Prototype is available through "MyClass.prototype"
*/
MyClass::ExposeClass<1>(cx, "MyClass");
}
/**
Called during a JS "var obj = new MyClass(param) call"
Returning nullptr trigger a JS exception
@param args A list of arguments provided to the contructor
@param obj The JSObject corresponding to the instance
*/
MyClass *MyClass::Constructor(JSContext *cx, JS::CallArgs &args,
JS::HandleObject obj)
{
/**
arguments are available in args() like a regular method call.
*/
return new MyClass();
}
/**
Called during a JS "obj.myMethod(param)" call
Returning nullptr trigger a JS exception
@param args A list of arguments provided to method call
Return value is set via args.rval().setXXX()
*/
bool MyClass::JS_myMethod(JSContext *cx, JS::CallArgs &args)
{
/*
Convert the first argument as a string
*/
JS::RootedString myparam(cx, JS::ToString(cx, args[0]));
JSAutoByteString cmyparam;
cmyparam.encodeUtf8(cx, myparam);
printf("First argument is %s\n", cmyparam.ptr());
/*
Set the JavaScript value returned to "myMethod"
*/
args.rval().setBoolean(true);
/*
No Exception
*/
return true;
}
/**
Called when accessing "obj.myProperty".
@param vp The value of "myProperty" to be returned.
Set using vp.setXXX()
*/
bool MyClass::JSGetter_myProperty(JSContext *cx, JS::MutableHandleValue vp)
{
vp.setInt32(42);
return true;
}
/**
Optional.
List all the methods available on MyClass prototype
CLASSMAPPER_FN arguments :
1. Must be the name of the current C++ class
2. The name of the method exposed to the JS
3. Minimum number of arguments
*/
JSFunctionSpec *MyClass::ListMethods()
{
static JSFunctionSpec funcs[] = {
CLASSMAPPER_FN(MyClass, myMethod, 1),
JS_FS_END
};
return funcs;
}
/**
Optional.
List all the properties available on MyClass prototype
*/
JSPropertySpec *MyClass::ListProperties()
{
static JSPropertySpec props[] = {
CLASSMAPPER_PROP_G(MyClass, myProperty),
JS_PS_END
};
return props;
}
ClassMapper provides 2 way to managed the lifetime of instantiated objects.
The default behaviour is to let the JS garbage collector handles the destruction of the object. That is, when the object become unreachable from the JS code, the JS engine will finalize the underlying JSObject
and ClassMapper will delete
the C++ instance.
function foobar()
{
var x = new MyClass("hello");
// After foobar() has returned, |x| becomes unreachable
// and is marked for deletion by the garbage collector.
// The C++ destructor ~MyClass() is then automatically called.
}
foobar();
However it's also possible for a ClassMapper instance to takeover the control over the lifetime of the object. Let's say you have some internal pending operation on your C++ object and you don't want the GC to delete the instance for you.
The only thing you need to do is to call root()
on your C++ instance.
MyClass *MyClass::Constructor(JSContext *cx, JS::CallArgs &args,
JS::HandleObject obj)
{
MyClass *myclass = new MyClass();
myclass->root(); // Tells the JS engine to keep a
// reference to the underlying JSObject.
return myclass();
}
function foobar()
{
var x = new MyClass("hello");
// After foobar() has returned, |x| becomes unreachable to the JavaScript code.
// However, |x| was internally maked as
// reachable by calling |myclass->root()| meanings
// it's still reachable by C++
}
foobar();
It's now up to the C++ to call either unroot()
on the instance or directly delete
the object.
It's possible that a JS class is not constructible by the end-user but only by the C++.
For instance, image a Socket
class (which is constructible), where an instance of SocketClientConnection
is passed as a callback argument when a connection is established.
In that case, the user doesn't have to create the instance manually.
var socket = new Socket(8080).listen();
socket.onaccept = function(client) {
// client is an instance of SocketClientConnection
}