Embedding Wren in C++, part 2

You can read part 1 here.

Writing you own Wren bindings gives you full control over how your code interfaces with Wren. However, manually implementing the binding code for a large C++-Wren interface can be somewhat time consuming, especially when changes are made to the interface over time. Wren++ is a small C++ library that aims to automate most code binding tasks with a minimal runtime overhead. Like Wren itself, Wren++ aims to be simple and minimalistic to use. Here are the features currently supported.

  • a RAII wrapper class for the virtual machine instance and refcounted methods
  • automatic binding code generation for any C++ type and method
  • a convenient way of calling methods from C++ and accessing return values
  • template-based – no macros! If you feel like the binding code in Wren++ is too verbose, you can roll your own macros.

The easiest way to get started with Wren++ is simply to include the source code in your project. Just remember to compile with C++14 features turned on! Alternatively, you can use the included premake script to generate a Makefile or project file for your preferred IDE to generate a static library to link to. In order to use Wren++, include Wren++.h in your code. You also need to link to the Wren static library.

Creating a VM instance

Wren++ provides reasonable defaults for the virtual machine configuration. To get started, just write:

#include <Wren++.h>

int main() {
  wrenpp::VM vm;
  wrenpp::Result result = vm.executeString("System.print(\"Hello, world\")");
  return 0;
}

Like the Wren C API itself, Wren++ provides the interpretation result as an enumeration class. The values are Success, CompileError, and RuntimeError.

You can also execute code directly from a module:

auto result = vm.executeModule("vector");

Module, class contexts and binding free functions to Wren

All method binding takes place in a module and class context. The binding methods return references back to the class context so that you can chain binding commands like in LuaBridge or Luabind. Let’s look at binding functions to the Math class that we did earlier.

wrenpp::beginModule("main")
  .beginClass("Math")
    .bindFunction<decltype(&cos), &cos>(true, "cos(_)")
    .bindFunction<decltype(&sin), &sin>(true, "sin(_)")
  .endClass()
.endModule();

bindFunction takes two template parameters and two function arguments. The template parameters are the declared type of the function, and then the function pointer value itself. Instead of writing decltype(&cos), we could have written double(*)(double). As for the function arguments, the boolean indicates whether the method is static, and the string is the method’s signature.

It is entirely optional to include endClass() and endModule() at the end of the binding statement. endClass() returns a reference back to the enclosing module context, so that you can continue binding code to a new class without having to open the context again. Not all binding statements have to be in the same place. You can reopen the module and class contexts somewhere else and continue binding code. All that the module and class contexts do is store the module and class names for binding method to use.

Binding types and methods to Wren

Binding types and their methods works very similarly. Let’s look at rebinding the Vec3f example from earlier. When binding a type, we use a different class context. It’s created using the template method bindClass:

wrenpp::beginModule("Vec3")
  .bindClass<Vec3f, float, float, float>("Vec3");

bindClass tells Wren++ not only to store the name of the class for future reference, but also to generate the allocator and finalizer functions for the given type. The first template parameter is the type of bound class. The following template parameters are optional – they are the values that can be passed to the bound type’s constructor from Wren. The three floats in this example mean that we can construct the type from Wren using three numbers:

foreign class Vec3 {
  construct new(x, y, z) {}
  // the rest of the methods as before
}

// now we can construct with values
var v = Vec3.new(2.0, 1.0, -3.0)

Even though we can bind to overloaded methods in Wren, we can bind only one constructor to a class.

Binding the rest of Vec3f’s methods is done using bindMethod:

wrenpp::beginModule("Vec3")
  .bindClass<Vec3f, float, float, float>("Vec3")
    .bindMethod<decltype(&Ve3f::norm),   &Vec3f::norm>(false, "norm()")
    .bindMethod<decltype(&Vec3f::dot),   &Vec3f::dot>(false, "dot(_)")
    .bindMethod<decltype(&Vec3f::cross), &Vec3f::cross>(false, "cross(_)");

The function arguments have the same meaning as for bindFunction.

Finally, let’s bind the accessors for the fields themselves:

  .bindGetter<decltype(Vec3::x), &Vec3::x>("x")
  .bindSetter<decltype(Vec3::x), &Vec3::x>("x=(_)")
  .bindGetter<decltype(Vec3::y), &Vec3::y>("y")
  .bindSetter<decltype(Vec3::y), &Vec3::y>("y=(_)")
  .bindGetter<decltype(Vec3::z), &Vec3::z>("z")
  .bindSetter<decltype(Vec3::z), &Vec3::z>("z=(_)")

The getters and setters are always non-static.

C++ and Wren lifetimes for returned objects

The rules for who owns the returned objects are simple. Objects which are returned from C++ by reference or by pointer have C++ lifetime, and will not be garbage collected by Wren. Objects which are returned from C++ by value have Wren lifetime and are thus garbage collected.

Binding your own implementations

Sometimes the calling convention of a C++ API is just not very amenable for binding to Wren, and you want to write your own glue code. For instance, pointers to primitive types like float or int might be passed to a function. Wren++ simply won’t handle function signatures like that, since Wren itself has no concept of out parameters. Instead, Wren++ allows you to bind functions of type WrenForeignMethodFn directly, so that you may manually implement the foreign function implementation.

Let’s look at binding the excellent dear imgui library to Wren. Looking at imgui.h, you can see lots of long function signatures with pointers to primitive types. We want to provide reasonable default arguments to most of the arguments, as well as return the new values, instead of passing in a reference to the number. Here’s the bare-bones Wren-interface we want to implement:

