lua require机制

转:https://blog.csdn.net/zxm342698145/article/details/80607072 

相信大家在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等待
-----------------

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值