看了一些文档,对于 C++ 的类来说,其实是导入了全局环境内的,所以就可以直接用那个类名来进行调用方法。这其实巧妙的利用了一个特性:对于 C++ 的类对象方法,其都会接收一个 obj 本身的参数;而且方法是存储在代码区的。但是,一个 C++ 类到底是如何导出的呢?

再探 luaL_newState()

事实上,这个函数只是对 lua_newstate() 的包装而已,其以一个默认的内存分配函数 l_alloc 来调用 lua_newstate()

LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) lua_atpanic(L, &panic);
return L;
}

static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
(void)ud; (void)osize; /* not used */
if (nsize == 0) {
free(ptr);
return NULL;
}
else
return realloc(ptr, nsize);
}

其只是调用 f_alloc() 函数来分配了一个 sizeof(LG) 大小的内存区域。

f_alloc()

LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i;
lua_State *L;
global_State *g;
LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
if (l == NULL) return NULL;
L = &l->l.l;
g = &l->g;
L->next = NULL;
L->tt = LUA_TTHREAD;
g->currentwhite = bitmask(WHITE0BIT);
L->marked = luaC_white(g);
preinit_thread(L, g);
g->frealloc = f;
g->ud = ud;
g->mainthread = L;
g->gcrunning = 0; /* no GC while building state */
g->GCestimate = 0;
g->strt.size = g->strt.nuse = 0;
g->strt.hash = NULL;
setnilvalue(&g->l_registry);
g->panic = NULL;
g->version = NULL;
g->gcstate = GCSpause;
g->gckind = KGC_NORMAL;
g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL;
g->sweepgc = NULL;
g->gray = g->grayagain = NULL;
g->weak = g->ephemeron = g->allweak = NULL;
g->twups = NULL;
g->totalbytes = sizeof(LG);
g->GCdebt = 0;
g->gcfinnum = 0;
g->gcpause = LUAI_GCPAUSE;
g->gcstepmul = LUAI_GCMUL;
for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
/* memory allocation error: free partial state */
close_state(L);
L = NULL;
}
return L;
}

LG 是一个全局环境加上一个主线程(这里的线程和操作系统的线程并不一直,这里的线程其实就是一个 lua_State 而已):

/*
** thread state + extra space
*/
typedef struct LX {
lu_byte extra_[LUA_EXTRASPACE];
lua_State l;
} LX;


/*
** Main thread combines a thread state and the global state
*/
typedef struct LG {
LX l;
global_State g;
} LG;

接着,会对我们的 lua_State 进行预初始化。

preinit_thread()

在这个函数中,我们的 lua_State 持有了 global_State 的引用。

/*
** preinitialize a thread with consistent values without allocating
** any memory (to avoid errors)
*/
static void preinit_thread (lua_State *L, global_State *g) {
G(L) = g;
L->stack = NULL;
L->ci = NULL;
L->nci = 0;
L->stacksize = 0;
L->twups = L; /* thread has no upvalues */
L->errorJmp = NULL;
L->nCcalls = 0;
L->hook = NULL;
L->hookmask = 0;
L->basehookcount = 0;
L->allowhook = 1;
resethookcount(L);
L->openupval = NULL;
L->nny = 1;
L->status = LUA_OK;
L->errfunc = 0;
}

f_luaopen()

最后,对于我们的 lua_State 会调用 f_luaopen() 方法来进行初始化:

static void f_luaopen (lua_State *L, void *ud) {
global_State *g = G(L);
UNUSED(ud);
stack_init(L, L); /* init stack */
init_registry(L, g);
luaS_init(L);
luaT_init(L);
luaX_init(L);
g->gcrunning = 1; /* allow gc */
g->version = lua_version(NULL);
luai_userstateopen(L);
}

init_registry()

下面就是对注册表进行初始化了。

