lua require机制
相信大家在lua中都用过require。为了达到代码复用和结构化的目的,各种语言都有require机制。lua的require看似简单,其实里面有很多玄机。一 require从哪里加载模块文件从虚拟机的path,cpath等全局变量中。虚拟机有默认的值,在变量package.path和package.cpath中。例如我打印的path,cpath分别为:path :/usr/loc...
相信大家在lua中都用过require。为了达到代码复用和结构化的目的,各种语言都有require机制。lua的require看似简单,其实里面有很多玄机。
一 require从哪里加载模块文件
从虚拟机的path,cpath等全局变量中。虚拟机有默认的值,在变量package.path和package.cpath中。例如我打印的path,cpath分别为:
path : /usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua
cpath : /usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so
当然我们也可以改变默认的值。
我们可以看出上述的path其实并不是真正意义上的路径,他只是寻找lua文件的路径模版。最终还是要从上述的path中通过变换路径找到lua文件,这个就涉及到查找策略了。
二 require加载模块文件的策略
如果模块文件曾经被加载过,我们不会傻傻的再加载一次模块。所以加载模块要遵循一定的规则,也就是策略。这个策略是:
1 首先查找package.loaded表,检测是否被加载过。如果被加载过,require返回保存的值,这个值在哪里被保存后面会讲到。否则进行下面的为模块寻找加载器。
2 加载器在package的searchers表中,共有四个加载器。require也按照顺序来执行加载器。找到了就成功返回,没有就继续查找。
a 预加载器,执行package.preload[modname],一些特殊模块会有预加载器。
b lua加载器查找前面介绍的package.path
c c加载器查找package.cpath
d 一体化加载器,这个在后面会有详细解释。
三 加载器如何加载
由于路径是一个包含有一系列以;分隔的模版构成的字符串,所以在加载器加载文件时,首先将每个路径模版用模块名字替换其中的?,然后尝试打开这个文件名。例如,如果路径字符串是
"./?.lua;./?.lc;/usr/local/?/init.lua"
require('foo'),将会依次尝试打开文件./foo.lua,./foo.lc,以及/usr/local/foo/init.lua。如果模块名包含.,例如require('foo.a')那么将会依次尝试打开文件./foo/a.lua,./foo/a.lc,以及/usr/local/foo/a/init.lua。
同理,如果这一步没有找到,则以同样的方式查找cpath。例如cpath是这个字符串
"./?.so;./?.dll;/usr/local/?/init.so"
查找器查找模块 foo 会依次尝试打开文件 ./foo.so,./foo.dll, 以及 /usr/local/foo/init.so。 一旦它找到一个 C 库, 查找器首先使用动态链接机制连接该库。然后尝试在该库中找到可以用作加载器的 C 函数。 这个 C 函数的名字必须是 "luaopen_" 紧接模块名的字符串,其中字符串中所有的下划线都会被替换成点。 此外,如果模块名中有横线, 横线后面的部分(包括横线)都被去掉。 例如,如果模块名为 a.b.c-v2.1, 函数名就是 luaopen_a_b_c。
也就是不仅提供的c模块名字要和require的模块名字一致,而且c模块导出的函数也要符合规范才行。如果有该模块而函数名对不上则会出现如下错误
lua loader error : error loading module 'xxx' from file './luaclib/xxx.so'。
最后一个搜索器是一体化加载器。本质上他是cpath加载器的延伸。他允许多个导出函数绑定在一个c库里。例如require('foo.a'),上述加载器都没有加载到,那么试图加载foo模块(过程和上面一样),然后再在foo里寻找luaopen_foo_a导出函数。
lua为我们提供了在指定path中搜索模块的函数,package.searchpath(),过程上述已讲。
四 加载成功后
如果require('mod')成功,则在package.loaded['mod']中记录该模块。如果该模块没有返回值则package.loaded['mod']==true,有返回值则记录的是其返回值。
值得注意的是,在同一个lua虚拟机中,多次require同一个模块,该模块返回一个table,那么任何地方修改该table的值都会引起其他地方table值的改变。例如如下代码:
--mode_a.lua
local skynet = {
name = 'shonm'
}
return skynet
-----------
--mode_b.lua
local skynet = require('mode_a')
skynet.age = 21
skynet.name = 'zxm'
return skynet
-----------
--test.lua
local skynet = require("mode_a")
require('mode_b')
print(skynet.name) --已经被改变为'zxm'
require的代码始终只会被执行一次,把上面的代码稍作改变:
--mode_a.lua
local skynet = {
name = 'shonm'
}
skynet.name = 'tcj'
return skynet
-----------
--mode_b.lua
local skynet = require('mode_a')
skynet.age = 21
skynet.name = 'zxm'
return skynet
-----------
--test.lua
require('mode_b')
local skynet = require("mode_a") --再次加载时,不会执行mode_a的代码,因为上面已经执行过,只从package.loaded['mode_a']中获得table的值
print(skynet.name) --仍然是'zxm'
注意,文中提到的package是lua暴露的一个全局模块,他是一个表,正如require,print一样,只不过他们是函数。类似模块还有os,math等待。
我自己写一个了蹩脚的lua解释器,能够执行大部分lua语法,代码在git上,赏颗星星
https://github.com/shonm520/mlua
欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。
参考:
http://www.runoob.com/manual/lua53doc/manual.html#pdf-package.searchpath
更多推荐
所有评论(0)