Skip to content

Instantly share code, notes, and snippets.

@Youka
Last active May 16, 2024 19:59
Show Gist options
  • Save Youka/2a6e69584672f7cb0331 to your computer and use it in GitHub Desktop.
Save Youka/2a6e69584672f7cb0331 to your computer and use it in GitHub Desktop.
Example of Lua in C++ and userdata objects
// Lua C API
#include <lua.hpp>
// C++ input/output streams
#include <iostream>
// MyObject as C++ class
class MyObject{
private:
double x;
public:
MyObject(double x) : x(x){}
void set(double x){this->x = x;}
double get() const{return this->x;}
};
// MyObject identifier for the Lua metatable
#define LUA_MYOBJECT "MyObject"
// Create & return MyObject instance to Lua
static int myobject_new(lua_State* L){
double x = luaL_checknumber(L, 1);
*reinterpret_cast<MyObject**>(lua_newuserdata(L, sizeof(MyObject*))) = new MyObject(x);
luaL_setmetatable(L, LUA_MYOBJECT);
return 1;
}
// Free MyObject instance by Lua garbage collection
static int myobject_delete(lua_State* L){
delete *reinterpret_cast<MyObject**>(lua_touserdata(L, 1));
return 0;
}
// MyObject member functions in Lua
static int myobject_set(lua_State* L){
(*reinterpret_cast<MyObject**>(luaL_checkudata(L, 1, LUA_MYOBJECT)))->set(luaL_checknumber(L, 2));
return 0;
}
static int myobject_get(lua_State* L){
lua_pushnumber(L, (*reinterpret_cast<MyObject**>(luaL_checkudata(L, 1, LUA_MYOBJECT)))->get());
return 1;
}
// Register MyObject to Lua
static void register_myobject(lua_State* L){
lua_register(L, LUA_MYOBJECT, myobject_new);
luaL_newmetatable(L, LUA_MYOBJECT);
lua_pushcfunction(L, myobject_delete); lua_setfield(L, -2, "__gc");
lua_pushvalue(L, -1); lua_setfield(L, -2, "__index");
lua_pushcfunction(L, myobject_set); lua_setfield(L, -2, "set");
lua_pushcfunction(L, myobject_get); lua_setfield(L, -2, "get");
lua_pop(L, 1);
}
// Program entry
int main(int argc, char** argv){
if(argc > 1){
lua_State* L = luaL_newstate();
luaL_openlibs(L);
register_myobject(L);
if(luaL_dofile(L, argv[1]))
std::cerr << lua_tostring(L, -1);
lua_close(L);
}else
std::cerr << "Expected filename from command line!";
return 0;
}
local obj = MyObject(42)
print(obj:get()) -- 42
obj:set(-1.5)
print(obj:get()) -- -1.5
@myQwil
Copy link

myQwil commented Sep 19, 2022

instead of using int main(int argc, char** argv) use int luaopen_myobject(lua_State *L)
lua is going to look for a function with that name

@myQwil
Copy link

myQwil commented Sep 19, 2022

Also, you'll probably need this as well:
extern "C" { int luaopen_myobject(lua_State *L); }

@WillNilges
Copy link

Awesome! I got it!

Here's the code for anyone now or in the future:

First, you'll have to write your C++ file as it was at the top of this gist, but replace the main function with this:

extern "C" {
// Program entry
int luaopen_myobject(lua_State *L)
{
                luaL_openlibs(L);
                register_myobject(L);
        return 1;
}
}

You can compile using this Makefile

CC=g++
LUA_VERSION=5.4
output=myobject.so
build: *.cpp
        $(CC) $< -g -llua -fPIC -shared -o $(output)
clean:
        rm $(output)

When you run make, that'll produce a myobject.so.

Write a lua script like so. Call it test.lua.

myobject = require "myobject"

local obj = MyObject(42)

print(obj:get())    -- 42
obj:set(-1.5)
print(obj:get())    -- -1.5

Finally, run it with lua test.lua, and you should see the output

42.0
-1.5

Thanks @myQwil !!

@myQwil
Copy link

myQwil commented Sep 19, 2022

Glad I could help :)

I'd like to add just a few things:

  • luaL_openlibs(L); probably isn't necessary in this context
  • As far as I know, it's considered bad practice to use lua_register because it makes a global declaration. Instead, our luaopen function should deliver the library as a return value, which can then be made either local or global on the lua end. In our case, we can return a function like so:
static void register_myobject(lua_State* L) {
	static const luaL_Reg meta[] = {
		{ "__gc", myobject_delete },
		{ NULL, NULL }
	};
	static const luaL_Reg meth[] = {
		{ "set", myobject_set },
		{ "get", myobject_get },
		{ NULL, NULL }
	};
	luaL_newmetatable(L, LUA_MYOBJECT);
	luaL_setfuncs(L, meta, 0);
	luaL_newlib(L, meth);
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);

	lua_pushcfunction(L, myobject_new);
}

and then in test.lua:

local myObj = require "myobject"

local obj = myObj(42)
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment