Lua C++ binding generator
Create lua bindings by parsing C++ header files using doxygen.
Generated lua bindings do not contain any external dependencies. Dub C++ files are copied along with the generated C++ bindings.
MIT license © Gaspard Bucher 2014.
Installation
With luarocks:
$ luarocks install dub
Speed
We tried our best to make these bindings as safe and fast as possible, ensuring we do not break return value optimization and we execute as few lines of code between Lua and C++. Even if we did our best to keep the overhead between Lua and C++ as small as possible, calling between Lua and C++ prevents some of the optimizations LuaJIT is very good at.
When needing very fast execution implying lots of Lua and C++ code, either push all data into C++ structures and work from there or push all data into Lua to avoid binding and function calls overhead.
For example, it would be a bad idea to loop through all the pixels of an image using operator[](int i) to implement a filter in Lua. In such a case, use LuaJIT FFI to build a buffer, copy content inside the buffer and work from there.
Use Case
Some of the main reasons for using this binding generator over other solutions such as 'LuaJIT FFI' are:
memory | Some libraries put all the burden of memory management on the end-user. Using FFI bindings, you still have to handle memory management. By using 'dub', you get garbage collection protection for pointers. We used this in our bindings for Bullet. |
abstraction | Sometime we want to write C++ wrapper code to keep an API as simple as possible. We did this for custom zmq bindings. See callbacks for another example. |
no magic | The generated bindings are easy to read C++ files without needing fancy runtime introspection or advanced C++ features. |
customizable | Method bindings can be customized with custom definitions or even entire ".cpp" file templates. |
Installation
Install with luarocks:
$ luarocks install dub
.VERSION = '2.2.2'
Current version of 'dub' respecting semantic versioning.
.DEPENDS = {
'lua >= 5.1, < 5.4'
Compatible with Lua 5.1 to 5.3 and LuaJIT
'lub >= 1.0.4, < 2'
Uses Lubyk base library
'xml ~> 1'
'yaml ~> 1'
Uses Lubyk yaml library
}
Usage example
The following code is used to generated bindings from all the headers located in include/xml
and output generated files inside src/bind
.
First we must parse headers. To do this, we create a dub.Inspector which we could also use to query function names, classes and other information on the parsed C++ headers.
local lub = require 'lub' local dub = require 'dub' local inspector = dub.Inspector { INPUT = { lub.path '|include/xml', }, }
We can now generate Lua bindings with the dub.LuaBinder.
local binder = dub.LuaBinder() binder:bind(inspector, { -- Mandatory library name. This is used as prefix for class types. lib_name = 'xml', output_directory = lub.path '|src/bind', -- Remove this part in included headers header_base = lub.path '|include', -- Open the library with require 'xml.core' (not 'xml') because -- we want to add some more Lua methods inside 'xml.lua'. luaopen = 'xml_core', })
The code above generates files such as:
xml_core.cpp | This contains bindings for methods in the 'xml' namespace, constants and also the main 'luaopen_xml_core' function. |
xml_Parser.cpp | This contains bindings for xml::Parser class. |
dub/... | Compile time dub C++ and header files. |
When building the library, make sure to include all C++ files (including those in the dub
folder. You should also include the binding folder in the include path so that #include "dub/dub.h"
is properly resolved.
You can view the generated files on xml lib on github.
Compatibility
The bindings generated by dub are heavily tested and are compatible with Lua 5.1, 5.2 and LuaJIT.
There is no external library dependency.
Binding style
Dub generates bindings so that using the library/class from Lua looks as familiar as possible for those knowing C++ code. For example, here is some code based on HelloWorld.cpp from Box2D.
In C++:
// Define the dynamic body. We set its position and call the body factory. b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; bodyDef.position.Set(0.0f, 4.0f); b2Body* body = world.CreateBody(&bodyDef); // Define another box shape for our dynamic body. b2PolygonShape dynamicBox; dynamicBox.SetAsBox(1.0f, 1.0f);
In Lua:
-- Define the dynamic body. We set its position and call the body factory. local bodyDef = b2.BodyDef() bodyDef.type = b2.dynamicBody bodyDef.position:Set(0.0, 4.0) local body = world:CreateBody(bodyDef) -- Define another box shape for our dynamic body. local dynamicBox = b2.PolygonShape() dynamicBox:SetAsBox(1.0, 1.0)
Extra features
Apart from supporting many advanced and complicated C++ features (such as inheritance, type casting, overloaded operators, etc). Dub has some nice additions to make working with C++ objects easier.
super
All C++ objects in Lua can be wrapped in a table and behave like the original objects with all the advantages of Lua tables. Simply set 'super' to the userdata in the wrapping class:
local p = xml.Parser() -- print(type(p)) --> userdata -- Wrap userdata in a Lua table local t = setmetatable({super = p}, xml.Parser) t.info = 'some attached information' -- Methods works just like t.super:parse(some_string) t:parse(some_string)
automatic cast to parent class
local p = foo.SubClassOfBase() local b = foo.Base()
If we have a setFriend
method which expects Base*
type. The following code will automatically check if p
is sub-type of Base and cast it. The Base class knows nothing of SubClassOfBase (which could be defined in another module):
-- Cast 'p' to foo.Base b:setFriend(p)
public class methods
Example:
class Foo { public: static void sayHello() { printf("Hello\n"); } };
Lua code
Foo.sayHello() --> Hello
public attributes read/write
Example:
class Foo { public: std::string name; void printName() { printf("My name is '%s'\n", name.c_str()); } };
Lua code
local foo = Foo() foo.name = 'Prometheus' foo:printName() --> My name is 'Prometheus'
Pointer assignment
Pointer assignment is made possible because we use member pointer garbage collection protection. It is thus safer to use in Lua as it is in plain C++ !
Example:
class Foo { std::string name; public: Foo(const char *n) : name(n) {} Foo *other; void printOther() { if (other) { printf("Other name is '%s'\n", other->name.c_str()); } else { printf("No other\n"); } } };
Lua code
local a = Foo 'Armand' local b = Foo 'Bob' a.other = b a:printOther() --> Other name is 'Bob' b = nil collectgarbage 'collect' --> b is not destroyed because a references it a:printOther() --> Other name is 'Bob' a.other = nil a:printOther() --> No other collectgarbage 'collect' --> b is destroyed
Operator overloading
All operator[xx]()
functions in C++ are translated to native Lua operators when possible. See operators for a complete list.
C++:
Foo x(4); Foo y(3); Foo z = x + y; // calls x.operator+(y)
In Lua:
local x, y = Foo(4), Foo(3) local z = x + y -- calls x:__add(y) which calls operator+()
Callbacks
Calling Lua from C++ is made easy by deriving a class from dub::Thread, this class can then be used to call Lua methods on 'self' in an efficient way. For example, let's say we have a native QWidget class and we want to implement the 'resized' callback:
class Widget : public QWidget, public dub::Thread { Q_OBJECT public: Widget(int window_flags) : QWidget(NULL, (Qt::WindowFlags)window_flags = 0) {} ~Widget() {} protected: virtual void resizeEvent(QResizeEvent *event) { // If your code is multi-threaded (i.e. no single event loop) // you need to use a mutex here or better yet, use a FIFO queue // and a pipe between threads. // Get 'resized' callback on Lua object if (!dub_pushcallback("resized")) return; // dub_L is our object's current thread lua_pushnumber(dub_L, width); lua_pushnumber(dub_L, height); // 3 arguments, no return value dub_call(3, 0); } };
Usage in Lua:
win = gui.Widget() function win:resized(w, h) print('Hey why did you resize my window ?') end
Note that the resized
function defined on win
object is a regular Lua function and this can be called from Lua as well:
-- manual call to resized callback win:resized(win:width(), win:height())
Errors in callbacks
If some error occurs during a callback, this error does not disappear in limbo between C++ and Lua (and does not cause the C++ code to crash). The error is captured during the call and either printed out or handled by a custom error
function defined on the called object.
Example:
win = gui.Widget() function win:error(msg) print('Something went wrong in my window.', msg) end function win:resized(w, h) if (w > 300) then error('Bird crash!') end end
Custom bindings
Sometimes we need to write custom code either because 'dub' is cannot guess the proper way to call a function or because we want to make it more Lua-like (allowing multiple return values for example). To use custom bindings, pass the path to a folder containing yaml files (one per class). The yaml file for global functions should be named after the namespace (if any) or '_global.yml' if there is no namespace.
Things to note when writing the bindings:
self | This variable is set before the custom binding code and corresponds to the C++ object the binding is called on. Static methods and plain functions do not have 'self' set. |
arguments | Argument types and conversions are handled by dub. You can reference them by their names in the header declaration. |
return | If you are returning a value, you must add the call to return with the number of values pushed on the stack. |
arg count | If your method takes a varying number of arguments, you can use 'arg0', 'arg1' to set bindings for each case (see 'showFullScreen' example below). |
file name | The name of the YAML file should reflect the namespace or class being bound. For global functions, use "_global.yaml". |
inline | You can define the custom bindings by passing a lua table instead of a path to dub.LuaBinder.bind. See below for the format. |
YAML file format
For example, to define custom bindings for Rect::size, you need to create a 'Rect.yml' file like this:
lua: methods: size: | Point sz = self->size(); lua_pushnumber(L, sz.x()); lua_pushstring(L, sz.y()); return 2;
You do not have to do the arguments type checking. This is done by dub. You do need to return the number of Lua return values if there are any.
You can also define your own methods by using custom headers instead of the original ones. This is what we did for Qt bindings to whitelist the bindings we wanted. We also chose to declare inexistant methods and bind them by hand:
lua: methods: swapFullScreen: | if (!self->isFullScreen()) { self->showFullScreen(); } else { self->showNormal(); } showFullScreen: # Varying custom binding on arg count for overloaded-functions. arg0: | self->showFullScreen(); arg1: | if (enable) { self->showFullScreen(); } else { self->showNormal(); } globalMove: | self->move( self->mapToParent( self->mapFromGlobal(QPoint(x, y)) ) );
Note that we do not need to write the code testing argument.
Inline custom bindings
This is a quick and dirty solution when you only need to define a couple of custom bindings. In this case, you pass a table to the binder:
local bind = dub.LuaBinder() binder:bind(inspector, { lib_name = 'foo', output_directory = lub.path '|src/bind', custom_bindings = { Rect = { methods = { size = [=[ Point sz = self->size(); lua_pushnumber(L, sz.x()); lua_pushstring(L, sz.y()); return 2; ]=], }, }, }, })
The table format is exactly the same as the yaml files except for the 'lua' root key which is not needed.
Other features
As time passes, the undocummented feature list below will shrink. Until then, it's better having an idea what Dub does.
- pseudo-attributes read/write by calling getter/setter methods.
- custom read/write attributes (with void *userdata helper, union handling)
- public static attributes read/write
- pointer to member (gc protected)
- cast(default)/copy/disable const attribute
- natural casting from std::string to string type (can include '\0')
- class instantiation from templates through typedefs
- class alias through typedefs
- bindings for superclass
- default argument values
- overloaded functions with optimized method selection from arguments
- return value optimization (no copy)
- simple type garbage collection optimization (no __gc method)
- namespace
- nested classes
- class enums
- global enums
- build system
- group multiple bindings in a single library
- rewrite class or library names
- native Lua table wrapping setmetatable({super = obj}, Vect)
- fully tested
- custom method binding name
- remove exception catching if never throws
C++ exceptions
The dub library catches all exceptions thrown during binding execution and passes the exceptions as lua errors. Internally, the library itself throws dub::Exception
and dub::TypeException
.
Verbosity
.warn_level = 5
Default warning level (anything below or equal to this level will be notified). It is usually good to leave this warning level to 5 and fix individual warning messages by explicitely ignoring the problematic function during binding generation.
.warn (level, format, ...)
Warning function. Can be overwriten. The level
parameter is a value between 1 and 10 (the higher the level the less important the message).
Classes
CTemplate
(internal) C++ template definition. This is a sub-class of dub.Class.
Class
(internal) A C++ class/struct definition.
Function
(internal) A public C++ function or method definition.
Inspector
The Inspector 'knows' about the functions and classes and can answer queries.
LuaBinder
Use the dub.Inspector to create Lua bindings.
LuaBinderTODO MISSING DOCUMENTATION
#addCustomTypesTODO MISSING DOCUMENTATION
#parseCustomBindingsTODO MISSING DOCUMENTATION
#functionBodyTODO MISSING DOCUMENTATION
#bindNameTODO MISSING DOCUMENTATION
#customTypeAccessorTODO MISSING DOCUMENTATION
#libNameTODO MISSING DOCUMENTATION
#luaTypeTODO MISSING DOCUMENTATION
#toStringBodyTODO MISSING DOCUMENTATION
#resolveTypesTODO MISSING DOCUMENTATION
LuaBinderTODO move this into getParam ?
#openClassesTODO MISSING DOCUMENTATION
MemoryStorage
(internal) This is used to store all definitions in memory.
#findByFullnameTODO MISSING DOCUMENTATION
#findChildTODO MISSING DOCUMENTATION
#findChildForTODO MISSING DOCUMENTATION
#functionsTODO MISSING DOCUMENTATION
#variablesTODO MISSING DOCUMENTATION
#childrenTODO MISSING DOCUMENTATION
#superclassesTODO MISSING DOCUMENTATION
#constantsTODO MISSING DOCUMENTATION
#hasConstantsTODO MISSING DOCUMENTATION
#namespacesTODO MISSING DOCUMENTATION
#resolveTypeTODO MISSING DOCUMENTATION
#fullnameTODO MISSING DOCUMENTATION
#ignoredTODO MISSING DOCUMENTATION
Namespace
(internal) A C++ namespace definition with nested classes, enums and functions.
NamespaceTODO MISSING DOCUMENTATION
#namespacesTODO MISSING DOCUMENTATION
#functionsTODO MISSING DOCUMENTATION
#setNameTODO MISSING DOCUMENTATION
#fullnameTODO MISSING DOCUMENTATION
#childrenTODO MISSING DOCUMENTATION
OptParser
(internal) Simplified yaml parser to retrieve @dub inline options.
OptParserTODO MISSING DOCUMENTATION
OptParserTODO MISSING DOCUMENTATION