9

say I have a c++ class Point

class Point {
public:
    Point();
    Point(float x, float y);
    ~Point();

    float X;
    float Y;

};

I'd like to add javascript functionality to it and chose duktape.

is it possible to reuse this class in javascript? say

var p = new Point(1.23, 4.56);

I have been reading the duktape documentation and it only says how to reuse functions inside javascript.

mika
  • 420
  • 3
  • 12

1 Answers1

13

My personal advice is to create C++ bindings for it just like you would do in JavaScript.

The only need, is to save the real C++ object in the JavaScript object, we use internal properties for that purpose.

You need to create a function that will be called from JavaScript as a constructor function, then you just have to fill its prototype and set a finalizer. It's not hard but it required lot of code so you basically want to create wrapper to make them easier.

#include <iostream>

#include "duktape.h"

class Point {
public:
    float x;
    float y;
};

/*
 * This is the point destructor
 */
duk_ret_t js_Point_dtor(duk_context *ctx)
{
    // The object to delete is passed as first argument instead
    duk_get_prop_string(ctx, 0, "\xff""\xff""deleted");

    bool deleted = duk_to_boolean(ctx, -1);
    duk_pop(ctx);

    if (!deleted) {
        duk_get_prop_string(ctx, 0, "\xff""\xff""data");
        delete static_cast<Point *>(duk_to_pointer(ctx, -1));
        duk_pop(ctx);

        // Mark as deleted
        duk_push_boolean(ctx, true);
        duk_put_prop_string(ctx, 0, "\xff""\xff""deleted");
    }

    return 0;
}

/*
 * This is Point function, constructor. Note that it can be called
 * as a standard function call, you may need to check for
 * duk_is_constructor_call to be sure that it is constructed
 * as a "new" statement.
 */
duk_ret_t js_Point_ctor(duk_context *ctx)
{
    // Get arguments
    float x = duk_require_number(ctx, 0);
    float y = duk_require_number(ctx, 1);

    // Push special this binding to the function being constructed
    duk_push_this(ctx);

    // Store the underlying object
    duk_push_pointer(ctx, new Point{x, y});
    duk_put_prop_string(ctx, -2, "\xff""\xff""data");

    // Store a boolean flag to mark the object as deleted because the destructor may be called several times
    duk_push_boolean(ctx, false);
    duk_put_prop_string(ctx, -2, "\xff""\xff""deleted");

    // Store the function destructor
    duk_push_c_function(ctx, js_Point_dtor, 1);
    duk_set_finalizer(ctx, -2);

    return 0;
}

/*
 * Basic toString method
 */
duk_ret_t js_Point_toString(duk_context *ctx)
{
    duk_push_this(ctx);
    duk_get_prop_string(ctx, -1, "\xff""\xff""data");
    Point *point = static_cast<Point *>(duk_to_pointer(ctx, -1));
    duk_pop(ctx);
    duk_push_sprintf(ctx, "%f, %f", point->x, point->y);

    return 1;
}

// methods, add more here
const duk_function_list_entry methods[] = {
    { "toString",   js_Point_toString,  0   },
    { nullptr,  nullptr,        0   }
};

int main(void)
{
    duk_context *ctx = duk_create_heap_default();

    // Create Point function
    duk_push_c_function(ctx, js_Point_ctor, 2);

    // Create a prototype with toString and all other functions
    duk_push_object(ctx);
    duk_put_function_list(ctx, -1, methods);
    duk_put_prop_string(ctx, -2, "prototype");

    // Now store the Point function as a global
    duk_put_global_string(ctx, "Point");

    if (duk_peval_string(ctx, "p = new Point(20, 40); print(p)") != 0) {
        std::cerr << "error: " << duk_to_string(ctx, -1) << std::endl;
        std::exit(1);
    }

    return 0;
}
markand
  • 495
  • 1
  • 4
  • 16
  • Why do you use \xff\xffdata - is that to hide the property from Javascript? Also, why do you need a deleted property - could you not set the data property to nullptr and use that? – imekon Oct 28 '15 at 06:49
  • Ah... just read the internal properties documentation - hence the \xff\xff – imekon Oct 28 '15 at 07:13
  • Yes exactly, I'm sorry I forgot to explain it. – markand Oct 28 '15 at 08:20
  • 3
    what about the question about reusing the \xff\xffdata property as the ''flag'', by resetting its value to null? – ddevienne Jan 27 '16 at 15:23