/*
** Create registry table and its predefined values
*/
static void init_registry (lua_State *L, global_State *g) {
TValue temp;
/* create registry */
Table *registry = luaH_new(L);
sethvalue(L, &g->l_registry, registry);
luaH_resize(L, registry, LUA_RIDX_LAST, 0);
/* registry[LUA_RIDX_MAINTHREAD] = L */
setthvalue(L, &temp, L); /* temp = L */
luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &temp);
/* registry[LUA_RIDX_GLOBALS] = table of globals */
sethvalue(L, &temp, luaH_new(L)); /* temp = new table (global table) */
luaH_setint(L, registry, LUA_RIDX_GLOBALS, &temp);
}

这段代码看得非常的吃力,完全不知道是在干什么东西。不能不说,sethvalue(L,obj,x) 这个宏非常的坑爹。

sethvalue(L, &g->l_registry, registry);

#define sethvalue(L,obj,x) \
{ TValue *io = (obj); Table *x_ = (x); \
val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \
checkliveness(L,io); }

这就不得不说 lua 的数据类型了,lua 的值都是没有类型的,但是会为值打上一个标签来表示其类型:

/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;


#define TValuefields Value value_; int tt_


typedef struct lua_TValue {
TValuefields;
} TValue;

但事实上还有一种对象,叫做 GCObject,这种对象代表底层的可回收对象。

/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;


#define TValuefields Value value_; int tt_


typedef struct lua_TValue {
TValuefields;
} TValue;

简单来说,

sethvalue(L, &g->l_registry, registry);

就是将一个 GCObject 对象 registry 赋给一个 Lua Object 对象 &g->l_registry,并设置它的类型为 LUA_TTABLE

总而言之,init_registry 这个函数就干了这么几件事:

  1. 全局注册表初始化为一个空表
  2. 全局注册表的索引 LUA_RIDX_MAINTHREAD 处设置为 lua_State 。
  3. 全局注册表的 LUA_RIDX_GLOBALS 索引设置为一个新的空表。

luaL_openlibs()

此函数会将一些库加载到 lua_State内。具体怎么操作的呢?


/*
** these libs are loaded by lua.c and are readily available to any Lua
** program
*/
static const luaL_Reg loadedlibs[] = {
{"_G", luaopen_base},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_COLIBNAME, luaopen_coroutine},
{LUA_TABLIBNAME, luaopen_table},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
{LUA_DBLIBNAME, luaopen_debug},
#if defined(LUA_COMPAT_BITLIB)
{LUA_BITLIBNAME, luaopen_bit32},
#endif
{NULL, NULL}
};


LUALIB_API void luaL_openlibs (lua_State *L) {
const luaL_Reg *lib;
/* "require" functions from 'loadedlibs' and set results to global table */
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1); /* remove lib */
}
}

可以看到对于每个我们需要加载的库,其都有一个名称,一个初始化函数,然后循环调用 luaL_requiref() 来进行加载。

luaL_requiref()

/*
** Stripped-down 'require': After checking "loaded" table, calls 'openf'
** to open a module, registers the result in 'package.loaded' table and,
** if 'glb' is true, also registers the result in the global table.
** Leaves resulting module on the top.
*/
LUALIB_API void luaL_requiref (lua_State *L, const char *modname,
lua_CFunction openf, int glb) {
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
lua_getfield(L, -1, modname); /* LOADED[modname] */
if (!lua_toboolean(L, -1)) { /* package not already loaded? */
lua_pop(L, 1); /* remove field */
lua_pushcfunction(L, openf);
lua_pushstring(L, modname); /* argument to open function */
lua_call(L, 1, 1); /* call 'openf' to open module */
lua_pushvalue(L, -1); /* make copy of module (call result) */
lua_setfield(L, -3, modname); /* LOADED[modname] = module */
}
lua_remove(L, -2); /* remove LOADED table */
if (glb) {
lua_pushvalue(L, -1); /* copy of module */
lua_setglobal(L, modname); /* _G[modname] = module */
}
}

这个函数从首先从注册表内检查,在已加载模块表内是否已经存在了此名称的数据,如果没有才会进行注册,同时,如果我们的 glb 参数还会将此模块,加载到全局注册表内去。

