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.