Skip to content

Latest commit

 

History

History
186 lines (151 loc) · 9.19 KB

ch06-模块.md

File metadata and controls

186 lines (151 loc) · 9.19 KB

##模块 ###模块的加载 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表中有一个同名成员指向自己.

好了,前面两句的作用已经分析完毕.其结果有两个:

  1. _G = _G["_G"]
  2. _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函数的分析,得出的是以下几个结论:

  1. 创建模块时会创建一个表,该表挂载在registry["_LOADED"],_G[模块名]下,自然而然的,该模块中的变量(函数也是一种变量)就会挂载到这个表里面.
  2. 在module函数的参数中写下package.seeall将会创建该表的metatable,同时该表的__index将指向_G表.简单的说,这个模块将可以看到所有全局环境下的变量(再一次提醒,函数也是一种变量).

明白了module背后的作用,再来看看require函数.它对应的处理函数是loadlib.c中的ll_require函数,代码太多不贴在这里,简单的说做了如下几件事情:

  1. 首先在registry["_LOADED"]表中查找该库,如果已经存在则是加载过的模块,不再重复加载直接返回.
  2. 在当前环境表中查找”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),那么说明加载成功.

  1. 最后,尝试加载该模块.加载之前在Lua栈中压入一个哨兵值sentinel,如果加载完毕之后这个值没有被改动过,则说明加载完毕,将registry["_LOADED"]赋值为true表示加载成功.