当我们使用 LUA_REGISTRYINDEX 或者 setglobal 这个函数的时候,实际上其最终获得的,还是我们的全局注册表,其效果是一致的:

在 Lua 的栈上,在 lapi.c 中,有一个函数 index2addr 会根据索引来返回对象的地址

static TValue *index2addr (lua_State *L, int idx) {
CallInfo *ci = L->ci;
if (idx > 0) {
TValue *o = ci->func + idx;
api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index");
if (o >= L->top) return NONVALIDVALUE;
else return o;
}
else if (!ispseudo(idx)) { /* negative index */
api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index");
return L->top + idx;
}
else if (idx == LUA_REGISTRYINDEX)
return &G(L)->l_registry;
else { /* upvalues */
idx = LUA_REGISTRYINDEX - idx;
api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large");
if (ttislcf(ci->func)) /* light C function? */
return NONVALIDVALUE; /* it has no upvalues */
else {
CClosure *func = clCvalue(ci->func);
return (idx <= func->nupvalues) ? &func->upvalue[idx-1] : NONVALIDVALUE;
}
}
}

可以看到当用的是 LUA_REGISTRYINDEX 的时候,返回的就是全局注册表的地址:

else if (idx == LUA_REGISTRYINDEX)
return &G(L)->l_registry;

lua_setglobal 也是直接返回的注册表地址:

LUA_API void lua_setglobal (lua_State *L, const char *name) {
Table *reg = hvalue(&G(L)->l_registry);
lua_lock(L); /* unlock done in 'auxsetstr' */
auxsetstr(L, luaH_getint(reg, LUA_RIDX_GLOBALS), name);
}

两v的效果是一致的,不过是细节上有所不同。

综上,对于加载一个模块,实际上都是首先从全局环境进行检查,然后如果没有加载到注册表,才会进行加载到 lua_State 的,如果我们在加载一个模块的时候没有设置加载到注册表,那么每个 lua_State 都会重新加载一份。

从 Lua 访问 C++ (LuaBridge)

我们来看一下,LuaBridge 是如何将 C++ 的数据导出给 Lua 使用的。

首先一个前提,任何想要给 Lua 使用的数据,都必须先注册。LuaBridge 可以注册五种数据类型:

  • Namespace 一个 Lua 表,包含了其他注册的内容
  • Data 全局或者静态变量,数据成员,静态数据成员
  • Functions 常规函数,成员函数,静态成员函数
  • CFunctions 一个常规函数,成员函数,静态成员函数,不过使用了 lua_CFunction 的调用约定
  • Properties 全局属性,属性成员,静态属性成员。这个看起来就像导出到 Lua 的数据,不过使用了 C++ 函数来 get/set 值。

Namespace

在这里说到的 Namespace 实际上与 C++ 里说的 namespace 不是一个东西,这里说的 Namespace 实际上是站在 Lua 的角度来说的,他实际上就是一个 Lua 表。不过呢,为了方便我们进行注册,所以将这个表进行了抽象成一个 Namespace。

我们可以通过下面的方法来获取 Lua 虚拟机内的全局 Namespace ,实际上就是 _G

getGlobalNamespace(L);

查看这个函数的具体代码,就能发现其表达的是什么了:

static Namespace getGlobalNamespace (lua_State* L)
{
lua_atpanic (L, throwAtPanic);
return Namespace (L);
}

explicit Namespace (lua_State* L)
: Registrar (L)
{
lua_getglobal (L, "_G");
++m_stackSize;
}

很明显,其所做的事情,就是将 _G 这个全局注册表压到 lua_State 的栈上。

我们可以在这个 Namespace 内添加新的 Namespace:

getGlobalNamespace (L)
.beginNamespace ("test");

Namespace beginNamespace (char const* name)
{
assertIsActive ();
return Namespace (name, *this);
}

