解析Lua中的全局环境、包、模块组织结构
模块就是一个程序库,而包是一系列模块。Lua中可以通过require来加载模块,然后得到一个全局变量表示一个table。Lua将其所有的全局变量保存在一个被称为“环境”的常规table中。本文首先介绍环境的一些实用技术,然后介绍如何引用模块及编写模块的基本方法。
1.环境
Lua将环境table保存在一个全局变量_G中,可以对其访问和设置。有时我们想操作一个全局变量,而它的名称却存储在另一个变量中,或者需要通过运行时的计算才能得到,可以通过value=_G[varname]来获得动态名字的全局变量。
关于“环境”的一大问题是它是全局的,任何对它的修改都会影响程序的所有部分。Lua5允许每个函数拥有一个子集的环境来查找全局变量,可以通过setfenv来改变一个函数的环境,第一个参数若是1则表示当前函数,2则表示调用当前函数的函数(依次类推),第二个参数是一个新的环境table。
a=1 setfenv(1,{}) print(a)--会报错,print是一个nil。这是因为一旦改变环境,所有的全局访问都会使用新的table
为了避免上述问题,可以使用setfenv(1,{_G=_G})将原来的环境保存起来,然后用_G.print来引用。另一种组装新环境的方法是使用继承,下面的代码新环境从源环境中继承了print和a,任何赋值都发生在新的table中。
a=1 localnewgt={} setmetatable(newgt,{__index=_G}) setfenv(1,newgt) print(a)
2.模块与包
2.1调用模块
要调用模块mod中的foo方法,可以用require函数来加载,如:
require"mod" mod.foo() --或者 localm=require"mod" m.foo()
require函数的行为:(关于require使用的路径查找策略不赘述)
在package.loaded这个table中检查模块是否已加载
=>已加载,就返回相应的值(可见一个模块只会加载一次)
=>未加载,就试着在package.preload中查询传入的模块名
===>找到一个函数,就以该函数作为模块的加载器
===>找不到,则尝试从Lua文件或C程序库中加载模块
=====>找到Lua文件,通过loadfile来加载文件
=====>找到C程序库,通过loadlib来加载文件
2.2使用环境
下面的代码说明了如何用环境来创建一个复数(complex)模块:
--模块设置 localmodname="complex" localM={} _G[modname]=M package.loaded[modname]=M --声明模块从外界所需的所有东西 local_G=_G--保留旧环境的引用,使用时需要像_G.print这样用 localio=io --运行这句之后环境就变了 setfenv(1,M) functionnew(r,i)return{r=r,i=i}end functionadd(c1,c2) returnnew(c1.r+c2.r,c1.i+c2.i) end
这样声明函数add时,就成为了complex.add,调用同一模块的其他函数也不需要加前缀。
2.3module函数
Lua5.1提供了一个新函数module,囊括了上面一系列定义环境的功能。在开始编写一个模块时,可以直接用module("modname",package.seeall)来取代前面的设置代码。在一个模块文件开头有这句调用后,后续所有代码都不需要限定模块名和外部名字,同样也不需要返回模块table了。
2.4子模块与包
Lua支持具有层级的模块名,用一个点来分隔名称中的层级。例如一个模块名为mod.sub,就是mod的一个子模块。一个包(package)就是一个完整的模块树,它是Lua中发型的单位。注意,当搜索一个子模块文件时,require会把点号当做目录分隔符来搜索,也就是说调用require"a.b"会尝试打开./a/b.lua,/usr/local/lua/a/b.lua,/usr/local/lua/a/b/init.lua。通过这种加载策略,可以将包的所有模块组织到一个目录中。
2.5以自定义方式加载lua模块
从Lua5.1以后,Lua有了标准的模块管理库。所以所有的模块加载都是通过require来完成。require的设计是颇具扩展性的,它会从若干个定义好的loader中逐个尝试加载新的模块。系统库中提供了四个loader,分别实现已加载模块,Lua模块,和C扩展模块(用了两个loader来实现C扩展模块的加载)。这些loader以CFunction的形式放在require的环境中的一个table里。
如果我们想改变lua模块的加载形式,只需要替换或增加一个新的loader就可以了。
要做的只需要模仿loadlib.c中的loader_Lua函数做一个自己的实现,比如在我们的项目中,就允许从自定义格式数据包中,加载一个被加密过的Lua代码文件。然后写几行C代码,获得require的环境(使用lua_getfenv),然后取出其中"loaders"这个table,把新的自定义loader插入到index2的地方。
具体的代码就不详述了,仔细阅读一下ll_require的实现(在loadlib.c中)就很容易明白。我们的整个工作从分析到实现没有超过两个小时,这真是得益于Lua良好的设计啊:D甚至如果你想从一个网络连接的数据流中加载Lua模块,或是通过http/ftp协议下载,也是行的通的吧。