obuchtala / swig

Branch for the development of SWIG's javascript target (v8 and jsc)
https://github.com/swig/swig
Other
25 stars 14 forks source link

Wrapping a JS Callback #35

Open Pagghiu opened 10 years ago

Pagghiu commented 10 years ago

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.

zittix commented 10 years ago

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);
    }
};
cthurston commented 8 years ago

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);