簡化 Cpp 綁定

lua-users home
wiki

描述

以下再次引用 SimpleCppBinding 的範例,但這次會使用 LTN 5.0 版的 Luna [1]

將您要從 C++ 匯出至 Lua 的方法放入 Account::methods 表格中。使用者資料 index 事件會在使用 obj:method(...) 語法時於方法表格中尋找方法。

方法表格會儲存在名為 Account 的全域變數,這樣 Lua 程式碼才能新增用 Lua 編寫的方法。

可以在 Lua 程式碼中用 Account:new(...)Account(...) 語法建立新的 Account 物件。後者是使用 call 事件於方法表格中執行的。Lua 程式碼所建立的任何 C++ 物件都會被使用者資料 gc 事件刪除。

使用者資料 metatable 已透過將 __metatable 欄位設為方法表格隱藏在 Lua 程式碼之外。注意 getmetatable 會傳回方法表格,該表格也儲存在全域變數 Account 之中。

方法表格有一個 metatable,目的在簡化繼承。若要讓 Account 繼承 parent 的方法,請像這樣設定方法表格的 index 事件:getmetatable(Account).__index = parent

[UML 圖形] 可以幫助視覺化表格關係。參考組合連結表示 metatable 關係。

如果您要從 C++ 程式碼呼叫 Lua 函式,請參閱 CallingLuaFromCpp;如果您要使用 Luna 5 的改良版本,請參閱 CppBindingWithLunar

account.cc C++ 程式碼

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;
}

適用於 Lua 5.0 的 luna.h

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

Lua 測試程式碼

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 函式驗證使用者資料為正確類型,並取得物件的指標。

在這個範例中,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;
}

Lua 測試程式碼

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)

另請參閱


RecentChanges · preferences
edit · history
最後編輯時間 2022 年 5 月 5 日 上午 11:18 GMT (diff)