环境:
Lua5.1 LuaForWindows
LuaForWindows的下载地址
http://files.luaforge.net/releases/luaforwindows/luaforwindows
正题:
require作用类似于C/C++中的#include,其特性为:
1. 根据搜索目录加载指定模块
2. 判定模块是否已加载,避免重复加载
其实现原理:
对于require加载的模块数据是存储在package.loaded表中,其存储方式以模块名为key,以返回值(模块若无返回值,默认为true)为value进行存储的。比如运行如下程序:
-- package.loaded的类型
print(type(package.loaded)) -- table
-- 没有require模块文件
for i, v in pairs(package.loaded) do
print(i,v)
end
--[[
>>>输出
string table: 006EDE40
debug table: 006EDF08
package table: 006EDAF8
_G table: 00501C88
io table: 006EDC88
os table: 006EDDC8
table table: 006EDB98
math table: 006EDEB8
]]
然后我们任意加载一个Demo.lua,代码如下:
-- requireDemo.lua
-- 内部实现代码:
local requireDemo = {}
return true
require("requireDemo")
for i, v in pairs(package.loaded) do
print(i,v)
end
--[[
输出:
string table: 002DDE40
debug table: 002DDF08
package table: 002DDAF8
_G table: 008C1C88
io table: 002DDC88
os table: 002DDDC8
table table: 002DDB98
math table: 002DDEB8
coroutine table: 002DDA58
requireDemo true -- 新加的,已经加入到表中了
]]
经过如上代码对比,我们可以这样理解,通过require加载某模块的时候,首先通过package.loaded来判定是否已加载。如果已加载,则返回该模块数据,如果没有加载,进行搜索加载,如果加载成功,返回该模块数据,否则报错。比如:
-- 加载不存在的模块
require("ErrorModel")
--[[
错误堆栈信息:
lua: require.lua:21: module 'ErrorModel' not found:
no field package.preload['ErrorModel']
no file '.\ErrorModel.lua'
no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel.lua'
no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel\init.lua'
no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel.lua'
no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel\init.lua'
no file 'E:\Program Files (x86)\Lua\5.1\lua\ErrorModel.luac'
no file '.\ErrorModel.dll'
no file '.\ErrorModel51.dll'
no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel.dll'
no file 'E:\Program Files (x86)\Lua\5.1\ErrorModel51.dll'
no file 'E:\Program Files (x86)\Lua\5.1\clibs\ErrorModel.dll'
no file 'E:\Program Files (x86)\Lua\5.1\clibs\ErrorModel51.dll'
no file 'E:\Program Files (x86)\Lua\5.1\loadall.dll'
no file 'E:\Program Files (x86)\Lua\5.1\clibs\loadall.dll'
stack traceback:
[C]: in function 'require'
require.lua:21: in main chunk
[C]: ?
]]
对比着错误的堆栈信息,我们可以得到这样的信息:根据指定的搜索路径,查找了Lua和C中的相关文件。我们来详细的说明下:
在程序中,lua是通过LUA_PATH进行初始化,C是通过LUA_CPATH进行初始化的
// 在luaconf.h中 // Environment variable names for path overrides and initialization code // 用于初始化和覆盖环境变量名 #define LUA_PATH "LUA_PATH" #define LUA_CPATH "LUA_CPATH"
如果LUA_PATH,LUA_CPATH没有相关的变量,会通过LUA_PATH_DEFAULT,LUA_CPATH_DEFAULT来进行默认初始化,代码如下:
// luaconf.h中
/*
** In Windows, any exclamation mark ('!') in the path is replaced by the
** path of the directory of the executable file of the current process.
*/
#define LUA_LDIR "!\\lua\\"
#define LUA_CDIR "!\\"
#define LUA_PATH_DEFAULT \
".\\?.lua;" LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;"
#define LUA_CPATH_DEFAULT \
".\\?.dll;" LUA_CDIR"?.dll;" LUA_CDIR"loadall.dll"
#else
/*
** Note to distribution maintainers: do NOT patch the following lines!
** Please read ../doc/install.html#distro and pass PREFIX=/usr instead.
*/
#ifndef LUA_MULTILIB
#define LUA_MULTILIB "lib"
#endif
#ifndef LUA_LMULTILIB
#define LUA_LMULTILIB "lib"
#endif
#define LUA_LROOT "/usr/local"
#define LUA_LUADIR "/lua/5.1/"
#define LUA_LJDIR "/luajit-2.1.0-beta2/"
#ifdef LUA_ROOT
#define LUA_JROOT LUA_ROOT
#define LUA_RLDIR LUA_ROOT "/share" LUA_LUADIR
#define LUA_RCDIR LUA_ROOT "/" LUA_MULTILIB LUA_LUADIR
#define LUA_RLPATH ";" LUA_RLDIR "?.lua;" LUA_RLDIR "?/init.lua"
#define LUA_RCPATH ";" LUA_RCDIR "?.so"
#else
#define LUA_JROOT LUA_LROOT
#define LUA_RLPATH
#define LUA_RCPATH
#endif
#define LUA_JPATH ";" LUA_JROOT "/share" LUA_LJDIR "?.lua"
#define LUA_LLDIR LUA_LROOT "/share" LUA_LUADIR
#define LUA_LCDIR LUA_LROOT "/" LUA_LMULTILIB LUA_LUADIR
#define LUA_LLPATH ";" LUA_LLDIR "?.lua;" LUA_LLDIR "?/init.lua"
#define LUA_LCPATH1 ";" LUA_LCDIR "?.so"
#define LUA_LCPATH2 ";" LUA_LCDIR "loadall.so"
#define LUA_PATH_DEFAULT "./?.lua" LUA_JPATH LUA_LLPATH LUA_RLPATH
#define LUA_CPATH_DEFAULT "./?.so" LUA_LCPATH1 LUA_RCPATH LUA_LCPATH2
#endif
初始化后,会将相关的路径分别放置到package.path,package.cpath中:
print("package.path路径相关:")
print(package.path)
--[[
-- 为了便于查看,进行了分行
;
.\?.lua;
E:\Program Files (x86)\Lua\5.1\lua\?.lua;
E:\Program Files (x86)\Lua\5.1\lua\?\init.lua;
E:\Program Files (x86)\Lua\5.1\?.lua;
E:\Program Files (x86)\Lua\5.1\?\init.lua;
E:\Program Files (x86)\Lua\5.1\lua\?.luac
]]
print("package.cpath路径相关:")
print(package.cpath)
--[[
-- 为了便于查看,进行了分行
.\?.dll;
.\?51.dll;
E:\Program Files (x86)\Lua\5.1\?.dll;
E:\Program Files (x86)\Lua\5.1\?51.dll;
E:\Program Files (x86)\Lua\5.1\clibs\?.dll;
E:\Program Files (x86)\Lua\5.1\clibs\?51.dll;
E:\Program Files (x86)\Lua\5.1\loadall.dll;
E:\Program Files (x86)\Lua\5.1\clibs\loadall.dll
]]
看到如上的输出,其模块名的显示"?",在lua中,其搜索的路径实质上属于模板路径,在搜索模块时,首先会把模块名替换模块路径下的"?",再进行开始搜索。
从实质上来说,搜索的步骤依次分为如下部分:
1. 预加载搜索,通过package.preload来进行
2. Lua中搜索,通过package.path获取搜索路径,成功后会调用loadFile加载
3. C库中搜索,通过package.cpath获取搜索路径,成功后,会调用loadlib来加载
加载成功后,会存储到表package.loaded中。
其它:能否重新加载模块吗?
答案可以,我们需要将已加载模块的表置空,如下:
package.loaded["*"] = nil -- 置空已加载的模块数据
required("*") -- 再次加载
关于require的实现,代码如下:
-- 参考: Lua程序设计(第2版)function require(name)
-- 判定模块是否已加载
if not package.loaded[name] then
local loader = findloader(name)
if loader == nil then
error("unable to load module " .. name)
end
-- 将模块标记为已加载
package.loaded[name] = true
-- 初始化模块
local res = loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
-- 返回模块数据
return package.loaded[name]
end
来源:https://www.cnblogs.com/SkyflyBird/p/7851752.html