更简单的 Cpp 绑定 |
|
这是来自 SimpleCppBinding 的示例,但这次使用的是 Lua 5.0 版本的 Luna [1]。
将您想要从 C++ 导出到 Lua 的方法放在 Account::methods 表中。当使用 obj:method(...) 语法时,userdata 的 *index* 事件将在方法表中查找方法。
方法表存储在名为 Account 的全局变量中,以便 Lua 脚本可以添加用 Lua 编写的函数。
新的 Account 对象可以在 Lua 脚本中使用 Account:new(...) 或 Account(...) 语法创建。后者是使用方法表的 *call* 事件实现的。由 Lua 脚本创建的任何 C++ 对象都将在 userdata 的 *gc* 事件中被删除。
userdata 元表通过将 __metatable 字段设置为方法表来隐藏在 Lua 脚本中。请注意 getmetatable 如何返回方法表,该方法表也存储在全局变量 Account 中。
方法表有一个元表,使继承更容易。为了让 Account 继承 parent 的方法,请像这样设置方法表的 *index* 事件:getmetatable(Account).__index = parent
一个 [UML 图] 可能有助于可视化表关系。引用组合链接表示元表关系。
如果您想从 C++ 代码调用 Lua 函数,请参阅 CallingLuaFromCpp,并参阅 CppBindingWithLunar 以了解 Luna 5 的改进版本。
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
#include "luna.h"
class Account {
lua_Number m_balance;
public:
static const char className[];
static Luna<Account>::RegType methods[];
Account(lua_State *L) { m_balance = luaL_checknumber(L, 1); }
int deposit (lua_State *L) { m_balance += luaL_checknumber(L, 1); return 0; }
int withdraw(lua_State *L) { m_balance -= luaL_checknumber(L, 1); return 0; }
int balance (lua_State *L) { lua_pushnumber(L, m_balance); return 1; }
~Account() { printf("deleted Account (%p)\n", this); }
};
const char Account::className[] = "Account";
#define method(class, name) {#name, &class::name}
Luna<Account>::RegType Account::methods[] = {
method(Account, deposit),
method(Account, withdraw),
method(Account, balance),
{0,0}
};
int main(int argc, char *argv[])
{
lua_State *L = lua_open();
luaopen_base(L);
luaopen_table(L);
luaopen_io(L);
luaopen_string(L);
luaopen_math(L);
luaopen_debug(L);
Luna<Account>::Register(L);
if(argc>1) lua_dofile(L, argv[1]);
lua_setgcthreshold(L, 0); // collected garbage
lua_close(L);
return 0;
}
extern "C" {
#include "lua.h"
#include "lauxlib.h"
}
template <typename T> class Luna {
typedef struct { T *pT; } userdataType;
public:
typedef int (T::*mfp)(lua_State *L);
typedef struct { const char *name; mfp mfunc; } RegType;
static void Register(lua_State *L) {
lua_newtable(L);
int methods = lua_gettop(L);
luaL_newmetatable(L, T::className);
int metatable = lua_gettop(L);
// store method table in globals so that
// scripts can add functions written in Lua.
lua_pushstring(L, T::className);
lua_pushvalue(L, methods);
lua_settable(L, LUA_GLOBALSINDEX);
lua_pushliteral(L, "__metatable");
lua_pushvalue(L, methods);
lua_settable(L, metatable); // hide metatable from Lua getmetatable()
lua_pushliteral(L, "__index");
lua_pushvalue(L, methods);
lua_settable(L, metatable);
lua_pushliteral(L, "__tostring");
lua_pushcfunction(L, tostring_T);
lua_settable(L, metatable);
lua_pushliteral(L, "__gc");
lua_pushcfunction(L, gc_T);
lua_settable(L, metatable);
lua_newtable(L); // mt for method table
int mt = lua_gettop(L);
lua_pushliteral(L, "__call");
lua_pushcfunction(L, new_T);
lua_pushliteral(L, "new");
lua_pushvalue(L, -2); // dup new_T function
lua_settable(L, methods); // add new_T to method table
lua_settable(L, mt); // mt.__call = new_T
lua_setmetatable(L, methods);
// fill method table with methods from class T
for (RegType *l = T::methods; l->name; l++) {
/* edited by Snaily: shouldn't it be const RegType *l ... ? */
lua_pushstring(L, l->name);
lua_pushlightuserdata(L, (void*)l);
lua_pushcclosure(L, thunk, 1);
lua_settable(L, methods);
}
lua_pop(L, 2); // drop metatable and method table
}
// get userdata from Lua stack and return pointer to T object
static T *check(lua_State *L, int narg) {
userdataType *ud =
static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
if(!ud) luaL_typerror(L, narg, T::className);
return ud->pT; // pointer to T object
}
private:
Luna(); // hide default constructor
// member function dispatcher
static int thunk(lua_State *L) {
// stack has userdata, followed by method args
T *obj = check(L, 1); // get 'self', or if you prefer, 'this'
lua_remove(L, 1); // remove self so member function args start at index 1
// get member function from upvalue
RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1)));
return (obj->*(l->mfunc))(L); // call member function
}
// create a new T object and
// push onto the Lua stack a userdata containing a pointer to T object
static int new_T(lua_State *L) {
lua_remove(L, 1); // use classname:new(), instead of classname.new()
T *obj = new T(L); // call constructor for T objects
userdataType *ud =
static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType)));
ud->pT = obj; // store pointer to object in userdata
luaL_getmetatable(L, T::className); // lookup metatable in Lua registry
lua_setmetatable(L, -2);
return 1; // userdata containing pointer to T object
}
// garbage collection metamethod
static int gc_T(lua_State *L) {
userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));
T *obj = ud->pT;
delete obj; // call destructor for T objects
return 0;
}
static int tostring_T (lua_State *L) {
char buff[32];
userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));
T *obj = ud->pT;
sprintf(buff, "%p", obj);
lua_pushfstring(L, "%s (%s)", T::className, buff);
return 1;
}
};
此代码可以为 Lua 5.0 编译如下
g++ -o test account.cc -L/usr/local/lib -llua -llualib
function printf(...) io.write(string.format(unpack(arg))) end function Account:show() printf("Account balance = $%0.02f\n", self:balance()) end a = Account(100) b = Account:new(30) print('a =', a) print('b =', b) print('metatable =', getmetatable(a)) print('Account =', Account) table.foreach(Account, print) a:show() a:deposit(50.30) a:show() a:withdraw(25.10) a:show() parent = {} function parent:rob(amount) amount = amount or self:balance() self:withdraw(amount) return amount end getmetatable(Account).__index = parent debug.debug()
$ ./test account.lua a = Account (0xa041d98) b = Account (0xa045390) metatable = table: 0xa044f28 Account = table: 0xa044f28 show function: 0xa046760 balance function: 0xa0455f8 withdraw function: 0xa045300 deposit function: 0xa045508 new function: 0xa044fe8 Account balance = $100.00 Account balance = $150.30 Account balance = $125.20 lua_debug> a:show() Account balance = $125.20 lua_debug> b:show() Account balance = $30.00 lua_debug> print(a:rob(20)) 20 lua_debug> a:show() Account balance = $105.20 lua_debug> b:deposit(a:rob()) lua_debug> a:show() Account balance = $0.00 lua_debug> b:show() Account balance = $135.20 lua_debug> cont deleted Account (0xa045390) deleted Account (0xa041d98)
如果您想将对象传递给另一个 C++ 函数,可以使用 check 函数来验证 userdata 是否为正确的类型,并获取指向该对象的指针。
在本例中,C 函数 rob 从 Account 中扣除 20 美元。
extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
#include "luna.h"
class Account {
lua_Number m_balance;
public:
Account(double balance=0) : m_balance(balance) { }
void deposit(double amount) { m_balance += amount; }
void withdraw(double amount) { m_balance -= amount; }
double balance(void) { return m_balance; }
~Account() { printf("deleted Account (%p)\n", this); }
// Lua interface
Account(lua_State *L) : m_balance(luaL_checknumber(L, 1)) { }
int deposit (lua_State *L) { deposit (luaL_checknumber(L, 1)); return 0; }
int withdraw(lua_State *L) { withdraw(luaL_checknumber(L, 1)); return 0; }
int balance (lua_State *L) { lua_pushnumber(L, balance()); return 1; }
static const char className[];
static Luna<Account>::RegType methods[];
};
const char Account::className[] = "Account";
#define method(class, name) {#name, &class::name}
Luna<Account>::RegType Account::methods[] = {
method(Account, deposit),
method(Account, withdraw),
method(Account, balance),
{0,0}
};
static int report (lua_State *L, int status)
{
if (status) {
const char *msg = lua_tostring(L, -1);
if (msg == NULL) msg = "(error with no message)";
fprintf(stderr, "ERROR: %s\n", msg);
lua_pop(L, 1);
}
return status;
}
static int application (lua_State *L)
{
lua_settop(L, 0);
lua_pushliteral(L, "_TRACEBACK");
lua_rawget(L, LUA_GLOBALSINDEX); // get traceback function
int tb = lua_gettop(L);
lua_pushliteral(L, "main");
lua_gettable(L, LUA_GLOBALSINDEX);
report(L, lua_pcall(L, 0, 1, tb));
Account *a = Luna<Account>::check(L, -1);
printf("the balance of 'a' is $%.2lf\n", a->balance());
return 0;
}
static int rob (lua_State *L)
{
Account *b = Luna<Account>::check(L, 1);
b->withdraw(20.00);
printf("take $20.00 from 'b'. the balance of 'b' is $%.2lf\n", b->balance());
return 1;
}
int main (int argc, char *argv[])
{
lua_State *L = lua_open();
luaopen_base(L);
luaopen_table(L);
luaopen_io(L);
luaopen_string(L);
luaopen_math(L);
luaopen_debug(L);
Luna<Account>::Register(L);
lua_register(L, "rob", rob);
if (argc>1) {
printf("loading '%s'\n", argv[1]);
if (report(L, luaL_loadfile(L, argv[1]) || lua_pcall(L, 0, 0, 0)) == 0) {
printf("running application\n");
if (report(L, lua_cpcall(L, &application, 0)) == 0) {
printf("okay\n");
}
}
}
lua_setgcthreshold(L, 0); // collected garbage
lua_close(L);
return 0;
}
function printf(...) io.write(string.format(unpack(arg))) end function Account:show() printf("Account balance = $%0.02f\n", self:balance()) end b = Account(30) print('b =', b) b:show() rob(b) b:show() function main() a = Account(100) print('a =', a) a:show() a:deposit(50.30) a:show() return a end
$ ./test account.lua loading 'account4.lua' b = Account (0xa041d98) Account balance = $30.00 take $20.00 from 'b'. the balance of 'b' is $10.00 Account balance = $10.00 running application a = Account (0xa045390) Account balance = $100.00 Account balance = $150.30 the balance of 'a' is $150.30 okay deleted Account (0xa045390) deleted Account (0xa041d98)