Namespace (char const* name, Namespace& parent)
: Registrar (parent)
{
assert (lua_istable (L, -1)); // Stack: parent namespace (pns)

rawgetfield (L, -1, name); // Stack: pns, namespace (ns) | nil

if (lua_isnil (L, -1)) // Stack: pns, nil
{
lua_pop (L, 1); // Stack: pns

lua_newtable (L); // Stack: pns, ns
lua_pushvalue (L, -1); // Stack: pns, ns, ns

// na.__metatable = ns
lua_setmetatable (L, -2); // Stack: pns, ns

// ns.__index = indexMetaMethod
lua_pushcfunction (L, &CFunc::indexMetaMethod);
rawsetfield (L, -2, "__index"); // Stack: pns, ns

// ns.__newindex = newindexMetaMethod
lua_pushcfunction (L, &CFunc::newindexStaticMetaMethod);
rawsetfield (L, -2, "__newindex"); // Stack: pns, ns

lua_newtable (L); // Stack: pns, ns, propget table (pg)
lua_rawsetp (L, -2, getPropgetKey ()); // ns [propgetKey] = pg. Stack: pns, ns

lua_newtable (L); // Stack: pns, ns, propset table (ps)
lua_rawsetp (L, -2, getPropsetKey ()); // ns [propsetKey] = ps. Stack: pns, ns

// pns [name] = ns
lua_pushvalue (L, -1); // Stack: pns, ns, ns
rawsetfield (L, -3, name); // Stack: pns, ns
#if 0
lua_pushcfunction (L, &tostringMetaMethod);
rawsetfield (L, -2, "__tostring");
#endif
}

++m_stackSize;
}

以上代码的基本过程可以总结为下:

  1. 先将全局注册表 _G 压入栈。
  2. 查询一下是否已经存在了名为 name 的表存在。如果不存在,就新建一个表。
  3. 将新建立的表的元表设置为其自身。
  4. 设置新建立的表的 __index, __newindex 元方法。
  5. 设置新建的 Namespace 的属性获取表,和属性设置表
  6. _G.name 设置为新建的 Namespace 。

最终用 Lua 的结构来表示就是如下:

_G = {
"test" = {
"__index" = CFunc::indexMetaMethod,
"__newindex" = CFunc::newindexStaticMetaMethod,
"__gc" =
"propgetKey" = {},
"propsetKey" = {},
"__mt" = _G.test -- 元表就是其自身
}
}

类导出

我们可以调用 beginClass<T>() 来开始导出一个类:

  template <class T>
Class <T> beginClass (char const* name)
{
assertIsActive ();
return Class <T> (name, *this);
}

Class (char const* name, Namespace& parent)
: ClassBase (parent)
{
assert (lua_istable (L, -1)); // Stack: namespace table (ns)
rawgetfield (L, -1, name); // Stack: ns, static table (st) | nil

if (lua_isnil (L, -1)) // Stack: ns, nil
{
lua_pop (L, 1); // Stack: ns

createConstTable (name); // Stack: ns, const table (co)
lua_pushcfunction (L, &CFunc::gcMetaMethod <T>); // Stack: ns, co, function
rawsetfield (L, -2, "__gc"); // Stack: ns, co
++m_stackSize;

createClassTable (name); // Stack: ns, co, class table (cl)
lua_pushcfunction (L, &CFunc::gcMetaMethod <T>); // Stack: ns, co, cl, function
rawsetfield (L, -2, "__gc"); // Stack: ns, co, cl
++m_stackSize;

createStaticTable (name); // Stack: ns, co, cl, st
++m_stackSize;

// Map T back to its tables.
lua_pushvalue (L, -1); // Stack: ns, co, cl, st, st
lua_rawsetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getStaticKey ()); // Stack: ns, co, cl, st
lua_pushvalue (L, -2); // Stack: ns, co, cl, st, cl
lua_rawsetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getClassKey ()); // Stack: ns, co, cl, st
lua_pushvalue (L, -3); // Stack: ns, co, cl, st, co
lua_rawsetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getConstKey ()); // Stack: ns, co, cl, st
}
else
{
assert (lua_istable (L, -1)); // Stack: ns, st
++m_stackSize;

// Map T back from its stored tables

lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getConstKey ()); // Stack: ns, st, co
lua_insert (L, -2); // Stack: ns, co, st
++m_stackSize;

