##模块 ###模块的加载 Lua内部所有模块的注册都在linit.c的函数luaL_openlibs中提供.可以看到的是,它依次访问一个数组lualibs中的一个成员,每个成员中定义了每个模块的模块名及相应的模块注册函数,依次调用函数就完成了模块的注册:
(linit.c)
17 static const luaL_Reg lualibs[] = {
18 {"", luaopen_base},
19 {LUA_LOADLIBNAME, luaopen_package},
20 {LUA_TABLIBNAME, luaopen_table},
21 {LUA_IOLIBNAME, luaopen_io},
22 {LUA_OSLIBNAME, luaopen_os},
23 {LUA_STRLIBNAME, luaopen_string},
24 {LUA_MATHLIBNAME, luaopen_math},
25 {LUA_DBLIBNAME, luaopen_debug},
26 {NULL, NULL}
27 };
28
29
30 LUALIB_API void luaL_openlibs (lua_State *L) {
31 const luaL_Reg *lib = lualibs;
32 for (; lib->func; lib++) {
33 lua_pushcfunction(L, lib->func);
34 lua_pushstring(L, lib->name);
35 lua_call(L, 1, 0);
36 }
37 }
结构体luaL_Reg有两个变量,分别是模块名以及模块初始化函数.可以看到第一个模块是一个空字符串,因此访问这个模块的函数不需要"模块名."前缀,比如我们熟悉的print函数,这里就以base模块为例来讲解模块的注册过程.
加载base模块最终会调用base_open函数,我们看这个函数里面最核心的几行代码:
(lbaselib.c)
626 static void base_open (lua_State *L) {
627 /* set global _G */
628 lua_pushvalue(L, LUA_GLOBALSINDEX);
629 lua_setglobal(L, "_G");
630 /* open lib into global table */
631 luaL_register(L, "_G", base_funcs);
// ...
645 }
这个函数中最开始的这两句首先将LUA_GLOBALSINDEX对应的值压入栈中,其次调用”lua_setglobal(L, “_G”);”,这句代码的意思是在Lua_state的l_gt表中,当查找”_G”时,查找到的是索引值为LUA_GLOBALSINDEX的表.如果觉得有点绕,可以简单这个理解,在Lua中的G表,也就是全局表,满足这个等式”_G = _G["_G"]“,也就是这个叫”_G”的表,内部有一个key为”_G”的表是指向自己的.怀疑这个结论的,可以在Lua命令行中执行print(_G)和print(_G["_G"])看看输出结果是不是一致的.
我想,Lua中要这么处理的理由是:为了让G表和处理其它表使用同样的机制.查找一个变量时,最终会一直查到G表中,这是很自然的事情;所以为了也能按照这个机制顺利的查找到自己,于是在G表中有一个同名成员指向自己.
好了,前面两句的作用已经分析完毕.其结果有两个:
- _G = _G["_G"]
- _G表的值压入函数栈中方便了下面的调用.
所以,这个G表的注册操作需要在所有模块注册之前首先进行.
继续看下面的语句:
luaL_register(L, “_G”, base_funcs);
base_funcs也是一个luaL_Reg数组,上面的操作会将base_funcs数组中的函数注册到G表中,但是里面还有些细节需要看看的,这个操作最终会调用函数luaI_openlib:
(lauxlib.c)
242 LUALIB_API void luaI_openlib (lua_State *L, const char *libname,
243 const luaL_Reg *l, int nup) {
244 if (libname) {
245 int size = libsize(l);
246 /* check whether lib already exists */
247 luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
248 lua_getfield(L, -1, libname); /* get _LOADED[libname] */
249 if (!lua_istable(L, -1)) { /* not found? */
250 lua_pop(L, 1); /* remove previous result */
251 /* try global variable (and create one if it does not exist) */
252 if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
253 luaL_error(L, "name conflict for module " LUA_QS, libname);
254 lua_pushvalue(L, -1);
255 lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */
256 }
257 lua_remove(L, -2); /* remove _LOADED table */
258 lua_insert(L, -(nup+1)); /* move library table to below upvalues */
259 }
260 for (; l->name; l++) {
261 int i;
262 for (i=0; i<nup; i++) /* copy upvalues to the top */
263 lua_pushvalue(L, -nup);
264 lua_pushcclosure(L, l->func, nup);
265 lua_setfield(L, -(nup+2), l->name);
266 }
267 lua_pop(L, nup); /* remove upvalues */
268 }
注册这些函数之前,首先会到registry["_LOADED"]表中查找该库,如果不存在则再在G表中查找这个库,不存在则创建一个表.因此,不管是Lua中内部的库或者是外部使用require引用的库,首先会到registry["_LOADED"]中存放该库的表.最后,再遍历传进来的函数指针数组,完成库函数的注册.
比如,注册os.print时,首先将print函数绑定在一个函数指针上,再去l_registry["_LOADED"]和G表中查询该名为”os”的库是否存在,不存在则创建一个表,即:
G["os"] = {}
紧跟着注册print函数,即: G["os"]["print"] = 待注册的函数指针.这样,在调用Lua代码os.print(1)时,首先根据”os”到G表中查找对应的表,再在这个表中查找”print”成员得到函数指针,最后完成函数的调用.
###模块的编写 来看Lua中与模块相关的几个函数.
在定义一个Lua模块时,第一句代码一般都是"module(xxx)".来看这一句背后的含义.module调用对应的c函数是loadlib.c中的函数ll_module:
(loadlib.c)
544 static int ll_module (lua_State *L) {
545 const char *modname = luaL_checkstring(L, 1);
546 int loaded = lua_gettop(L) + 1; /* index of _LOADED table */
547 lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
548 lua_getfield(L, loaded, modname); /* get _LOADED[modname] */
549 if (!lua_istable(L, -1)) { /* not found? */
550 lua_pop(L, 1); /* remove previous result */
551 /* try global variable (and create one if it does not exist) */
552 if (luaL_findtable(L, LUA_GLOBALSINDEX, modname, 1) != NULL)
553 return luaL_error(L, "name conflict for module " LUA_QS, modname);
554 lua_pushvalue(L, -1);
555 lua_setfield(L, loaded, modname); /* _LOADED[modname] = new table */
556 }
557 /* check whether table already has a _NAME field */
558 lua_getfield(L, -1, "_NAME");
559 if (!lua_isnil(L, -1)) /* is table an initialized module? */
560 lua_pop(L, 1);
561 else { /* no; initialize it */
562 lua_pop(L, 1);
563 modinit(L, modname);
564 }
565 lua_pushvalue(L, -1);
566 setfenv(L);
567 dooptions(L, loaded - 1);
568 return 0;
569 }
代码的前半部分,首先根据module(XXX)中的模块名去registry["_LOADED"]表中查找,如果找不到则创建出一个新表,这个表由_G["XXX"] = registry["_LOADED"]["XXX"].
紧跟着,在modinit函数中,将这个表的成员_M,_NAME,_PACKAGE成员分别赋值. 最后,调用setfenv将该模块对应的环境置空.但是这里得回忆一下,”模块对应的环境”实际上是什么?Lua解释器在分析一个Lua文件时,最后得出的结果都会存在一个Closure中(见前面对f_parser函数的分析),可见一个结论:一个Lua文件,分析完毕之后对Lua而言其实就是一个Closure,也就是函数.所以,回到前面的问题,setfenv将该模块对应的环境置空就是将这个模块分析完毕之后返回的Closure对应的env环境表置空.
而如果写下的是"module(xxx,package.seeall)"呢?它将会调用后面的dooptions函数并且最后调用到package.seeall对应的处理函数:
(loadlib.c)
572 static int ll_seeall (lua_State *L) {
573 luaL_checktype(L, 1, LUA_TTABLE);
574 if (!lua_getmetatable(L, 1)) {
575 lua_createtable(L, 0, 1); /* create new metatable */
576 lua_pushvalue(L, -1);
577 lua_setmetatable(L, 1);
578 }
579 lua_pushvalue(L, LUA_GLOBALSINDEX);
580 lua_setfield(L, -2, "__index"); /* mt.__index = _G */
581 return 0;
582 }
上面这段函数就两个作用,一个创建该模块对应表的metatable,另外一个将meta表的__index指向_G表,也就是说,所有在该模块找不到的变量,都会去_G表中查找.
于是,前面对module函数的分析,得出的是以下几个结论:
- 创建模块时会创建一个表,该表挂载在registry["_LOADED"],_G[模块名]下,自然而然的,该模块中的变量(函数也是一种变量)就会挂载到这个表里面.
- 在module函数的参数中写下package.seeall将会创建该表的metatable,同时该表的__index将指向_G表.简单的说,这个模块将可以看到所有全局环境下的变量(再一次提醒,函数也是一种变量).
明白了module背后的作用,再来看看require函数.它对应的处理函数是loadlib.c中的ll_require函数,代码太多不贴在这里,简单的说做了如下几件事情:
- 首先在registry["_LOADED"]表中查找该库,如果已经存在则是加载过的模块,不再重复加载直接返回.
- 在当前环境表中查找”loaders”变量,这里存放的是所有加载器组成的数组,在Lua代码中有四个loader:
(loadlib.c)
623 static const lua_CFunction loaders[] =
624 {loader_preload, loader_Lua, loader_C, loader_Croot, NULL};
变量里面的loader,分别使用它们对模块进行加载.如果加载的结果,在Lua栈中返回的是一个函数(前面已经提过分析完一个Lua源代码文件返回的是一个Closure),那么说明加载成功.
- 最后,尝试加载该模块.加载之前在Lua栈中压入一个哨兵值sentinel,如果加载完毕之后这个值没有被改动过,则说明加载完毕,将registry["_LOADED"]赋值为true表示加载成功.