-
-
Save Youka/2a6e69584672f7cb0331 to your computer and use it in GitHub Desktop.
// 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 |
this is a good example! Thank you
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
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()
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?
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.
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
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? 😅
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
Also, you'll probably need this as well:
extern "C" { int luaopen_myobject(lua_State *L); }
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 !!
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, ourluaopen
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)
...
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!