在以下示例中,我用 C++ 创建了一个简单的类,可以使用 Lua C API 在 Lua 中使用。完成后,我想其中大部分超出了您的问题范围;但是,我希望我的解释和示例有所帮助。
当我使用 Lua 的 C API 创建 userdata 时,这就是我通常使用 C++ 编写 main 函数的方式。在本例中,MyClass.h 是我为本示例编写的简单类的头文件。
#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;
}
现在是文件MyClass.h,它包含我的简单示例类的声明:
#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
这里是MyClass.h对应的.cpp文件:
#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;
}
最后,这是我输入的 Lua 文件,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())
这将输出以下内容:
0
5
6
函数lua_MyClass_register的定义比较复杂,涉及到很多表的创建。我按照我的方式编写它,因为我想创建一个包含 MyClass 构造函数和方法的全局表,其格式与 Lua 中的全局 string 表相同。以函数string.match为例:它可以作为函数调用,与string.match(str, pattern)一样;或者,它可以作为方法调用,如str:match(pattern)。我为MyClass 注册所有函数的方式允许这种行为,除了全局表MyClass 还包含一个构造函数,而MyClass 类型的对象没有。