Dukglue
A C++ binding/wrapper library for the Duktape JavaScript interpreter.
Install / Use
/learn @Aloshi/DukglueREADME
Dukglue
A C++ extension to the embeddable Javascript engine Duktape.
NOTE: The master branch is for Duktape 2.x. Check the duktape_1.x branch if you want to use the older Duktape 1.x.
Dukglue offers:
- An easy, type-safe way to bind functions:
// C++:
bool is_mod_2(int a)
{
return (a % 2) == 0;
}
dukglue_register_function(ctx, &is_mod_2, "is_mod_2");
// --------------
// Script:
is_mod_2(5); // returns false
is_mod_2("a string!"); // throws an error
- An easy, type-safe way to use C++ objects in scripts:
// C++:
class TestClass
{
public:
TestClass() : mCounter(0) {}
void incCounter(int val) {
mCounter += val;
}
void printCounter() {
std::cout << "Counter: " << mCounter << std::endl;
}
private:
int mCounter;
};
dukglue_register_constructor<TestClass>(ctx, "TestClass");
dukglue_register_method(ctx, &TestClass::incCounter, "incCounter");
dukglue_register_method(ctx, &TestClass::printCounter, "printCounter");
// --------------
// Script:
var test = new TestClass();
test.incCounter(1);
test.printCounter(); // prints "Counter: 1" via std::cout
test.incCounter("a string!") // throws an error (TestClass::incCounter expects a number)
- You can safely call C++ functions that take pointers as arguments:
// C++:
struct Point {
Point(float x_in, float y_in) : x(x_in), y(y_in) {}
float x;
float y;
}
float calcDistance(Point* p1, Point* p2) {
float x = (p2->x - p1->x);
float y = (p2->y - p1->y);
return sqrt(x*x + y*y);
}
dukglue_register_constructor<Point, /* constructor args */ float, float>(ctx, "Point");
dukglue_register_function(ctx, calcDistance, "calcDistance");
class Apple {
void eat();
}
dukglue_register_constructor<Apple>(ctx, "Apple");
// --------------
// Script:
var p1 = new Point(0, 0);
var p2 = new Point(5, 0);
var apple = new Apple();
print(calcDistance(p1, p2)); // prints 5
calcDistance(apple, p2); // throws an error
- You can even return new C++ objects from C++ functions/methods:
// C++:
class Dog {
Dog(const char* name) : name_(name) {}
const char* getName() {
return name_.c_str();
}
private:
std::string name_;
}
Dog* adoptPuppy() {
return new Dog("Gus");
}
void showFriends(Dog* puppy) {
if (puppy != NULL)
std::cout << "SO CUTE" << std::endl;
else
std::cout << "why did you call me, there is nothing cute here" << std::endl;
}
dukglue_register_function(ctx, adoptPuppy, "adoptPuppy");
dukglue_register_function(ctx, showFriends, "showFriends");
// Notice we never explicitly told Duktape about the "Dog" class!
// --------------
// Script:
var puppy = adoptPuppy();
showFriends(puppy); // prints "SO CUTE" via std::cout
showFriends(null); // prints "why did you call me, there is nothing cute here"
- You can invalidate C++ objects when they are destroyed:
// C++:
void puppyRanAway(Dog* dog) {
delete dog;
dukglue_invalidate_object(ctx, dog); // tell Duktape this reference is now invalid
cry();
// (note: you'll need access to the Duktape context, e.g. as a global/singleton, for this to work)
}
dukglue_register_function(ctx, puppyRanAway, "puppyRanAway");
// --------------
// Script:
var puppy = adoptPuppy();
puppyRanAway(puppy);
print(puppy.getName()); // puppy has been invalidated, methods throw an error
showFriends(puppy); // also throws an error, puppy has been invalidated
- Dukglue also works with single inheritance:
// C++:
class Shape {
public:
Shape(float x, float y) : x_(x), y_(y) {}
virtual void describe() = 0;
protected:
float x_, y_;
}
class Circle : public Shape {
Circle(float x, float y, float radius) : Shape(x, y), radius_(radius) {}
virtual void describe() override {
std::cout << "A lazily-drawn circle at " << x_ << ", " << y_ << ", with a radius of about " << radius_ << std::endl;
}
protected:
float radius_;
}
dukglue_register_method(ctx, &Shape::describe, "describe");
dukglue_register_constructor<Circle, float, float, float>(ctx, "Circle");
dukglue_set_base_class<Shape, Circle>(ctx);
// --------------
// Script:
var shape = new Circle(1, 2, 42);
shape.describe(); // prints "A lazily-drawn circle at 1, 2, with a radius of about 42"
(multiple inheritance is not supported)
- Dukglue supports Duktape properties (getter/setter pairs that act like values):
class MyClass {
public:
MyClass() : mValue(0) {}
int getValue() const {
return mValue;
}
void setValue(int v) {
mValue = v;
}
private:
int mValue;
};
dukglue_register_constructor<MyClass>(ctx, "MyClass");
dukglue_register_property(ctx, &MyClass::getValue, &MyClass::setValue, "value");
// --------------
// Script:
var test = MyClass();
test.value; // calls MyClass::getValue(), which returns 0
test.value = 42; // calls MyClass::setValue(42)
test.value; // again calls MyClass::getValue(), which now 42
(also works with non-const getter methods)
- You can also do getter-only or setter-only properties:
// continuing with the class above...
dukglue_register_property(ctx, &MyClass::getValue, nullptr, "value");
var test = MyClass();
test.value; // still 0
test.value = 42; // throws an error
dukglue_register_property(ctx, nullptr, &MyClass::setValue, "value");
test.value = 42; // works
test.value; // throws an error
(it is also safe to re-define properties like in this example)
- There are utility functions for pushing arbitrary values onto the Duktape stack:
// you can push primitive types
int someValue = 12;
dukglue_push(ctx, someValue);
// you can push native C++ objects
Dog* myDog = new Dog("Zoey");
dukglue_push(ctx, myDog); // pushes canonical script object for myDog
// you can push multiple values at once
dukglue_push(ctx, 12, myDog); // stack now contains "12" at position -2 and "myDog" at -1
- There is a utility function for doing a
duk_pevaland getting the return value safely:
// template argument is the return value we expect
int result = dukglue_peval<int>(ctx, "4 * 5");
result == 20; // true
duk_get_top(ctx) == 0; // the Duktape stack is empty, return value is always popped off the stack
int error = dukglue_peval<int>(ctx, "'a horse-sized duck'"); // string is not a number; throws a DukException
// we can also choose to ignore the return value
dukglue_peval<void>(ctx, "function makingAFunction() { doThings(); }");
// the Duktape stack will be clean
- There is a helper function for registering script objects as globals (useful for singletons):
Dog* myDog = new Dog("Zoey");
dukglue_register_global(ctx, myDog, "testDog");
// testDog in script now refers to myDog in C++
// use dukglue_invalidate_object(ctx, myDog) to invalidate it
// --------------
// Script:
testDog.getName() == "Zoey"; // this evaluates to true
- You can call script methods on native objects (useful for callbacks that are defined as properties):
// continuing from the above example...
// Script:
testDog.barkAt = function (at) { print(this.getName() + " barks at " + at +"!"); }
// --------------
// C++:
// template parameter is required, and is the desired return type for the method
dukglue_pcall_method<void>(ctx, myDog, "barkAt", "absolutely nothing"); // prints "Zoey barks at absolutely nothing!"
- We can get return values from methods:
// Script:
testDog.checkWantsTreat = function() { return true; } // frequency chosen via empirical evidence
// --------------
// C++:
bool wantsTreatNow = dukglue_pcall_method<bool>(ctx, myDog, "checkWantsTreat"); // true
// return types are typechecked, so the following is an error (reported via exception):
std::string error = dukglue_pcall_method<std::string>(ctx, myDog, "checkWantsTreat"); // throws DukException (inherits std::exception)
// we can ignore the return value (whatever it is)
dukglue_pcall_method<void>(ctx, myDog, "checkWantsTreat");
- You can get/persist references to script values using the
DukValueclass:
// template parameter is return type
DukValue testObj = dukglue_peval<DukValue>(ctx,
"var testObj = new Object();"
"testObj.value = 42;"
"testObj.myFunc = function(a, b) { return a*b; };"
"testObj;"); // returns testObj
// testObj now holds a reference to the testObj we made in script above
// we can call methods on it:
{
int result = dukglue_pcall_method<int>(ctx, testObj, "myFunc", 3, 4);
result == 12; // true
}
// if we don't know what the return type will be, we can use a DukValue:
{
DukValue result = dukglue_pcall_method<DukValue>(ctx, testObj, "myFunc", 5, 4);
if (result.type() == DukValue::NUMBER)
std::cout << "Returned " << result.as_int() << "\n";
if (result.type() == DukValue::UNDEFINED)
std::cout << "Didn't return anything\n";
}
// we can also have DukValue hold a script function
DukValue printValueFunc = dukglue_peval<DukValue>(ctx,
"function printValueProperty(obj) { print(obj.value); };"
"printValueProperty;");
// we can use duk_pcall to call a callable DukValue (i.e. a function)
// and we can also use a DukValue as a parameter to another function
dukglue_pcall(ctx, printValueFunc, testObj); // prints 42
// we can copy DukValues if we want:
DukValue printCopy = printValueFunc;
printCopy == printValueFunc; // true
// since printCopy.type() == OBJECT, both values will reference the same object
// (i.e. changing printCopy with also change printValueFunc)
// DukValues are reference counted, you don't need to worry about manually freeing them!
// even if we make a totally new DukValue, it will still reference the same script object
// (and the equality operator still works):
DukValue anotherPrintValueFunc = dukglue_peval<DukValue>(ctx, "printValueProperty;");
anotherPrintValueFunc == printValueFunc; // true
(a DukValue can hold any Duktape value except for buffer and lightfunc) (C++ getters for type, null, boolean, number, string, and pointer - no getter for
