在学习Lua和C/C++过程中,在这个过程花费了不少时间。总是在几个地方卡住。Programming In Lua 和Lua 5.1 Reference Manual 为主要学习资料。
但是Lua 5.1 Reference Manual 在演化过程中,把对虚拟栈管理部分分散在不同的章节里面。Lua 5.0 Reference Manual 版本却有这一个章节。然后,后来在lua 源代码中找到了对虚拟栈管理东东。还有在Lua的不同版本中,对注册C函数也有不同的演化。比如Function luaL_openlib was replaced by luaL_register.
其实在Lua中注册C code函数来扩展lua,最简单,最直接的学习是从Lua 源代码本身。
本着简单原则,在Lua 源代码中 lmathlib.c 数学库,loslib.c 系统库,lbaselib.c 基本库最为简单。
1、在Lua 中注册C code函数必须遵循一定的准则。在Lua 5.1 Reference Manual 中typedef int (*lua_CFunction) (lua_State *L); 详细说明如下:
In order to communicate properly with Lua, a C function must use the following protocol, which defines the way parameters and results are passed:
a C function receives its arguments from Lua in its stack in direct order (the first argument is pushed first).
So, when the function starts, lua_gettop(L)
returns the number of arguments received by the function.
The first argument (if any) is at index 1 and its last argument is at index lua_gettop(L)
.
To return values to Lua, a C function just pushes them onto the stack, in direct order (the first result is pushed first), and returns the number of results.
Any other value in the stack below the results will be properly discarded by Lua.
Like a Lua function, a C function called by Lua can also return many results.
为了和Lua 进行互通,C函数必须遵循如下规则来接受Lua Code传递的参数和传递结果给Lua Code:
当一个Lua Code调用了已经注册的C code函数,C函数从虚拟栈中接受到参数。当在C code开始时,调用lua_gettop(L)将获取Lua Code传递参数的个说。
第一个被传递到C code函数的参数正向索引为1,栈顶索引为lua_gettop(L).
为了把结果返回到Lua Code中,在C函数中可以把结果值压入虚拟栈。同时C函数的返回值必须是一个整数。代表着返回值的个数。在返回值下面的数据都将被丢弃。
2、从最简单的lmathlib.c的源代码分析开始学习在Lua中定义C code函数。
2.1在lua.h中定义了函数指针:typedef int (*lua_CFunction) (lua_State *L); 所有在Lua中定义的函数形式必须和这个一样。 返回值int ,表示从C函数返回到Lua Code结果的数量。
2.2 真正注册C函数的关键Lua C API为void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n); 这里先提及以后详细介绍。其余注册函数最终都是包裹着这个函数。
2.3 对lmathlib.c部分代码进行分析
/*
** Open math library 注册数学库
*/
LUALIB_API int luaopen_math (lua_State *L)
{
//通过luaL_register 把libname和lib一次性注册到_G[math] Table中,同时把_G[math] Table压入到虚拟栈顶
luaL_register(L, LUA_MATHLIBNAME, mathlib);
//压入PI 数值到虚拟栈顶
lua_pushnumber(L, PI);
//设置_G[math][pi]=PI
lua_setfield(L, -2, "pi");
lua_pushnumber(L, HUGE_VAL);
lua_setfield(L, -2, "huge");
//在版本演化中,为了兼容低版本
#if defined(LUA_COMPAT_MOD)
lua_getfield(L, -1, "fmod");
lua_setfield(L, -2, "mod");
#endif
return 1;
}
static const luaL_Reg mathlib[] = {
{"abs", math_abs},
{NULL, NULL}
};
最后的NULL必须有,标记着指针数组的结束。
//取绝对值函数
//所有在Lua Code中注册的函数必须为lua_CFunction函数指针
//因为函数名称就是函数指针
static int math_abs (lua_State *L)
{
//当lua Code调用math.abs(-1)函数时,参数-1被压入栈顶
//利用ANSC C提供的标准函数库来对栈顶元素进行求绝对值
//然后利用lua_pushnumber()函数把结果压入虚拟栈
lua_pushnumber(L, fabs(luaL_checknumber(L, 1)));
//最后返回值标记着虚拟栈中数据的数量
return 1;
}
local a =math.abs(-1)
print("a="..a)
当math.abs(-1) Lua Code函数执行时,Lua虚拟栈就会调用math_abs( lua_State *L ) 函数,同时传递-1参数到虚拟栈顶部。
3 对Lua loslib.c 源代码部分进行分析,
//os.time ([table])
//Returns the current time when called without arguments, or a time representing the date and time specified by the given table.
static int os_time (lua_State *L)
{
time_t t;
//当os.time()函数使用无参数调用时,获取当前时间
if (lua_isnoneornil(L, 1)) /* called without args? */
t = time(NULL); /* get current time */
else
{
struct tm ts;
//否则获取虚拟栈上面table数据结构
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 1); /* make sure table is at the top */
//获取结构中的字段,如果获取不到,设置不同的默认值
ts.tm_sec = getfield(L, "sec", 0);
ts.tm_min = getfield(L, "min", 0);
ts.tm_hour = getfield(L, "hour", 12);
ts.tm_mday = getfield(L, "day", -1);
ts.tm_mon = getfield(L, "month", -1) - 1;
ts.tm_year = getfield(L, "year", -1) - 1900;
ts.tm_isdst = getboolfield(L, "isdst");
//转换成时间戳
t = mktime(&ts);
}
//判断是否越界,然后把结果值压入虚拟栈返回给Lua Code函数
if (t == (time_t)(-1))
lua_pushnil(L);
else
lua_pushnumber(L, (lua_Number)t);
return 1;
}