pmed / v8pp

Bind C++ functions and classes into V8 JavaScript engine
http://pmed.github.io/v8pp/
Other
898 stars 120 forks source link

Bind simple function as class member #46

Closed jcmonnin closed 7 years ago

jcmonnin commented 7 years ago

I would like to bind a simple function (with the v8::FunctionCallbackInfo signature) as a class member. The use case for that is to have a more convenient API to access the class from JavaScript (in some cases I don't want a 1:1 mapping of the C++ methods in JavaScript). It would also be a workaround for method overloads. This is similar to the simple function as ctor in issue #41, but for methods. Please note that it's for a case where the original class cannot be modified.

Is there a way to achieve that with the current version of v8pp?

Below is what I tired. It does compile, but property 'f' of an instance of X is undefined.

// Some class in external library; cannot be modified
struct X
{
    bool b;
    X(bool b) : b(b) {}
};

// Add a function that should appear like a method of X in JavaScript
// (using FunctionCallbackInfo to handle overloads)
static void f(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    // How to get a pointer or reference to class instance here (args.Data() ?)
}

void v8ppTestBind(v8::Isolate* isolate)
{
    v8pp::class_<X> X_class(isolate);
    X_class
        .ctor<bool>()
        .set("b", &X::b)
        .set("f", &f);

    v8pp::module module(isolate);
    module.set("X", X_class);

    isolate->GetCurrentContext()->Global()->Set(v8::String::NewFromUtf8(isolate, "module"), module.new_instance());
}
pmed commented 7 years ago

Hi @jcmonnin

There is already a v8pp::class_::set() overloading for static functions and lambdas in the library. The only question was how to obtain a V8 object in such a static function, because args.This() returned undefined.

I now add static functions into the class prototype chain. This allows to use a static function with an object in JavaScript and to obtain this object in the corresponding C++ function:

static void f(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    X* self = v8pp::class_<X>::unwrap_object(args.GetIsolate(), args.This());
    if (self) args.GetReturnValue().Set(self->b);
    else args.GetReturnValue().Set(args[0]);
}
// JavaScript side
var x = new module.X(true)
var y = new module.X(false);

console.log(x.f(), y.f()); // true false
console.log(module.X.f(100)); // undefined
jcmonnin commented 7 years ago

Thanks for the commit. This allows me to achieve what I want.

The only cosmetic issue about this solution is that module.X.f is a function, when if fact it's a normal member, so it shouldn't appear here.

My function looks like this now:

static void f(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    v8::Isolate* isolate = args.GetIsolate();
    X* self = v8pp::class_<X>::unwrap_object(isolate, args.This());
    if (self) {
        args.GetReturnValue().Set(self->b);
    } else {
        isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "No instance")));
    }
}