lua_rawgetp (L, LUA_REGISTRYINDEX, ClassInfo <T>::getClassKey ()); // Stack: ns, co, st, cl
lua_insert (L, -2); // Stack: ns, co, cl, st
++m_stackSize;
}
}

其本质也是类似的,只是细节上有所不同。

_G = {
"test" = {
"__index" = CFunc::indexMetaMethod,
"__newindex" = CFunc::newindexStaticMetaMethod,
"__gc" =
"propgetKey" = {},
"propsetKey" = {},
"__mt" = _G.test, -- 元表就是其自身
"clz" = {
"co" = {
"__mt" = _G.test.clz.co,
"cname" = "clz",
"__index" = CFunc::indexMetaMethod,
"__newindex" = CFunc::newindexObjectMetaMethod,
"__gc" = CFunc::gcMetaMethod <T>
},
"cl" = {},
"st" = {}
}
}
}

最终又会将,co,cl,st 都注册到全局注册表内去。将类的成员:const, 普通,static 分别存储到三个表内去。

类的构造函数

如果我们已经注册了一个类 clz,也就是说已经在注册表内有了一个名叫 clz 的 表 {}。那么我们如果用其构造一个对象呢?

getGlobalNamespace (L)
.beginNamespace ("test")
.beginClass <A> ("clz")
.addConstructor <void (*) (void)> ()
.endClass ()
.endNamespace ();

我们在 .addConstructor <void (*) (void)> () 时指定了构造函数的签名是 void (*) (void) 一个不带参数的构造函数。

template <class MemFn>
Class <T>& addConstructor ()
{
assertStackState (); // Stack: const table (co), class table (cl), static table (st)

lua_pushcclosure (L, &ctorPlacementProxy <typename FuncTraits <MemFn>::Params, T>, 0);
rawsetfield (L, -2, "__call");

return *this;
}

其本质上就是把 clz {} 的 __call 元方法设置为 ctorPlacementProxy <typename FuncTraits <MemFn>::Params, T>

__call 元方法的官方解释是:

__call: The call operation func(args). This event happens when Lua tries to call a non-function value (that is, func is not a function). The metamethod is looked up in func. If present, the metamethod is called with func as its first argument, followed by the arguments of the original call (args). All results of the call are the result of the operation. (This is the only metamethod that allows multiple results.)

__call:调用操作 func(args)。这个元方法会在 Lua 试图调用一个非函数的值时触发(意思就是说 func 并不是一个函数 )。这个元方法会在 func 内进行寻找,那么会将 func 作为第一个参数进行调用,后面加上原始的参数。

对于我们的例子 clz{}。当我们用

test.clz()

进行调用的时候,就会在 clz 内查找其 __call 元方法,接着就会调用我们的 ctorPlacementProxy <typename FuncTraits <MemFn>::Params, T> 函数。

template <class Params, class T>
static int ctorPlacementProxy (lua_State* L)
{
ArgList <Params, 2> args (L);
Constructor <T, Params>::call (UserdataValue <T>::place (L), args);
return 1;
}

其结果就是会在 Lua 的 userdata 内建立一个 clz 对象了。

Lua_CFunction

lua_CFunction 都会接收一个 lua_State 作为参数:

/**
Add or replace a member lua_CFunction.
*/
Class <T>& addCFunction (char const* name, int (T::*mfp) (lua_State*))
{
assertStackState (); // Stack: const table (co), class table (cl), static table (st)

typedef int (T::*MFP) (lua_State*);
new (lua_newuserdata (L, sizeof (mfp))) MFP (mfp); // Stack: co, cl, st, function ptr
lua_pushcclosure (L, &CFunc::CallMemberCFunction <T>::f, 1); // Stack: co, cl, st, function
rawsetfield (L, -3, name); // Stack: co, cl, st

return *this;
}

