{"id":17239416,"url":"https://github.com/aloshi/dukglue","last_synced_at":"2025-07-25T02:06:30.244Z","repository":{"id":56052727,"uuid":"48725685","full_name":"Aloshi/dukglue","owner":"Aloshi","description":"A C++ binding/wrapper library for the Duktape JavaScript interpreter.","archived":false,"fork":false,"pushed_at":"2023-08-09T20:03:36.000Z","size":1856,"stargazers_count":170,"open_issues_count":19,"forks_count":32,"subscribers_count":22,"default_branch":"master","last_synced_at":"2024-12-10T00:42:00.704Z","etag":null,"topics":["binding","cplusplus","cpp","cpp11","duktape","wrapper"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aloshi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-12-29T03:34:51.000Z","updated_at":"2024-09-27T02:17:40.000Z","dependencies_parsed_at":"2024-01-16T12:49:36.597Z","dependency_job_id":"bc9fb9e6-fde1-4836-a194-6625afea2494","html_url":"https://github.com/Aloshi/dukglue","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aloshi%2Fdukglue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aloshi%2Fdukglue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aloshi%2Fdukglue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aloshi%2Fdukglue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aloshi","download_url":"https://codeload.github.com/Aloshi/dukglue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230423559,"owners_count":18223435,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binding","cplusplus","cpp","cpp11","duktape","wrapper"],"created_at":"2024-10-15T05:48:45.225Z","updated_at":"2024-12-19T11:11:51.439Z","avatar_url":"https://github.com/Aloshi.png","language":"C","readme":"Dukglue\n=======\n\nA C++ extension to the embeddable Javascript engine [Duktape](http://duktape.org/).\n\nNOTE: The master branch is for Duktape 2.x. Check the `duktape_1.x` branch if you want to use the older Duktape 1.x.\n\nDukglue offers:\n\n* An easy, type-safe way to bind functions:\n\n```cpp\n// C++:\nbool is_mod_2(int a)\n{\n  return (a % 2) == 0;\n}\n\ndukglue_register_function(ctx, \u0026is_mod_2, \"is_mod_2\");\n\n// --------------\n\n// Script:\n\nis_mod_2(5);  // returns false\nis_mod_2(\"a string!\");  // throws an error\n```\n\n* An easy, type-safe way to use C++ objects in scripts:\n\n```cpp\n// C++:\nclass TestClass\n{\npublic:\n  TestClass() : mCounter(0) {}\n\n  void incCounter(int val) {\n    mCounter += val;\n  }\n\n  void printCounter() {\n    std::cout \u003c\u003c \"Counter: \" \u003c\u003c mCounter \u003c\u003c std::endl;\n  }\n\nprivate:\n  int mCounter;\n};\n\ndukglue_register_constructor\u003cTestClass\u003e(ctx, \"TestClass\");\ndukglue_register_method(ctx, \u0026TestClass::incCounter, \"incCounter\");\ndukglue_register_method(ctx, \u0026TestClass::printCounter, \"printCounter\");\n\n// --------------\n\n// Script:\n\nvar test = new TestClass();\ntest.incCounter(1);\ntest.printCounter();  // prints \"Counter: 1\" via std::cout\ntest.incCounter(\"a string!\")  // throws an error (TestClass::incCounter expects a number)\n```\n\n* You can safely call C++ functions that take pointers as arguments:\n\n```cpp\n// C++:\nstruct Point {\n  Point(float x_in, float y_in) : x(x_in), y(y_in) {}\n\n  float x;\n  float y;\n}\n\nfloat calcDistance(Point* p1, Point* p2) {\n  float x = (p2-\u003ex - p1-\u003ex);\n  float y = (p2-\u003ey - p1-\u003ey);\n  return sqrt(x*x + y*y);\n}\n\ndukglue_register_constructor\u003cPoint, /* constructor args */ float, float\u003e(ctx, \"Point\");\ndukglue_register_function(ctx, calcDistance, \"calcDistance\");\n\n\nclass Apple {\n  void eat();\n}\n\ndukglue_register_constructor\u003cApple\u003e(ctx, \"Apple\");\n\n// --------------\n\n// Script:\nvar p1 = new Point(0, 0);\nvar p2 = new Point(5, 0);\nvar apple = new Apple();\nprint(calcDistance(p1, p2));  // prints 5\ncalcDistance(apple, p2);  // throws an error\n```\n\n* You can even return new C++ objects from C++ functions/methods:\n\n```cpp\n// C++:\nclass Dog {\n  Dog(const char* name) : name_(name) {}\n\n  const char* getName() {\n    return name_.c_str();\n  }\n\nprivate:\n  std::string name_;\n}\n\nDog* adoptPuppy() {\n  return new Dog(\"Gus\");\n}\n\nvoid showFriends(Dog* puppy) {\n  if (puppy != NULL)\n    std::cout \u003c\u003c \"SO CUTE\" \u003c\u003c std::endl;\n  else\n    std::cout \u003c\u003c \"why did you call me, there is nothing cute here\" \u003c\u003c std::endl;\n}\n\ndukglue_register_function(ctx, adoptPuppy, \"adoptPuppy\");\ndukglue_register_function(ctx, showFriends, \"showFriends\");\n\n// Notice we never explicitly told Duktape about the \"Dog\" class!\n\n// --------------\n\n// Script:\nvar puppy = adoptPuppy();\nshowFriends(puppy);  // prints \"SO CUTE\" via std::cout\nshowFriends(null);  // prints \"why did you call me, there is nothing cute here\"\n```\n\n* You can invalidate C++ objects when they are destroyed:\n\n```cpp\n// C++:\nvoid puppyRanAway(Dog* dog) {\n  delete dog;\n  dukglue_invalidate_object(ctx, dog);  // tell Duktape this reference is now invalid\n  cry();\n\n  // (note: you'll need access to the Duktape context, e.g. as a global/singleton, for this to work)\n}\n\ndukglue_register_function(ctx, puppyRanAway, \"puppyRanAway\");\n\n// --------------\n\n// Script:\nvar puppy = adoptPuppy();\npuppyRanAway(puppy);\nprint(puppy.getName());  // puppy has been invalidated, methods throw an error\nshowFriends(puppy);  // also throws an error, puppy has been invalidated\n```\n\n* Dukglue also works with single inheritance:\n\n```cpp\n// C++:\n\nclass Shape {\npublic:\n  Shape(float x, float y) : x_(x), y_(y) {}\n\n  virtual void describe() = 0;\n\nprotected:\n  float x_, y_;\n}\n\nclass Circle : public Shape {\n  Circle(float x, float y, float radius) : Shape(x, y), radius_(radius) {}\n\n  virtual void describe() override {\n    std::cout \u003c\u003c \"A lazily-drawn circle at \" \u003c\u003c x_ \u003c\u003c \", \" \u003c\u003c y_ \u003c\u003c \", with a radius of about \" \u003c\u003c radius_ \u003c\u003c std::endl;\n  }\n\nprotected:\n  float radius_;\n}\n\ndukglue_register_method(ctx, \u0026Shape::describe, \"describe\");\n\ndukglue_register_constructor\u003cCircle, float, float, float\u003e(ctx, \"Circle\");\ndukglue_set_base_class\u003cShape, Circle\u003e(ctx);\n\n// --------------\n\n// Script:\nvar shape = new Circle(1, 2, 42);\nshape.describe();  // prints \"A lazily-drawn circle at 1, 2, with a radius of about 42\"\n```\n\n  (multiple inheritance is not supported)\n\n* Dukglue supports Duktape properties (getter/setter pairs that act like values):\n\n```cpp\nclass MyClass {\npublic:\n  MyClass() : mValue(0) {}\n\n  int getValue() const {\n    return mValue;\n  }\n  void setValue(int v) {\n    mValue = v;\n  }\n\nprivate:\n  int mValue;\n};\n\ndukglue_register_constructor\u003cMyClass\u003e(ctx, \"MyClass\");\ndukglue_register_property(ctx, \u0026MyClass::getValue, \u0026MyClass::setValue, \"value\");\n\n// --------------\n\n// Script:\nvar test = MyClass();\ntest.value;  // calls MyClass::getValue(), which returns 0\ntest.value = 42;  // calls MyClass::setValue(42)\ntest.value;  // again calls MyClass::getValue(), which now 42\n```\n\n  (also works with non-`const` getter methods)\n\n* You can also do getter-only or setter-only properties:\n\n```cpp\n// continuing with the class above...\n\ndukglue_register_property(ctx, \u0026MyClass::getValue, nullptr, \"value\");\n\nvar test = MyClass();\ntest.value;  // still 0\ntest.value = 42;  // throws an error\n\ndukglue_register_property(ctx, nullptr, \u0026MyClass::setValue, \"value\");\ntest.value = 42;  // works\ntest.value;  // throws an error\n```\n\n  (it is also safe to re-define properties like in this example)\n\n* There are utility functions for pushing arbitrary values onto the Duktape stack:\n\n```cpp\n// you can push primitive types\nint someValue = 12;\ndukglue_push(ctx, someValue);\n\n// you can push native C++ objects\nDog* myDog = new Dog(\"Zoey\");\ndukglue_push(ctx, myDog);  // pushes canonical script object for myDog\n\n// you can push multiple values at once\ndukglue_push(ctx, 12, myDog);  // stack now contains \"12\" at position -2 and \"myDog\" at -1\n```\n\n* There is a utility function for doing a `duk_peval` and getting the return value safely:\n\n```cpp\n// template argument is the return value we expect\nint result = dukglue_peval\u003cint\u003e(ctx, \"4 * 5\");\nresult == 20;  // true\nduk_get_top(ctx) == 0;  // the Duktape stack is empty, return value is always popped off the stack\n\nint error = dukglue_peval\u003cint\u003e(ctx, \"'a horse-sized duck'\");  // string is not a number; throws a DukException\n\n// we can also choose to ignore the return value\ndukglue_peval\u003cvoid\u003e(ctx, \"function makingAFunction() { doThings(); }\");\n// the Duktape stack will be clean\n```\n\n* There is a helper function for registering script objects as globals (useful for singletons):\n\n```cpp\nDog* myDog = new Dog(\"Zoey\");\ndukglue_register_global(ctx, myDog, \"testDog\");\n// testDog in script now refers to myDog in C++\n// use dukglue_invalidate_object(ctx, myDog) to invalidate it\n\n// --------------\n// Script:\ntestDog.getName() == \"Zoey\";  // this evaluates to true\n```\n\n* You can call script methods on native objects (useful for callbacks that are defined as properties):\n\n```cpp\n// continuing from the above example...\n// Script:\ntestDog.barkAt = function (at) { print(this.getName() + \" barks at \" + at +\"!\"); }\n\n// --------------\n// C++:\n// template parameter is required, and is the desired return type for the method\ndukglue_pcall_method\u003cvoid\u003e(ctx, myDog, \"barkAt\", \"absolutely nothing\");  // prints \"Zoey barks at absolutely nothing!\"\n```\n\n* We can get return values from methods:\n\n```cpp\n// Script:\ntestDog.checkWantsTreat = function() { return true; }  // frequency chosen via empirical evidence\n\n// --------------\n// C++:\nbool wantsTreatNow = dukglue_pcall_method\u003cbool\u003e(ctx, myDog, \"checkWantsTreat\");  // true\n\n// return types are typechecked, so the following is an error (reported via exception):\nstd::string error = dukglue_pcall_method\u003cstd::string\u003e(ctx, myDog, \"checkWantsTreat\");  // throws DukException (inherits std::exception)\n\n// we can ignore the return value (whatever it is)\ndukglue_pcall_method\u003cvoid\u003e(ctx, myDog, \"checkWantsTreat\");\n```\n\n\n* You can get/persist references to script values using the `DukValue` class:\n\n```cpp\n// template parameter is return type\nDukValue testObj = dukglue_peval\u003cDukValue\u003e(ctx,\n  \"var testObj = new Object();\"\n  \"testObj.value = 42;\"\n  \"testObj.myFunc = function(a, b) { return a*b; };\"\n  \"testObj;\");  // returns testObj\n\n// testObj now holds a reference to the testObj we made in script above\n// we can call methods on it:\n{\n  int result = dukglue_pcall_method\u003cint\u003e(ctx, testObj, \"myFunc\", 3, 4);\n  result == 12;  // true\n}\n\n// if we don't know what the return type will be, we can use a DukValue:\n{\n  DukValue result = dukglue_pcall_method\u003cDukValue\u003e(ctx, testObj, \"myFunc\", 5, 4);\n  if (result.type() == DukValue::NUMBER)\n    std::cout \u003c\u003c \"Returned \" \u003c\u003c result.as_int() \u003c\u003c \"\\n\";\n  if (result.type() == DukValue::UNDEFINED)\n    std::cout \u003c\u003c \"Didn't return anything\\n\";\n}\n\n// we can also have DukValue hold a script function\nDukValue printValueFunc = dukglue_peval\u003cDukValue\u003e(ctx,\n  \"function printValueProperty(obj) { print(obj.value); };\"\n  \"printValueProperty;\");\n\n// we can use duk_pcall to call a callable DukValue (i.e. a function)\n// and we can also use a DukValue as a parameter to another function\ndukglue_pcall(ctx, printValueFunc, testObj);  // prints 42\n\n// we can copy DukValues if we want:\nDukValue printCopy = printValueFunc;\nprintCopy == printValueFunc;  // true\n// since printCopy.type() == OBJECT, both values will reference the same object\n// (i.e. changing printCopy with also change printValueFunc)\n// DukValues are reference counted, you don't need to worry about manually freeing them!\n\n// even if we make a totally new DukValue, it will still reference the same script object\n// (and the equality operator still works):\nDukValue anotherPrintValueFunc = dukglue_peval\u003cDukValue\u003e(ctx, \"printValueProperty;\");\nanotherPrintValueFunc == printValueFunc;  // true\n```\n\n  (a DukValue can hold any Duktape value except for buffer and lightfunc)\n  (C++ getters for type, null, boolean, number, string, and pointer - no getter for native objects yet, though this is definitely possible)\n\n\nWhat Dukglue **doesn't do:**\n\n* Dukglue does not support automatic garbage collection of C++ objects. Why?\n\n    To support objects going back and forth between C++ and script, Dukglue keeps one canonical script object per native object (which is saved in the Duktape 'heap stash'). The downside to this is that Dukglue is always holding onto script object references, so they can't be garbage collected.\n\n    If Dukglue *didn't* keep a registry of native object -\u003e canonical script object, you could have two different script objects with the same underlying native object, but different dynamic properties. Equality operators like `obj1 == obj2` would also be unreliable, since they would technically be different objects.\n\n    (You *could* keep dynamic properties consistent by using a [Proxy object](http://wiki.duktape.org/HowtoVirtualProperties.html), but you still couldn't compare objects for equality with `==`.)\n\n    Basically, for this to be possible, Duktape needs support for weak references, so Dukglue can keep references without keeping them from being garbage collected.\n\n* Dukglue supports `std::shared_ptr`, but with two major caveats:\n\n    1. **Dynamic properties will not persist** - any properties not defined with `dukglue_register_property` will not be the same between two shared_ptrs pointing to the same object. For example:\n\n    ```cpp\n    std::shared_ptr\u003cResource\u003e getResource() {\n      static std::shared_ptr\u003cResource\u003e resource = std::make_shared\u003cResource\u003e();\n      return resource;\n    }\n    ```\n\n    ```js\n    var resource = getResource();\n    tex.isAwesome = true;\n    var resourceAgain = getResource();\n    print(resourceAgain.isAwesome);  // prints undefined\n    print(tex.isAwesome);  // prints true\n    ```\n\n    (However, dynamic properties will stay on the object once it has entered script until it is garbage collected - `tex.isAwesome` will still be `true`.)\n\n    One minor upside to properties not persisting is that **std::shared_ptr objects don't need to call `dukglue_invalidate_reference()` when they are destroyed**.\n    \n    2. **JavaScript equality checks will not work**. This is because the current implementation treats shared_ptrs like a value type. \n\n    ```js\n    print(tex === texAgain);  // prints false\n    ```\n\n    I'm not really happy with these caveats, but it was the fastest way to implement and it is acceptable for my use case. The entire implementation is in detail_primitive_types.h if you want to try and improve it (maybe try using Duktape's ES6 proxy subset?).\n\n* Dukglue *might* not follow the \"compact footprint\" goal of Duktape. I picked Duktape for it's simple API, not to script my toaster. YMMV if you're trying to compile this for a microcontroller. Why?\n\n    * Dukglue currently needs RTTI turned on. When Dukglue checks if an object can be cast to a particular type, it uses the typeid operator to compare if two types are equal. It's always used on compile-time types though, so you could implement it without RTTI if you needed to. Dukglue also uses exceptions in two places: the `dukglue_pcall*` functions (since these return a value instead of an error code, unlike Duktape), and the `DukValue` class (to communicate type errors on getters and unsupported types).\n\n    * An std::unordered_map is used to efficiently map object pointers to an internal Duktape array (see the `RefMap` class in `detail_refs.h`). This has some unnecessary memory overhead for every object (probably around 32 bytes per object). This could be improved to have no memory overhead - see detail_refs.h's comments for more information.\n\n    * That aside, run-time memory usage should be reasonable.\n\n* Dukglue may not be super fast. Duktape doesn't promise to be either.\n\nGetting Started\n===============\n\nDukglue has been tested with MSVC 2015, clang++-3.8, and g++-5.4. Your compiler must support at least C++11 to use Dukglue.\n\nDukglue is a header-only library. Dukglue requires Duktape to be installed such that `#include \u003cduktape.h\u003e` works.\n\nEverything you need (except Duktape) is in the `include` directory of this repository. If you prefer not to mess with CMake or build settings, you should be able to just copy the `include` directory into your project.\n\nI've also created some CMake files to try and make installing Dukglue as painless as possible:\n\n```bash\nmkdir build\ncd build\ncmake ..\nsudo make install\n```\n\nThis will copy the Dukglue headers to `/usr/local/include/dukglue/*` by default. You can use `cmake .. -DCMAKE_INSTALL_PREFIX:PATH=.` to change the install directory to something else (will install to `CMAKE_INSTALL_PREFIX/include/dukglue/*`).\n\nNow, all you need to do is add the include paths for Dukglue to your project and add duktape.c/duktape.h to your project.\n\nThen you can try the example below:\n\n```cpp\n#include \u003ciostream\u003e\n\n#include \u003cdukglue/dukglue.h\u003e\n\nint myFunc(int a)\n{\n  return a*a;\n}\n\nclass Dog {\npublic:\n  Dog(const char* name) : name_(name) {}\n\n  void bark() {\n    std::cout \u003c\u003c \"WOOF WOOF\" \u003c\u003c std::endl;\n  }\n\n  const char* getName() {\n    return name_.c_str();\n  }\n\nprivate:\n  std::string name_;\n}\n\nint main()\n{\n  duk_context* ctx = duk_create_heap_default();\n\n  dukglue_register_function(ctx, myFunc, \"myFunc\");\n\n  dukglue_register_constructor\u003cDog, /* constructor args: */ const char*\u003e(ctx, \"Dog\");\n  dukglue_register_method(ctx, \u0026Dog::bark, \"bark\");\n  dukglue_register_method(ctx, \u0026Dog::getName, \"getName\");\n\n  if (duk_peval_string(ctx,\n      \"var gus = new Dog('Gus');\"\n      \"gus.bark();\"\n      \"print(gus.getName());\")) {\n    // if an error occured while executing, print the stack trace\n    duk_get_prop_string(ctx, -1, \"stack\");\n    std::cout \u003c\u003c duk_safe_to_string(ctx, -1) \u003c\u003c std::endl;\n    duk_pop(ctx);\n  }\n\n  duk_destroy_heap(ctx);\n\n  return 0;\n}\n```\n\nTODO\n====\n\n* Make better organized documentation\n\n* Write a tutorial on how to add custom value types\n\nhttp://aloshi.com\n\nAlec 'Aloshi' Lofquist\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faloshi%2Fdukglue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faloshi%2Fdukglue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faloshi%2Fdukglue/lists"}