Skip to content

Instantly share code, notes, and snippets.

@Youka
Last active December 4, 2023 18:41
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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
@efortier
Copy link

I was having problems getting my Lua stuff working, but thanks to you sharing your code, I finally got everything working!

Thank you so much!

@sszwfy
Copy link

sszwfy commented Dec 21, 2017

this is a good example! Thank you

@ro-kue
Copy link

ro-kue commented Aug 26, 2018

In case you want to construct the object with MyObject.new(...) simply replace the lua_register call with

lua_newtable(L);
lua_pushcfunction(L, myobject_new);
lua_setfield(l, -2, "new");
lua_setglobal(l, LUA_MYOBJECT);

test.lua would then look like this

localobj = MyObject.new(42)
print(obj:get())  -- 42
obj:set(-1.5)
print(obj:get())   -- -1.5

@myQwil
Copy link

myQwil commented Nov 28, 2020

You can get static functions to work if you replace lua_register with this:

lua_newtable(L);
lua_pushcfunction(L, myobject_staticFunc);
lua_setfield(L, -2, "staticFunc");

lua_newtable(L);
lua_pushcfunction(L, myobject_new);
lua_setfield(L, -2, "__call");

lua_setmetatable(L, -2);
lua_setglobal(L, LUA_MYOBJECT);

test.lua:

local obj = MyObject(42)
MyObject.staticFunc()

@k3ll3x
Copy link

k3ll3x commented Jul 23, 2021

Do anyone knows how to implement the __add method? my approach was this:

int add_vectors(lua_State* L){
    Class** a = check_vector(L);
    Class** b = check_vector(L, 2);

    if((*(*a)).size() != (*(*b)).size())
        return luaL_error(L, "Class sizes are not the same");

    Class** v = (Class**)lua_newuserdata(L, sizeof(Class*));
    *v = new Class((*(*a)) + (*(*b)));

    luaL_getmetatable(L, "uclass");
    lua_setmetatable(L, -2);

    return 1;
}

The problem with this approach is that created a not used class while calling print(x+x) and no value is associated with that operation (x is a type Class) Anyone has any notes on this?

@myQwil
Copy link

myQwil commented Jul 23, 2021

did you register the __add method?

lua_pushcfunction(L, add_vectors);
lua_setfield(L, -2, "__add");

@myQwil
Copy link

myQwil commented Jul 23, 2021

Also, you're trying to print a userdata from the looks of it, and that's not going to work unless you implement some kind of __tostring method.
Actually, nevermind, I guess that does work. it's concatenating userdata that doesn't work.

@k3ll3x
Copy link

k3ll3x commented Jul 23, 2021

did you register the __add method?

lua_pushcfunction(L, add_vectors);
lua_setfield(L, -2, "__add");

Also, you're trying to print a userdata from the looks of it, and that's not going to work unless you implement some kind of __tostring method.
Actually, nevermind, I guess that does work. it's concatenating userdata that doesn't work.

Yes I created and registered the functions __add and __tostring, my question was more focused on memory Management and the garbage collection. When I create an object using lua and its passed with an add expression to the print function, another instance of the object is created in my __add implementation, but later I don't use that instance, because it was created on the print method. (I implemented the __gc method for delete all the created instances) Here is an example I am talking about (the __gc method prints the freed instances)

x = vector.new(1,2,3,4)
print(x)
{ 1,2,3,4 }
print(x+x)
{ 2,4,6,8 }
quit
0x55ef8e44e840  vector freed -- x was created
0x55ef8e44e320  vector freed -- x+x result instance was created but no variable is associated with it, So I really don't need to keep that in memory
-- this works and its ok because a variable is associated with it
x = x + x
-- as mentioned before, this creates an instance of the object but it has no variable associated,
-- so I need a way to delete object instances with no variable associated
print(x + x)

You can look more on my implementation in my repo. LAak interpreter

Also another question, there is a way to create a default __index method (e.g: x[0]) with multiple __index methods? I implemented __index methods for my vector object such as x:size() and x:normalize() but I cannot add the method for indexing without overriding the previous ones e.g: x[0] was changed to x:k(0) because __index is not associated with normalize and size.

//index methods
inline static const luaL_Reg vector_methods[] = {
        // { "__index", get_vecelem }, //commented because overrides the next ones
        { "k", get_vecelem }, // so it have been changed to function x:k
        { "__newindex", set_vecelem },
        { "normalize", norm_vector },
        { "size", get_vecsize },
        { nullptr, nullptr }
    };

// register __index methods
void LA::register_methods(lua_State* L, luaL_Reg const* methods){
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);
    for(int i = 0; (methods+i)->name != nullptr; ++i){
        lua_pushcfunction(L, (methods+i)->func);
        lua_setfield(L, -2, (methods+i)->name);
    }
}

Thank you

@WillNilges
Copy link

WillNilges commented Sep 19, 2022

How am I supposed to compile this? Can anyone offer a g++ command?

I'm using g++ myobject.cpp -llua -shared -fPIC -o myobject.so, but that's giving me this error when I run lua main.lua

lua: error loading module 'myobject' from file './myobject.so':
        ./myobject.so: undefined symbol: luaopen_myobject
stack traceback:
        [C]: in ?
        [C]: in function 'require'
        main.lua:1: in main chunk
        [C]: in ?

I'm trying to use Lua 5.4 if that helps.

Edit:

Oh, I see. This doesn't do what I need it to. You're supposed to compile this as a binary like so: g++ myobject.cpp -llua -o myobject then execute it like so: ./myobject test.lua. The C++ executes, loads up the Lua, then operates on it. Meanwhile I'm trying to do it the other way around. create a C++ library to be called from Lua. Specifically, I need a way to mutate an object from C++ (called from Lua) and then read by Lua.

Don't suppose anyone has an example for that? 😅

@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