in a file called imgui.wren

class ImGui {
  foreign static begin( name )
  foreign static end()
  
  // unlike what C++ ImGui does, this RETURNS the new value
  // arguments are all numbers passed by value
  foreign static sliderFloat( label, value, min, max )
  foreign static setWindowSize( size )
}

Let’s implement wrappers for ImGui::Begin and ImGui::SliderFloat with reasonable default values.

void begin(WrenVM* vm) {
  ImGui::Begin((const char*)wrenGetSlotString(vm, 1), NULL, 0);
}

void sliderFloat(WrenVM* vm) {
  const char* label = wrenGetSlotString(vm, 1);
  float value = float(wrenGetSlotDouble(vm, 2));
  float min =   float(wrenGetSlotDouble(vm, 3));
  float max =   float(wrenGetSlotDouble(vm, 4));
  ImGui::SliderFloat(label, &value, min, max);
  wrenSetSlotDouble(vm, 0, value);
}

The begin and sliderFloat functions satisfy the WrenForeignMethodFn typedef, so we will bind them using the bindCFunction method. The bindCFunction method stores the function pointer without generating any calls to the Wren slot API, so that you can do it yourself.

wrenpp::beginModule("imgui")
  .beginClass("ImGui")
    .bindCFunction(true, "begin(_)", begin)
    .bindFunction<decltype(&ImGui::End), &ImGui::End>(true, "end()")
    .bindCFunction(true, "sliderFloat(_,_,_,_)", sliderFloat)
  .endClass();

Note that we just bound ImGui::End the usual way since it had a trivial function signature!

Accessing foreign bytes manually

Next off, how do we deal with foreign types in our manual foreign method implementations? You can’t get the pointer to your foreign type just by using wrenGetSlotForeign, since Wren++ contains a layer of indirection. The object may live in C++ (C++ lifetime), or within the foreign bytes (Wren lifetime) themselves. Wren++ provides a helper function: wrenpp::getSlotForeign<class T>(WrenVM* vm, int slot). To see how it’s used, let’s wrap ImGui::SetNextWindowSize(const ImVec2&) for our Wren interface to use. Let’s assume that you’ve bound ImGui::ImVec2 to Wren – it’s just a simple struct: struct ImVec2 { float x, y; };.

void setWindowSize(WrenVM* vm) {
  ImGui::SetNextWindowSize(*(wrenpp::getSlotForeign<ImVec2>(vm, 1)));
}

Returning foreign types manually

You can return values from your manual implementations as well. To see how, let’s look at one final slightly contrived example.

class Mouse {
  // returns a Vec2
  foreign static coordinates
}

We will implement it using our hypothetical MouseDevice class.

// the mouse device
class MouseDevice {
public:
  static MouseDevice* get();
  const Vec2i& coordinates() const;
};

// elsewhere, in the CFunction implementation...
void getMouseCoordinates(WrenVM* vm) {
  Vec2i coords = MouseDevice::get()->coordinates();
  wrenpp::setSlotForeignValue<Vec2i>(vm, 0, coords);
}

// we will skip over the binding code itself, as you've now seen it a few times

Note that, as the function’s name indices, setSlotForeignValue places coords by value into the foreign bytes of a new foreign object at a given slot. Any changes in the mouse coordinates within the C++ object will not be reflected in the wren object.

You could also the pass the coordinates by pointer to Wren. An array of foreign bytes is created, but they only hold the pointer.

void getMouseCoordinates(WrenVM* vm) {
  // we need to throw away the const-ness of our object -- Wren++ doesn't
  // respect constness in reference objects
  Vec2i* coords = const_cast<Vec2i*>(&MouseDevice->get()->coordinates());
  wrenpp::setSlotForeignPtr<Vec2i>(vm, 0, coords);
}

Now changes in the C++ class’ coordinates will be reflected in the Wren object, since it points directly to the coordinates in C++.

Accessing Wren code from C++

Accessing Wren code from C++ is, at the time of writing, the most incomplete feature of Wren++, and it will be worked on in the feature. Here’s what you can do currently. Wren++ allows you store a references to class methods that you can call from C++. First you need to get the method from the virtual machine instance.

// call the Math.cos(_) method from the previous part of this post
wrenpp::Method cosMethod = vm.method("main", "Math", "cos(_)");

The three string arguments to wrenpp::VM::method are the module name, the variable name that the method is attached to (note again that this would be the class name for a static method!), and finally the method signature itself.

We call the method by using the parenthesis operator on it.

wrenpp::Value val = cosMethod(-5.0);
std::cout << val.as<double>();

Invocations on the parenthesis operator returns an instance of wrenpp::Value, which is a convenience class that can hold any return value from Wren.

In conclusion

There were some small details I glossed over – for instance, Wren++ allows you to customize the print, error, and module functions just like in Wren. To see how, take a look at the project’s README. Also, Wren access from C++ is likely to develop somewhat as the Wren C API itself develops, but that will be documented in the README once I get around to working on it. Some things that I wish to include in the future is easy access to Wren data structures, such as maps and lists.

Now it is time for you to go off and explore Wren yourself. You should have the tools to embed Wren in your application with confidence. Wren doesn’t have a lot of libraries yet, so you are going to have to reinvent a lot of basic functionality yourself. But that’s the best part, naturally! :)

comments powered by Disqus