Awesome Open Source
Awesome Open Source

Actions Status Actions Status Actions Status Actions Status Actions Status codecov

Glue

A common interface for C++ to other language bindings.

Motivation

C++ is a great language for writing algorithms and high-performance code. However, once it comes to building applications, servers or websites, simple things become complex and we settle for a scripting language to glue C++ components together. The bindings usually need to be hand-crafted for every exposed type and language. This project aims to create a generic language binding interface in pure standard C++, which allows two-way interactions, automatic declaration creation and relatively short compile times.

API

Maps and values

Glue is based on the Revisited framework and provides convenient wrapper methods. The most important types for creating the API and are glue::Value and glue::MapValue.

#include <glue/value.h>
#include <iostream>

void valueExample() {
  // `glue::Value`s hold an `revisited::Any` type that can store any type of value
  glue::Value value = "Hello glue!";

  // access the any type through the `->` or `*` operator
  // `as<T>()` returns an `std::optional<T>` that is defined if the cast is possible
  std::cout << *value->as<std::string>() << std::endl;

  // `glue::MapValue` is a wrapper for a container that maps strings to values
  // `glue::createAnyMap()` creates a map based on `std::unordered_set`
  // Values also accept lambda functions
  glue::MapValue map = glue::createAnyMap();
  map["myNumber"] = 42;
  map["myString"] = value;
  map["myCallback"] = [](bool a, float b){ return a ? b : -b; };
  map["myMap"] = glue::createAnyMap();

  // use helper functions to cast to maps or callbacks.
  // the result will evaluate to `false` if no cast is possible.
  if (auto f = map["myCallback"].asFunction()) {
    // callbacks are stored as a `revisited::AnyFunction` and can accept both values or `Any` arguments
    // (here `map["myNumber"]` is casted to an `Any` through the `*` operator)
    // `get<T>` returns casts the value to `T` or throws an exception if not possible
    std::cout << f(false, *map["myNumber"]).get<int>() << std::endl;
  }

  // inner maps are also `glue::MapValue`s.
  map["myMap"].asMap()["inner"] = "inner value";
}

Classes

Glue also has built-in support for maps representing classes and inheritance.

#include <glue/class.h>
#include <glue/context.h>
#include <iostream>

struct A {
  std::string member;
};

struct B: public A {
  B(std::string value) : A{value} {}
  int method(int v) { return int(member.size()) + v; }
};

void classExample() {
  auto map = glue::createAnyMap();

  // `glue::createClass<T>` is a convience function for declaring maps for class APIs
  // `addConstructor<Args...>()` adds a function that constructs `A` given the argument types `Args...`
  // `.addMember("name", &T::member)` adds a setter (`member`) and getter (`setMember`) function
  map["A"] = glue::createClass<A>()
    .addConstructor<>()
    .addMember("member", &A::member)
  ;

  // classes can be made inheritance aware
  // `setExtends(map)` uses the argument map or callback to retrieve undefined keys
  // `glue::WithBases<A>()` adds implicit conversions to the listed base classes
  // `addMethod("name", &T::method)` adds a method that calls a member function or lambda
  map["B"] = glue::createClass<B>(glue::WithBases<A>())
    .setExtends(map["A"])
    .addConstructor<std::string>()
    .addMethod("method", &B::method)
    .addMethod("lambda", [](const B &b, int x){ return b.member.size() + x; })
  ;

  // contexts collect map class data and can be used to test instance creation
  glue::Context context;
  context.addRootMap(map);

  // `glue::Instance` captures a value and behaves as a class instance
  auto b = context.createInstance(map["B"][glue::keys::constructorKey]("arg"));

  // calls will be treated as member functions
  std::cout << b["member"]().get<std::string>() << std::endl;
  b["setMember"]("new value");
  std::cout << b["lambda"](10).get<int>() << std::endl;
}

Declarations

Glue can automatically generate declarations for type-safe scripting using TypeScript or TypeScriptToLua.

glue::DeclarationPrinter printer;
printer.init();
printer.print(std::cout, map, &context);

In the example above, this would result in the following declarations.

/** @customConstructor A.__new */
declare class A {
  constructor()
  member(): string
  setMember(arg1: string): void
}
/** @customConstructor B.__new */
declare class B extends A {
  constructor(arg0: string)
  lambda(arg1: number): number
  method(arg1: number): number
}

Supported bindings

Here you can find current and planned bindings for Glue.

  • [x] Lua: LuaGlue
  • [x] JavaScript (Wasm): EmGlue
  • [ ] JavaScript (Duktape)
  • [ ] Python
  • [ ] Java
  • [ ] Swift
  • [ ] Rust

As you can see, many bindings are still missing from the list. If you've created Glue bindings for any language, feel free to open a PR to add them here.

Limitations

  • No support for pointer or optional arguments in callbacks (yet)
  • Class bindings aren't as sophisticated as other libraries bindings such as sol2
  • There is a small overhead through the additional abstraction layer compared to native bindings

Usage

Glue can be easily added to your project using CPM.cmake.

CPMAddPackage(
  NAME Glue
  VERSION 1.0
  GIT_REPOSITORY https://github.com/TheLartians/Glue.git
)

target_link_libraries(myLibrary Glue)

See here for an example project using Glue to create TypeScript bindings for C++.


Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
C Plus Plus (367,368
Typescript (246,428
Cpp (15,487
Bindings (905
Scripting Language (460
Automatic (433
Inheritance (241
Related Projects