这里 MFP 可以理解为 member function pointer。new (lua_newuserdata (L, sizeof (mfp))) MFP (mfp);

会在 Lua 内创建一个 userdata 区域,然后将 lua_CFunction 的指针放在这里。接着 lua_pushcclosure (L, &CFunc::CallMemberCFunction <T>::f, 1); 将此函数指针作为上值传递过去。最后才是将此成员函数设置到 cl 表内。这样做的好处是什么?反正我是没有看懂。最终调用的时候,就会调用到我们自定义的成员函数了。

std::function

一个常规的成员函数。定义得有点奇怪:

template< class >
class function; /* undefined */
(since C++11)
template< class R, class... Args >
class function<R(Args...)>;

其实是一个模板类,是模板参数是 返回类型,及多个参数类型。

下面的调用的方法,是由一个返回类型,多个参数 Params 还有一个 T 参数的方法,在实际调用的时候,这个函数被将 T 传递为我们的 lua_State。

//--------------------------------------------------------------------------
/**
Add or replace a member function by std::function.
*/
template <class ReturnType, class... Params>
Class <T>& addFunction (char const* name, std::function <ReturnType (T*, Params...)> function)
{
assertStackState (); // Stack: const table (co), class table (cl), static table (st)

using FnType = decltype (function);
new (lua_newuserdata (L, sizeof (function))) FnType (std::move (function)); // Stack: co, cl, st, function userdata (ud)
lua_newtable (L); // Stack: co, cl, st, ud, ud metatable (mt)
lua_pushcfunction (L, &CFunc::gcMetaMethodAny <FnType>); // Stack: co, cl, st, ud, mt, gc function
rawsetfield (L, -2, "__gc"); // Stack: co, cl, st, ud, mt
lua_setmetatable (L, -2); // Stack: co, cl, st, ud
lua_pushcclosure (L, &CFunc::CallProxyFunctor <FnType>::f, 1); // Stack: co, cl, st, function
rawsetfield (L, -3, name); // Stack: co, cl, st

return *this;
}

这种形式就略有不同了,但都是将此函数放到了一个 userdata 区域内,并设置其 __gc 元方法。最终,通过 CFunc::CallProxyFunctor <FnType>::f进行调用。

template <class Functor>
struct CallProxyFunctor
{
using Params = typename FuncTraits <Functor>::Params;
using ReturnType = typename FuncTraits <Functor>::ReturnType;

static int f (lua_State* L)
{
assert (isfulluserdata (L, lua_upvalueindex (1)));
Functor& fn = *static_cast <Functor*> (lua_touserdata (L, lua_upvalueindex (1)));
return Invoke <ReturnType, Params, 1>::run (L, fn);
}
};

只不过这样操作了以后,会自动从栈上取参数,来调用函数,我们写成员函数的时候就不需要用 lua_State 的形式来编写了。

template <class ReturnType, class Params, int startParam>
struct Invoke
{
template <class Fn>
static int run (lua_State* L, Fn& fn)
{
try
{
ArgList <Params, startParam> args (L);
Stack <ReturnType>::push (L, FuncTraits <Fn>::call (fn, args));
return 1;
}
catch (const std::exception& e)
{
return luaL_error (L, e.what ());
}
}

对象传递

一个已注册的类的实例对象, T 可以以几种形式传递给 Lua:

