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:
|
|
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:
|
|
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.
|
|
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
:
|
|
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:
|
|
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
:
|
|
The function arguments have the same meaning as for bindFunction
.
Finally, let’s bind the accessors for the fields themselves:
|
|
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:
|
|
Let’s implement wrappers for ImGui::Begin
and ImGui::SliderFloat
with reasonable default values.
|
|
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.
|
|
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; };
.
|
|
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.
|
|
We will implement it using our hypothetical MouseDevice
class.
|
|
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.
|
|
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.
|
|
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.
|
|
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! :)