swig
swig copied to clipboard
Wrapping a JS Callback
I was wondering: how would it be possible to wrap a callback from JS to C++ World? Similar to what NodeJS does for most of its async api. I think minimum need is a function object and a context scope.
C++
void myFun(int p, Handle<v8::Function> func,Context::Scope scope)
{
// store the callback for calling later in async
// fashion taking care of being in the v8 thread with own event loop
//or nodejs loop if we're in a nodejs plugin...
}
Javascript
myFun(1,function(){console.out("done!");});
An alternative path would be mapping a c++ "Functor" or std::function or Function pointer but I think it would become a lot more complex... Probably It's a lot easier to do manual wrapping of the functions with callbacks and use swig-v8 for everything else.
Keep up.
As a reference, you can use the following typemaps to handle callbacks with JavaScriptCore.
You have to make sure to declare the context argument at the end of your C++ function due to a bug in SWIG.
/* For having the context in the function
* Due to a bug in SWIG, the context argument has to be at the end !
*/
%typemap(in, numinputs=0) JSContextRef {
$1 = context;
}
%typemap(in) JSObjectRef {
if (!JSValueIsObject(context,$input)) {
SWIG_exception_fail(SWIG_ERROR, "in method '$symname', argument $argnum of type function()");
}
JSObjectRef c = JSValueToObject(context,$input,exception);
$1 = c;
}
It can be used this way:
class MyClass {
public:
void testMethod(const std::string& a, JSObjectRef callback, JSContextRef context) {
JSObjectCallAsFunction(context, callback, callback, 0, NULL, NULL);
}
};
For v8/node I was able to get this to work:
Need to make a static reference to hold the callback.
%{
static v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function>> jsFunctionCallback;
%}
Define the typemap to store the callback in the the static variable.
%typemap(in) v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function>>& {
v8::Handle<v8::Function> arg0 = v8::Handle<v8::Function>::Cast($input);
v8::Persistent<v8::Function> cb(v8::Isolate::GetCurrent(), arg0);
jsFunctionCallback = cb;
$1 = &jsFunctionCallback;
}
Example class using the callback in the constructor and the swig interface. Value is how to call the callback, which is expecting a double and is expected to return a double. You may not need to extend ObjectWrap within SWIG.
%{
#include <node_object_wrap.h>
class JsFunctionDelegate : public node::ObjectWrap {
public:
~JsFunctionDelegate(){};
double value(double x) {
auto isolate = v8::Isolate::GetCurrent();
v8::HandleScope scope(isolate);
auto context = isolate->GetCurrentContext();
auto global = context->Global();
const int argc = 1;
v8::Handle<v8::Value> argv[argc];
argv[0] = v8::Number::New(isolate, x);
auto fn = v8::Local<v8::Function>::New(isolate, javascriptCallback);
auto returnValue = fn->Call(Null(isolate), argc, argv);
return returnValue->NumberValue();
};
explicit JsFunctionDelegate(v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function>>& cb) : javascriptCallback(cb) {};
private:
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function>> javascriptCallback;
};
%}
%feature("director") JsFunctionDelegate;
class JsFunctionDelegate {
public:
virtual JsFunctionDelegate(v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function>>&);
virtual ~JsFunctionDelegate();
virtual double value(double x);
};
Then in the JS code you can define the delegate like so:
var unary = function(x) {
return x * x;
};
var delegate = new mySwigPlugin.JsFunctionDelegate(unary);
assert(delegate.value(5) === 25);