SkillAgentSearch skills...

Dukglue

A C++ binding/wrapper library for the Duktape JavaScript interpreter.

Install / Use

/learn @Aloshi/Dukglue
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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_peval and 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 DukValue class:
// 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

View on GitHub
GitHub Stars173
CategoryDevelopment
Updated2mo ago
Forks31

Languages

C

Security Score

100/100

Audited on Jan 19, 2026

No findings