本文主要参考了《lua程序设计》
为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来。现在看看lua的require的处理流程:例如require(modename)
一.首先lua会检查package.loaded表,如果package.loaded[modename]中有值,则用此值作为require的返回值,查找结束。因此,只要一个模块已经加载过,后续的require调用都将返回的时package.loaded[modename]中的值,不会再次加载它。因此不会有重复加载的问题。
二.若package.loaded表中找不到package.loaded[modename],则进入第二个方式查找,即尝试查找一个加载器(loader)。它又会经过以下步骤进行查找:
1.先查找package.preloaded[modname],如果有值,则此值为加载器loader,loader可以理解为一个函数,然后调用loader(modename),如果loader(modename)有值则作为require的返回值,查找结束,否则报错。
2.package.preloaded[modename]中没有值,则在package.path中存储的路径中查找lua文件,如果找到这么一个lua文件,它就通过loadfile来加载此文件。loadfiel只是加载了代码,并没有运行代码。为了运行代码,require会以模块名作为参数来调用这些代码,如果有返回值,require就将这个返回值存储到table package.loaded中,以作为下次require的返回值。如果没有返回值,require就会返回table package.loaded中的值。
3.package.path中仍然找不到,则在package.cpath中存储的路径查找一个C程序库,如果有这么一个程序库,则通过loadlib来加载。loadlib也只是加载了代码,并没有运行代码。为了运行代码,require会以模块名作为参数来调用这些代码,如果有返回值,require就将这个返回值存储到table package.loaded中,以作为下次require的返回值。如果没有返回值,require就会返回table package.loaded中的值。注:UNIX下是.so文件,windows下C动态库是.dll为后缀的文件,动态文件.dll生成不是本文要说的内容。但是要作为接口导出的C库文件里都要有个函数luaopen_modename文件名的函数,其中modename是模块名,也就是require里要传的那个参数,require会在链接完程序库后,尝试调用这个函数。
不过有个特殊情况。一般通过模块的名称来使用它们。但是有时必须将一根模块改名,以避免名称冲突。一种典型的情况是,在测试中需要加载同一模块的不同版本。对于一个lua模块来说,其内部名称是不固定的,可以轻易地编辑它以改变其名称。但是却无法编辑一个二进制模块中luaopen_*函数的名称。为了允许这种重命名,require用了一个小技巧:如果一个模块名中包含了连字符,也就是“-”,require就会用第一个连字符后的内容来创建luaopen_*函数名。例如,若一个模块名为a-b,require就认为他的open函数名为luaopen_b,而不是lua_a-b。因此,如果要使用的两个模块名都为mod,那么可以将其中一个重命名,比如重命名为v1-mod,当调用m1 = require"v1-mod"时,require会找到改名后的文件v1-mod,其中的open函数为luaopen_mod而不是luaopen_v1-mod。
三.如果还是找不到,返回错误。
-----------------------------------------------------------------------------------------------
&关于require文件的方式
在搜索一个文件时,require所使用的的路径与传统的路径有所不同。大部分程序所使用的路径就是一连串的目录,指定了某个文件的具体位置。然而,ANSIC却没有任何关于目录的概念。所以,require采用的路径是一连串的模式(pattern),其中每项都是一种将模块名转换为文件名的方式。进一步说,这种路径中的每项都是一个文件名,每项中还可以包含一个可选的问号。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件。如果不存在,就会尝试下一项。路径中的每项以分号隔开。例如,假设路径为:?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
那么,调用require"sql"就会试着打开以下文件:
sql
sqp.lua
c:\windows\sql
/use/local/lua/sql/sql.lua
require函数只处理了分号(作为各项之间的分隔符)和问号。其他例如目录分隔符或文件扩展名,都由路径自己定义。
------------------------------------------------------------------------------------------------------------
&保存文件的路径的文件
一.搜索lua文件的路径:require用于搜索lua文件的路径存放在变量package.path中。(1)当lua启动后,便以环境变量LUA_PATH的值来初始化这个变量。(2)如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
在使用LUA_PATH时,lua会将所有的子串“;;”替换成默认路径。例如,LUA_PATH为"mydir/?.lua;;",那么最终路径就是"mydir/?.lua" 后面加上默认路径。
二.搜索C文件的路径:如果require无法找到与模块名相符的lua文件,它就会找C程序库。这类搜索会从变量pacakge.cpath(相对于lua的package.path)来获取路径。而这个变量则是通过环境变量LUA_CPATH(相当于LUA_PATH)来初始化。在UNIX中,它的值一般是这样的:
./?.so;/usr/local/lib/lua/5.1/?.so
注意,文件的扩展名是由路径定义的(例如,上例中使用的.so)。而在wiindows中,此路径通常可以是这样的:
.\?.dll;C:\Program Files\Lua501\dll\?.dll
当找到一个C程序库后,require就会通过package.loadlib来加载它。C程序库与lua程序库是不同,它没有定义一个单元的主函数,而是导出了几个C函数。具有良好行为的C程序库应该导出一个名为"luaopen_<模块名>"的函数。require会在链接完成程序库后,尝试调用这个函数。