Reputation: 171
I'm trying to instantiate and use object of Lua classes from C++. Lua class is defined like this.
myclass = {}
function myclass:new(o)
o=o or {}
setmetatable(o,self)
self.__index=self
return o
end
function myclass:init()
self.something = 0
end
function myclass:perform()
self.something = self.something + 0.5
return performsomething(self.something)
end
To instantiate object in C++, I do the following :
lua_getglobal(L,"myclass");
lua_getfield(L, -1, "new");
lua_pcall(L,0,1,0);
lua_newtable(L);
lua_setglobal(L, "objname");
Then to initialize :
lua_getglobal(L,"myclass");
lua_getfield(L, -1, "init");
lua_getglobal(L,"objname");
lua_pcall(L, 0, 0, 0);
And then to perform :
lua_getglobal(L, "myclass");
lua_getfield(L, -1, "perform");
lua_getglobal(L, "objname");
lua_pcall(L, 0, 1, 0);
double res = lua_tonumber(-1);
For this example, I didn't include the lua_pop()
methods I use when needed.
It appears that, with prints, I could get the following informations. Lua object is successfully instantiated with new method. It is also successfully initialized in init method. But, when calling perform method, self.something
member doesn't change, its value is stucked to 0, which seems to mean I'm not calling the object member method.
I'm pretty sure there is something wrong with the way I manage Lua stack to access object member function.
Did anyone already worked with a similar case and could help here ? Thanks Bests
Upvotes: 1
Views: 1780
Reputation: 484
In the following examples, I created a simple class in C++ that can be used in Lua using the Lua C API. Having completed it, I imagine much of it extends beyond the scope of your question; however, I hope my explanations and examples help.
When I am using Lua's C API to create userdata
, this is how I typically write my main
function in C++. In this case, MyClass.h
is the header file for the simple class I wrote for this example.
#include <iostream>
#include <stdexcept>
#include <cstdio>
#include <cstdlib>
#include "MyClass.h"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
int main(int argc, char** argv) {
// Check to make sure you have at least one .lua input file.
if (argc < 2) {
std::cerr << "Filename(s) expected from command line" << std::endl;
std::exit(EXIT_FAILURE);
}
// Try-catch block to handle any errors encountered when executing the Lua file(s).
try {
lua_State* L = luaL_newstate(L);
luaL_openlibs(L);
// Call a function to register Lua methods for `MyClass`; its declaration and definition are below.
lua_MyClass_register(L);
for (char** cpp = argv + 1; cpp < argv + argc; ++cpp) {
// Run the Lua script--provided as a command-line arg--and handle errors.
if (luaL_dofile(L, *cpp)) {
const char* err = lua_tostring(L, -1);
lua_close(L);
throw std::runtime_error(err);
}
}
lua_close(L);
} catch (const std::runtime_error &err) {
// Catch fatal errors from the Lua script and exit.
std::cerr << err.what() << std::endl;
std::exit(EXIT_FAILURE);
}
return 0;
}
Now for the file MyClass.h
, which contains the declarations for my simple example class:
#ifndef MYCLASS_H
#define MYCLASS_H
#define LUA_MYCLASS "MyClass"
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
// Class declaration.
class MyClass {
public:
MyClass(int something_in);
~MyClass();
// Setter function to change the value of `something`.
void set(int something_in);
// Getter function to return `something`.
const int & get() const;
private:
int something;
};
// The following declarations are for the C functions that correspond to `MyClass`'s member function.
int lua_MyClass_new(lua_State* L);
int lua_MyClass_delete(lua_State* L);
int lua_MyClass_get(lua_State* L);
int lua_MyClass_set(lua_State* L);
int lua_MyClass_newindex(lua_state* L);
int lua_MyClass_table_newindex(lua_State* L);
void lua_MyClass_register(lua_State* L);
// `luaL_Reg` is a struct provided in one of the Lua header files used to register userdata methods.
// This one is for the class methods.
static const luaL_Reg MyClass_methods[] = {
{"set", lua_MyClass_set},
{"get", lua_MyClass_get},
{nullptr, nullptr}
};
// This one is for the class metamethods.
static const luaL_Reg MyClass_metamethods[] = {
{"__gc", lua_MyClass_delete},
{"__newindex", lua_MyClass_newindex},
{nullptr, nullptr}
};
#endif
Here is MyClass.h
's corresponding .cpp
file:
#include "MyClass.h"
// These are the class member function definitions.
MyClass::MyClass(int something_in) {
something = something_in;
}
MyClass::~MyClass() {
}
void MyClass::set(int something_in) {
something = something_in;
return;
}
const int & MyClass::get() const {
return something;
}
// These are the definitions for the C functions that Lua will use to call the member functions.
// `MyClass` constructor, which corresponds to `MyClass.new` in Lua.
int lua_MyClass_new(lua_State* L) {
// Optional argument to supply to the `MyClass` constructor; using `luaL_optinteger`, it defaults to 0.
int something_in = static_cast<int>(luaL_optinteger(L, 1, 0));
MyClass** mcpp = reinterpret_cast<MyClass**>(lua_newuserdata(L, sizeof(MyClass**)));
*mcpp = new MyClass(something_in);
luaL_setmetatable(L, LUA_MYCLASS);
return 1;
}
// `MyClass` destructor, which corresponds to the `__gc` metamethod in Lua.
int lua_MyClass_delete(lua_State* L) {
MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
delete mcp;
return 0;
}
// C function corresponding to `MyClass::set`.
int lua_MyClass_set(lua_State* L) {
MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
int something_in = static_cast<int>(luaL_checkinteger(L, 2));
mcp->set(something_in);
return 0;
}
// C function corresponding to `MyClass::get`.
int lua_MyClass_get(lua_State* L) {
MyClass* mcp = *reinterpret_cast<MyClass**>(luaL_checkudata(L, 1, LUA_MYCLASS));
lua_pushinteger(L, mcp->get());
return 1;
}
// `__newindex` metamethod for `MyClass` userdata that prevents any members from being added.
int lua_MyClass_newindex(lua_State* L) {
return luaL_error(L, "attempt to modify a read-only object");
}
// `__newindex` metamethod for the `MyClass` table that prevents any methods from being added--I will explain more below.
int lua_MyClass_table_newindex(lua_State* L) {
return luaL_error(L, "attempt to modify a read-only table");
}
// Function to register all the above functions for use in Lua; this gets called in `main.cpp`.
void lua_MyClass_register(lua_State* L) {
// Create a global table that will contain all the `MyClass` methods as functions.
// Include `lua_MyClass_new` as a constructor in the form `MyClass.new`.
lua_newtable(L);
lua_pushcfunction(L, lua_MyClass_new);
lua_setfield(L, -2, "new");
// Include `MyClass::get` and `MyClass::set` in this table as well.
luaL_setfuncs(L, MyClass_methods, 0);
// Create a metatable for the global table `MyClass`--which was just created.
lua_newtable(L);
// Prevent access to the metatable.
lua_pushliteral(L, "metatable");
lua_setfield(L, -2, "__metatable");
lua_pushcfunction(L, lua_MyClass_table_newindex);
lua_setfield(L, -2, "__newindex");
// Set this second table as the metatable for the one created above.
lua_setmetatable(L, -2);
// Call the first table "MyClass" and add it to the global environment table (_ENV).
lua_setglobal(L, LUA_MYCLASS);
// Create a metatable to be used by `MyClass` objects--this is different from the above tables because it will not contain the `new` method.
luaL_newmetatable(L, LUA_MYCLASS);
// Same as before, lock the metatable.
lua_pushliteral(L, "metatable");
lua_setfield(L, -2, "__metatable");
// Add metamethods contained in the `luaL_Reg` struct `MyClass_metamethods`.
luaL_setfuncs(L, MyClass_metamethods, 0);
// Create an index--the `__index` metamethod--for the above table to use for `MyClass` objects.
lua_newtable(L);
// Add methods.
luaL_setfuncs(L, MyClass_methods, 0);
lua_setfield(L, -2, "__index");
// This pop operation is probably unnecessary since the Lua stack should be cleaned up when this function returns.
lua_pop(L, 1);
return;
}
Lastly, here is my input Lua file, test.lua
:
-- `MyClass` constructor with no arguments; `something` will be set to 0.
m1 = MyClass.new()
print(m1:get())
-- `MyClass` constructor with an argument; `something` will be set to 5.
m2 = MyClass.new(5)
print(m2:get())
-- Use the `set` method to change the value of `something` to 6.
m2:set(6)
print(m2:get())
This will output the following:
0
5
6
The definition of the function lua_MyClass_register
is quite complicated and involves the creation of many tables. I wrote it the way I did because I wanted to create a global table containing the MyClass
constructor and methods in the same form as, say, the global string
table in Lua. Take the function string.match
, for instance: it can be called as a function, as with string.match(str, pattern)
; or, it can be called as a method, as with str:match(pattern)
. The way I registered all the functions for MyClass
allows for this behavior, except that the global table MyClass
also contains a constructor, whereas objects of type MyClass
do not.
Upvotes: 0
Reputation: 5847
It's likely you didn't fix the nargs
argument to lua_pcall
function.
When the function is defined with colon syntax (class:func()
) you have to explicitly pass the self
argument from the C/C++ side. There's nothing like that in your example.
Minimally changing your code, it would look like this:
Lua side:
function performsomething(x)
print("type:", type(x))
print("value:", x)
return x
end
myclass = {}
function myclass:new(o)
o=o or {}
setmetatable(o,self)
self.__index=self
return o
end
function myclass:init()
self.something = 0
end
function myclass:perform()
self.something = self.something + 0.5
return performsomething(self.something)
end
C/C++ side:
#include <stdio.h>
#include <lualib.h>
#include <lauxlib.h>
int main()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaL_dofile(L, "myclass.lua");
// instantiate
lua_getglobal(L,"myclass");
lua_getfield(L, -1, "new");
lua_getglobal(L, "myclass");
lua_pcall(L,1,1,0);
lua_setglobal(L, "objname");
// init
lua_getglobal(L,"myclass");
lua_getfield(L, -1, "init");
lua_getglobal(L,"objname");
lua_pcall(L, 1, 0, 0);
// perform
lua_getglobal(L, "myclass");
lua_getfield(L, -1, "perform");
lua_getglobal(L, "objname");
lua_pcall(L, 1, 1, 0);
double res = lua_tonumber(L, -1);
printf("Result: %f\n", res);
lua_close(L);
return 0;
}
Upvotes: 0