olua is a code generation-based Lua binding library. It can generate Lua binding code for C++ classes, enums, lambda functions, operator functions, instantiated templates, etc. You can customize the generated details, such as inserting code before and after function calls, generating code blocks with exception handling, creating iterators, generating Lua comments, and more.
Create a new directory to store the configuration and olua script with the following structure:
tree -L 2 .
.
├── build.lua
└── olua -> git@github.com:zhongfq/olua.git
clang {
'-DOLUA_DEBUG',
'-Isrc',
'-I../common',
'-I../..',
}
require "olua"
OLUA_AUTO_EXPORT_PARENT = true
clang {
'std=c++17'
}
module "example"
output_dir "src"
headers [[
#include "Example.h"
#include "xlua.h"
]]
include "../common/lua-object.lua"
typeconf "example::Hello"
Once the required classes for export are configured, you can run the following command to export the bindings:
lua build.lua
You can adjust the scanning behavior by setting variables in the build.lua
file:
olua.AUTO_BUILD
: Defaulttrue
. Automatically exports the binding code after scanning.olua.AUTO_GEN_PROP
: Defaulttrue
. Automatically generatesname
andvisible
properties forgetName
andisVisible
.olua.AUTO_EXPORT_PARENT
: Defaultfalse
. Automatically exports the parent class if it is not explicitly specified usingtypeconf
.olua.ENABLE_DEPRECATED
: Defaultfalse
. Exports deprecated methods or variables.olua.ENABLE_WITH_UNDERSCORE
: Defaultfalse
. Exports variables or methods starting with_
.olua.MAX_VARIADIC_ARGS
: Default 16. Specifies the maximum number of arguments for generating overloaded methods.olua.ENABLE_EXCEPTION
: Defaultfalse
. Generates code with exception handling.olua.CAPTURE_MAINTHREAD
: Defaultfalse
. Captures theL
main thread.olua.PARSE_ALL_COMMENTS
: Defaultfalse
. Parses all comments.
Specifies the module name, which is also part of the exported file name.
module "example"
Exported information:
- Exported file names:
lua_example.h
andlua_example.cpp
- Module function:
luaopen_example
Specifies the directory for the exported files, which can be either an absolute path or a relative path.
output_dir '../../src'
Specifies the required header files. These are prerequisites for successful compilation.
headers [[
#include "lua-bindings/lua_conv.h"
#include "lua-bindings/lua_conv_manual.h"
#include "cclua/xlua.h"
#include "Example.h"
]]
If handwritten code needs to be included, it can be specified using codeblock
. This code is copied as-is into the exported file.
codeblock [[
static const std::string makeScheduleCallbackTag(const std::string &key)
{
return "schedule." + key;
}]]
Inserts code into the luaopen
function.
module 'example'
luaopen 'printf("hello luaopen!");'
Generated code:
static int luaopen_example(lua_State *L)
{
olua_require(L, "Hello", luaopen_Hello);
...
printf("hello luaopen!");
return 1;
}
Specifies the directory for the generated Lua annotation API.
api_dir '../../addons/example'
Specifies the class returned by the luaopen
function.
entry 'example::Hello'
Generated code:
OLUA_LIB int luaopen_example(lua_State *L)
{
olua_require(L, ".olua.module.example", _olua_module_example);
if (!olua_getclass(L, "example.Hello")) {
luaL_error(L, "class not found: example::Hello");
}
return 1;
}
Specifies types that should not be exported. If a type is excluded, any methods or variables involving this type are also ignored.
-- exclude example::Command and example::Command *
exclude_type 'example::Command'
-- exclude example::Command * and example::Command **
exclude_type 'example::Command *'
Imports another configuration file.
import 'olua/lua-types.lua'
Customizes the Lua class name generation function.
luacls(function (cppname)
return string.gsub(cppname, "::", ".")
end)
macro
is typically used for conditional compilation.
macro '#ifdef CCLUA_BUILD_EXAMPLE'
typeconf "Object"
macro '#endif'
Code generated for Object
will be wrapped with CCLUA_BUILD_EXAMPLE
.
#ifdef CCLUA_BUILD_EXAMPLE
// Code generated for Object
#endif
The typedef
directive defines a type, usually when you've manually implemented the type's convertor. It links the type with the configuration through typedef
.
typedef 'ClassName'
[.luacls]
[.conv]
[.packable]
[.packvars]
[.smartptr]
[.override]
[.default]
[.luatype]
[.from_string]
[.from_table]
Specifies the Lua class name.
Specifies the converter. If the type or converter is not explicitly declared, the default is olua_$$_ClassName
.
Indicates whether the type supports @pack
and @unpack
.
Specifies how many member variables the type comprises.
Specifies whether the type is a smart pointer. If true, for example, std::shared_ptr<Node *>
is treated as a whole rather than as a template container.
Replaces existing type information.
Specifies the default value for the type.
Specifies the Lua type.
Indicates whether the class's constructor supports initialization from a string. Defaults to false
.
Example:
static int _olua_fun_std_filesystem_ls$1(lua_State *L)
{
try {
olua_startinvoke(L);
std::filesystem::path *arg1; /** dir */
std::filesystem::path arg1_from_string; /** dir */
bool arg2 = false; /** recursive */
if (olua_isstring(L, 1)) {
olua_check_string(L, 1, &arg1_from_string);
arg1 = &arg1_from_string;
} else {
olua_check_object(L, 1, &arg1, "fs.path");
}
olua_check_bool(L, 2, &arg2);
// @extend(fs::fs_extend) static olua_Return ls(lua_State *L, std::filesystem::path dir, @optional bool recursive)
olua_Return ret = fs::fs_extend::ls(L, *arg1, arg2);
olua_endinvoke(L);
return (int)ret;
} catch (std::exception &e) {
lua_pushfstring(L, "std::filesystem::ls(): %s", e.what());
luaL_error(L, olua_tostring(L, -1));
return 0;
}
}
Indicates whether instances of the class can be created using a table. Defaults to false
.
Example:
OLUA_LIB void olua_check_table(lua_State *L, int idx, example::Point *value)
{
float arg1 = 0; /** x */
float arg2 = 0; /** y */
olua_getfield(L, idx, "x");
olua_check_number(L, -1, &arg1);
value->x = arg1;
lua_pop(L, 1);
olua_getfield(L, idx, "y");
olua_check_number(L, -1, &arg2);
value->y = arg2;
lua_pop(L, 1);
}
static int _olua_fun_example_Point___add$1(lua_State *L)
{
olua_startinvoke(L);
example::Point *self = nullptr;
example::Point *arg1; /** p */
example::Point arg1_from_table; /** p */
olua_to_object(L, 1, &self, "example.Point");
if (olua_istable(L, 2)) {
olua_check_table(L, 2, &arg1_from_table);
arg1 = &arg1_from_table;
} else {
olua_check_object(L, 2, &arg1, "example.Point");
}
// @operator(operator+) example::Point operator+(const example::Point &p)
example::Point ret = (*self) + (*arg1);
int num_ret = olua_copy_object(L, ret, "example.Point");
olua_endinvoke(L);
return num_ret;
}
typedef 'example::vector'
typedef 'example::Color'
.packable 'true'
.packvars '4'
typedef 'Uint'
.conv 'olua_$$_integer'
typedef 'Uint *'
.conv 'olua_$$_array'
.luacls 'olua.uint'
typeconf
is used to specify the export of a class or enum, including static methods, static variables, object methods, and object variables. It also generates typedef
information based on the scanned data, equivalent to:
-- C++ object
typedef 'Object'
.conv 'olua_$$_object'
.luacls 'Object'
-- Enum
typedef 'Object'
.conv 'olua_$$_enum'
.luacls 'Object'
typeconf 'ClassName'
[.codeblock]
[.luaname]
[.supercls]
[.packable]
[.packvars]
[.luaopen]
[.indexerror]
[.from_string]
[.from_table]
[.exclude]
[.include]
[.macro]
[.iterator]
[.once]
[.extend]
[.var]
[.attr]
[.index]
[.tag_scope]
[.tag_store]
[.tag_maker]
[.tag_mode]
[.tag_usepool]
[.insert_before]
[.insert_after]
[.insert_cbefore]
[.insert_cafter]
[.func]
[.body]
[.inst]
[.luafn]
[.tag_scope]
[.tag_store]
[.tag_maker]
[.tag_mode]
[.tag_usepool]
[.insert_before]
[.insert_after]
[.insert_cbefore]
[.insert_cafter]
[.ret]
[.arg1...N]
[.prop]
[.get]
[.set]
Specifies handwritten code for export.
typeconf 'Object'
.codeblock [[
static std::string makeForeachTag(int value)
{
return "foreach" + std::to_string(value);
}
]]
Customizes the Lua name for methods or variables.
typeconf 'Object'
.luaname(function (name)
if name == 'print' then
name = 'dump'
end
return name
end)
static int luaopen_Object(lua_State *L)
{
oluacls_class<Object>(L, "Object");
oluacls_func(L, "dump", _Hello_print);
...
return 1;
}
Specifies the parent class name. Defaults to nil
and is determined by the export tool based on scanned data.
typeconf 'Hello'
.supercls 'Object'
Indicates that this class supports @pack
and @unpack
. Automatically generates the following functions:
OLUA_LIB void olua_pack_object(lua_State *L, int idx, Object *value);
OLUA_LIB int olua_unpack_object(lua_State *L, const Object *value);
OLUA_LIB bool olua_canpack_object(lua_State *L, int idx, const Object *);
Specifies the number of member variables for this type. If set, you must manually provide the above functions.
Inserts code into the luaopen_Hello
function.
typeconf 'Hello'
.luaopen 'printf("hello luaopen!");'
static int luaopen_Hello(lua_State *L)
{
oluacls_class<Hello, Object>("Hello");
...
printf("hello luaopen!");
return 1;
}
Specifies the accessibility of class properties.
typeconf 'Object'
.indexerror 'rw'
r
: Throws an error when accessing non-existent properties.w
: Throws an error when writing new properties.
Indicates whether the class constructor supports string initialization. Defaults to false
.
typeconf "std::filesystem::path"
.extend "fs::path_extend"
.from_string "true"
.iterator "std::filesystem::path::iterator"
Indicates whether the class supports creating instances using a table. Defaults to false
.
Exports all methods and variables by default, except those specified.
typeconf 'Object'
.exclude 'visible'
.exclude 'retain'
Supports wildcard patterns: .exclude '^m_.*'
Exports no methods and variables by default, except those specified.
typeconf 'Object'
.include 'visible'
.include 'retain'
Specifies methods that should be conditionally compiled using macros.
typeconf 'Object'
.macro '#ifdef CCLUA_OS_ANDROID'
.func 'pay'
.macro '#endif'
#ifdef CCLUA_OS_ANDROID
static _Object_pay(lua_State *L)
{
...
return 0;
}
#endif
static int luaopen_Object(lua_State *L)
{
oluacls_class<Object>(L, "Object");
#ifdef CCLUA_OS_ANDROID
oluacls_func("pay", _Object_pay);
#endif
...
return 1;
}
Generates an iterator.
typeconf "std::filesystem::path"
.iterator "std::filesystem::path::iterator"
static int _olua_fun_std_filesystem_path___pairs(lua_State *L)
{
try {
olua_startinvoke(L);
auto self = olua_toobj<std::filesystem::path>(L, 1);
int ret = olua_pairs<std::filesystem::path, std::filesystem::path::iterator>(L, self, false);
olua_endinvoke(L);
return ret;
} catch (std::exception &e) {
lua_pushfstring(L, "std::filesystem::path::__pairs(): %s", e.what());
luaL_error(L, olua_tostring(L, -1));
return 0;
}
}
Extends the specified class.
typeconf 'Object'
.extend 'ObjectExtend'
Merges all static methods of ObjectExtend
into Object
.
.ret
and .arg1...N
provide details on how to handle return values and arguments during function export. For detailed usage instructions, refer to Parameter Tags.
.insert_before
, .insert_after
, .insert_cbefore
, and .insert_cafter
specify code insertion points during function execution. For detailed usage, refer to Code Insertion.
.tag_scope
, .tag_store
, .tag_maker
, .tag_mode
, and .tag_usepool
define how to configure callback functions. For detailed instructions, refer to Callback Function Configuration.
Defines a method.
typeconf 'Object'
.func 'dump'
.body [[
printf("call dump!");
return 0;
]]
...
static int _Object_dump(lua_State *L)
{
printf("call dump!");
return 0;
}
...
static int luaopen_Object(lua_State *L)
{
oluacls_class<Object>(L, "Object");
oluacls_func(L, "dump", _Object_dump);
...
return 1;
}
.inst
Instantiate the template function.
typeconf "OpenXLSX::XLQuery"
.func "setParam"
.inst "setParamString<std::string>"
.inst "setParamInt<uint64_t>"
.inst "setParamNumber<double>"
.inst "setParamBool<bool>"
.func "getParam"
.inst "getParamString<std::string>"
.inst "getParamInt<uint64_t>"
.inst "getParamNumber<double>"
.inst "getParamBool<bool>"
.ret
and .arg1...N
provide details on how to handle return values and arguments during function export. For detailed usage instructions, refer to Parameter Tags.
.insert_before
, .insert_after
, .insert_cbefore
, and .insert_cafter
specify code insertion points during function execution. For detailed usage, refer to Code Insertion.
.tag_scope
, .tag_store
, .tag_maker
, .tag_mode
, and .tag_usepool
define how to configure callback functions. For detailed instructions, refer to Callback Function Configuration.
Defines a property.
typeconf 'Object'
.prop 'visible'
.get 'bool isVisible()'
.prop 'y'
.get 'int getY()'
.set 'void setY(int value)'
.prop 'z'
.get [[
Object *obj = olua_toobj<Object>(L, 1);
int ret = self->getZ();
lua_pushinteger(L, ret);
return 1;
]]
.set [[
Object *obj = olua_toobj<Object>(L, 1);
int arg1 = (int)olua_checkinteger(L, 2);
self->setZ(arg1);
return 0;
]]
...
static int _Object_get_z(lua_State *L)
{
Object *obj = olua_toobj<Object>(L, 1);
int ret = self->getZ();
lua_pushinteger(L, ret);
return 1;
}
...
static int luaopen_Object(lua_State *L)
{
oluacls_class<Object>(L, "Object");
oluacls_prop(L, "visible", _Object_isVisible, NULL);
oluacls_prop(L, "y", _Object_getY, _Object_setY);
oluacls_prop(L, "z", _Object_get_z, _Object_set_z);
...
return 1;
}
Exports only type information without any methods or variables, equivalent to:
typeconf 'Object'
.exclude '*'
Allows code insertion at specific points during export. There are four insertion points:
insert_before
: Before the function call.insert_after
: After the function call.insert_cbefore
: Before the callback function call.insert_cafter
: After the callback function call.
typeconf 'Object'
.func 'pay'
.insert_before [[
printf("hello before!");
]]
.insert_after [[
printf("hello after!");
]]
.insert_cbefore [[
printf("hello callback_before!");
]]
.insert_cafter [[
printf("hello callback_after!");
]]
Generated code:
static int _Object_pay(lua_State *L)
{
...
printf("hello before!");
self->pay([cb_store, cb_name, cb_ctx]() {
...
printf("hello callback_before!");
olua_callback(L, cb_store, cb_name.c_str(), 0);
printf("hello callback_after!");
...
});
printf("hello after!");
...
return 0;
}
Defines the details of std::function
callbacks. Refer to: olua Callback Function Design
typeconf 'Object'
.func 'onClick'
.tag_usepool 'true'
.tag_mode 'replace|new|startwith|equal'
.tag_store '0'
.tag_maker 'click'
.tag_scope 'object|once|invoker'
Callback Storage Details:
callback functions
obj.uservalue {
|---id----|--class--|--tag--|
.olua.cb#1$classname@onClick = lua_func
.olua.cb#2$classname@onClick = lua_func
.olua.cb#3$classname@update = lua_func
.olua.cb#4$classname@onRemoved = lua_func
...
}
tag_usepool
: Specifies whether the callback's arguments use the object pool. Default:true
.tag_mode
: Specifies the tag matching mode. Defaults:replace
: Replaces existing callback if the tag matches, otherwise creates a new tag.new
: Always creates a new tag for storing the callback.startwith
: Deletes callbacks whose tags start with the specified value.equal
: Deletes callbacks with matching tags.
tag_store
: Specifies where the callback is stored. Default:0
. Possible values:0
: For static methods, stored in.classobj
. For object methods, stored inuserdata
.-1
: Stored in the return value.1, 2...N
: Stored in the N-th argument from left to right.
tag_maker
: Specifies the key for storing the callback. Two forms:string
: A literal string.makeTag(#N)
ormakeTag(#-N)
: Uses the N-th argument to generate the key viamakeTag
.
tag_scope
: Specifies the callback's lifecycle. Default:object
. Possible values:object
: Managed by the object.once
: Removed after a single invocation.invoker
: Removed after the underlying function call.
Example generated code:
static int _Object_onClick(lua_State *L)
{
...
void *cb_store = (void *)self;
std::string cb_tag = "click";
std::string cb_name;
if (olua_isfunction(L, 2)) {
cb_name = olua_setcallback(L, cb_store, 2, cb_tag.c_str(), OLUA_TAG_REPLACE);
olua_Context cb_ctx = olua_context(L);
arg1 = [cb_store, cb_name, cb_ctx](Object *arg1) {
lua_State *L = olua_mainthread(NULL);
olua_checkhostthread();
if (olua_contextequal(L, cb_ctx)) {
int top = lua_gettop(L);
size_t last = olua_push_objpool(L);
olua_enable_objpool(L);
olua_push_obj(L, arg1, "Object");
olua_disable_objpool(L);
olua_callback(L, cb_store, cb_name.c_str(), 1);
olua_pop_objpool(L, last);
lua_settop(L, top);
}
};
} else {
olua_removecallback(L, cb_store, cb_tag.c_str(), OLUA_TAG_equal);
arg1 = nullptr;
}
// void onClick(@nullable const Object::ClickCallback &callback)
self->onClick(arg1);
return 0;
}
Both .ret
and .arg1...N
support the @
keyword to add additional behaviors to parameters.
Marks the return value as newly created and applies olua_postnew
.
typeconf 'Object'
.func 'create' .ret '@postnew'
static int _Object_create(lua_State *L)
{
...
Object ret = Object::create();
// Insert code after the call
olua_postnew(L, ret);
...
return 0;
}
Marks the parameter as nullable (can be nil
).
-- void onClick(const ClickCallback &callback);
typeconf 'Object'
.func 'onClick' .arg1 '@nullable'
static int _Object_onClick(lua_State *L)
{
if (olua_isfunction(L, 2)) {
arg1 = ...
} else {
arg1 = nullptr;
}
// void onClick(@nullable const ClickCallback &callback);
self->onClick(arg1);
return 0;
}
Adds a reference to a parameter: @addref(name mode [where])
.
name
: Reference name.mode
: Reference storage mode:^
: Exists independently.|
: Coexists with others.
Example with ^
(independent existence):
-- void setScene(Object *scene);
typeconf 'Object'
.func 'setScene' .arg1 '@addref(scene ^) @nullable'
static int _Object_setScene(lua_State *L)
{
...
self->setScene(arg1);
...
olua_addref(L, 1, "scene", -1, OLUA_REF_ALONE);
...
}
Example with |
(coexistence):
-- void addChild(Object *child);
typeconf 'Object'
.func 'addChild' .arg1 '@addref(children |)' .ret '@delref(children ~)'
static int _Object_addChild(lua_State *L)
{
...
olua_startcmpref(L, 1, "children");
self->addChild(arg1);
olua_addref(L, 1, "children", -1, OLUA_REF_MULTI);
olua_endcmpref(L, 1, "children");
...
}
If a where
is specified, you must include insertion code to retrieve it.
-- void show();
typeconf 'Object'
.func 'show' .ret '@addref(children | parent)'
.insert_before [[
olua_pushobj<Object>(L, Object::getRoot());
int parent = lua_gettop(L);
]]
static int _Object_show(lua_State *L)
{
...
olua_pushobj<Object>(L, Object::getRoot());
int parent = lua_gettop(L);
self->show();
olua_addref(L, parent, "children", 1, OLUA_REF_MULTI);
...
}
Removes a reference: @delref(name mode [where])
.
mode
: Reference storage modes:^
: Independent existence.|
: Coexistence.~
: Removes references using comparison.*
: Removes all references.
Example with ^
:
-- void setScene(Object *scene);
typeconf 'Object'
.func 'setScene' .arg1 '@delref(scene ^) @nullable'
static int _Object_setScene(lua_State *L)
{
...
self->setScene(arg1);
olua_delref(L, 1, "scene", -1, OLUA_REF_ALONE);
...
}
Example with ~
:
-- void removeChildByName(const std::string &name);
typeconf 'Object'
.func 'removeChildByName' .ret '@delref(children ~)'
static int _Object_removeChildByName(lua_State *L)
{
...
olua_startcmpref(L, 1, "children");
self->removeChildByName(arg1);
olua_endcmpref(L, 1, "children");
...
}
Marks a parameter as optional. Primarily used with .var
and .func
.
Example for .var
:
typeconv 'Object'
.var 'x' .optional 'true'
void olua_check_Object(lua_State *L, int idx, Object *value)
{
...
olua_getfield(L, idx, "x");
if (!olua_isnoneornil(L, -1)) {
olua_check_integer(L, -1, &arg1);
value->x = arg1;
}
lua_pop(L, 1);
}
Packs multiple parameters into a single object.
-- void setPosition(const Point &p);
-- Point convert(const Point &p);
typeconf 'Object'
.func 'setPosition' .arg1 '@pack'
.func 'convert' .arg1 '@pack'
local obj = Object.new()
obj:setPosition(1, 1)
obj:setPosition({x = 1, y = 1})
local p = obj:convert({x = 1, y = 1})
local x, y = obj:convert(1, 1)
Unpacks an object into multiple values.
-- const Point &getPosition();
typeconf 'Object'
.func 'getPosition' .ret '@unpack'
local obj = Object.new()
local x, y = obj:getPosition()
Marks a variable as read-only, generating only a getter function.
Specifies an alternative type for a parameter.
void read(char *buf, size_t *len);
Normal behavior treats buf
as a string, but if it's a writable buffer, @type
can clarify the intention.
typeconf 'Object'
.func 'read'
.arg1 '@type(olua_char_t *)'
Use macro commands to directly annotate parameters or methods. The autoconf
script parses these annotations during the scanning phase.
Macro commands:
#define OLUA_EXCLUDE __attribute__((annotate("@exclude")))
#define OLUA_TYPE(name) __attribute__((annotate("@type("#name")")))
#define OLUA_NAME(name) __attribute__((annotate("@name("#name")")))
#define OLUA_ADDREF(...) __attribute__((annotate("@addref("#__VA_ARGS__")")))
#define OLUA_DEFREF(...) __attribute__((annotate("@delref("#__VA_ARGS__")")))
#define OLUA_PACK __attribute__((annotate("@pack")))
#define OLUA_UNPACK __attribute__((annotate("@unpack")))
#define OLUA_NULLABLE __attribute__((annotate("@nullable")))
#define OLUA_POSTNEW __attribute__((annotate("@postnew")))
#define OLUA_READONLY __attribute__((annotate("@readonly")))
#define OLUA_OPTIONAL __attribute__((annotate("@optional")))
#define OLUA_GETTER __attribute__((annotate("@getter")))
#define OLUA_SETTER __attribute__((annotate("@setter")))
Example:
class Object {
public:
static OLUA_POSTNEW Object *create();
void setScene(OLUA_ADDREF(^) OLUA_NULLABLE Scene *v);
OLUA_ADDREF(^) Scene *getScene();
OLUA_ADDREF(root ^) Scene *getRoot();
void addChild(OLUA_ADDREF(children |) Object *child);
OLUA_DELREF(children |) void removeChild(Object *child);
Point localToGlobal(OLUA_PACK const Point &p);
OLUA_READONLY int id;
};