  • T 值传递, Lua 管理生命周期
  • T const 值传递, Lua 管理生命周期。
  • T* 引用的形式传递, C++ 管理生命周期
  • T& 引用传递, C ++ 管理生命周期
  • T const * :const 引用传递, C++ 管理生命周期。
  • T const & :const 引用传递, C++ 管理生命周期。

C++ 生命周期

以 C++ 生命周期的对象,其建立和删除都是由 C++ 代码来控制的。 Lua 的垃圾回收器不会操纵这些对象,更确切的是,这些对象的析构器不会被 Lua 调用(因为其是 C++ 拥有的)。在 Lua 引用此对象时,C++ 确保不要将这些对象进行销毁。

请看例子:

A a;

push (L, &a); // pointer to 'a', C++ lifetime
lua_setglobal (L, "a");

push (L, (A const*)&a); // pointer to 'a const', C++ lifetime
lua_setglobal (L, "ac");

push <A const*> (L, &a); // equivalent to push (L, (A const*)&a)
lua_setglobal (L, "ac2");

push (L, new A); // compiles, but will leak memory
lua_setglobal (L, "ap");

Lua 生命周期

一个已注册的类的实例以值的形式传递给 Lua 的时候, Lua 会在 userdata 内建立一个值的副本,由 Lua 的垃圾管理器进行管理。

B b;

push (L, b); // Copy of b passed, Lua lifetime.
lua_setglobal (L, "b");
print (test.A.staticData)       -- Prints the static data member.
print (test.A.staticProperty) -- Prints the static property member.
test.A.staticFunc () -- Calls the static method.

print (a.data) -- Prints the data member.
print (a.prop) -- Prints the property member.
a:func1 () -- Calls A::func1 ().
test.A.func1 (a) -- Equivalent to a:func1 ().
test.A.func1 ("hello") -- Error: "hello" is not a class A.
a:virtualFunc () -- Calls A::virtualFunc ().

print (b.data) -- Prints B::dataMember.
print (b.prop) -- Prints inherited property member.
b:func1 () -- Calls B::func1 ().
b:func2 () -- Calls B::func2 ().
test.B.func2 (a) -- Error: a is not a class B.
test.A.func1 (b) -- Calls A::func1 ().
b:virtualFunc () -- Calls B::virtualFunc ().
test.B.virtualFunc (b) -- Calls B::virtualFunc ().
test.A.virtualFunc (b) -- Calls B::virtualFunc ().
test.B.virtualFunc (a) -- Error: a is not a class B.

a = nil; collectgarbage () -- 'a' still exists in C++.
b = nil; collectgarbage () -- Lua calls ~B() on the copy of b.

指针与引用

思索一下,一个 C++ 对象,其实就是一块内存区域,当其以值的时候传递的时,会将此内存区域内的内容复制一份到 Lua 内去;

以指针和引用则不然,其只会将此对象的地址值,复制一份到 Lua 内去,所以 Lua 是无法管理这些数据的。

调用 C++ 类对象的方法

当我们把一个 C++ 类的实例对象以 引用的形式(T*, T&) 传递给 Lua 的时候,我们又是如何能够调用到其成员方法的呢?

A a;

push (L, &a); // pointer to 'a', C++ lifetime
lua_setglobal (L, "a");

我们来看看 push() 函数:

template <class T>
static void push (lua_State* const L, T* const p)
{
if (p)
push (L, p, ClassInfo <T>::getClassKey ());
else
lua_pushnil (L);
}

static void push (lua_State* L, const void* p, void const* const key)
{
new (lua_newuserdata (L, sizeof (UserdataPtr))) UserdataPtr (const_cast <void*> (p));
lua_rawgetp (L, LUA_REGISTRYINDEX, key);
if (!lua_istable (L, -1))
{
throw std::logic_error ("The class is not registered in LuaBridge");
}
lua_setmetatable (L, -2);
}

实际上,在构造了一个 userdata 对象后,会利用我们类本身在 注册表内的键索引,然后将类在 Lua 的表,作为其元表,那么就能调用到成员方法了。这和文首所说的利用了一致。

可以为 C++ 的一个指针设置元表,这真是非常骚的操作,看一下 userdata 的结构:

typedef struct Udata {
CommonHeader;
lu_byte ttuv_; /* user value's tag */
struct Table *metatable;
size_t len; /* number of bytes */
union Value user_; /* user value */
} Udata;

其结构是确实是设置了一个元表的。