lua require

环境:Lua5.1

工具:LuaForWindows(http://files.luaforge.net/releases/luaforwindows/luaforwindows

require作用类似于C/C++中的#include,特性:

1. 根据搜索目录加载指定模块

2. 判定模块是否已加载,避免重复加载

require加载的模块数据存储在 package.loaded 表中,其存储方式以模块名为key,以返回值(若无返回值,默认true)为value进行存储的。比如:

-- 导入任意lua文件
require("Demo")                              
require("Demo")                               
require ("Desktop.Demo2")                -- 会将“.”替换为“/”
require ("Desktop/File/Demo1")

-- 遍历加载的lua文件表
for key, value in pairs(package.loaded) do
    print(key,value)
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

Demo    true                    -- 新加,导入的重复文件不会在加载
Desktop.Demo2    true           -- 新加    
Desktop/File/Demo3    true      -- 新加                       
]]      

如果我们试图加载一个不存在的lua文件:

-- 加载不存在的模块
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]: ?
]]

我们看下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

        -- 将模块先默认设置为true
        package.loaded[name] = true
        -- 初始化模块,若模块存在返回值,将true替换为返回值数据
        local res = loader(name)
        if res ~= nil then 
            package.loaded[name] = res
        end 
    end 
    -- 返回模块数据
    return package.loaded[name]
end         

如上代码,我们可以理解require实现原理。因此重载模块数据的话,我们可以这样:

package.loaded["*"] = nil      -- 置空已加载的模块数据
require("*")              -- 再次加载

对比着错误的堆栈信息我们可以简单的了解到findloader的搜索路径主要有:

1. Lua文件路径相关

2. C库文件路径相关

我们来说明下findloader搜索路径相关。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"

初始化后,lua相关放置到package.path中,C库相关放置到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中,其搜索的路径实质上属于模板路径。即在搜索模块时,首先会把模块名替换模块路径下的"?",再进行通过package.searchpath搜索。比如:

?; ?.lua; c:\windows\?;  /usr/local/lua/lua/?/?.lua

关于package.searchpath的类似实现:

function search(modname, path)
    -- 将“.”替换为“/”
    modname = string.gsub(modname, "%.", "/")

    local msg = {}
    for c in string.gmatch(path, "[^;]+") do 
        -- 将“?”替换为模块名
        local fname = string.gsub(c, "?", modname)
        -- 检测文件是否存在
        local f = io.open(fname)
        if f then 
            f:close()
            return fname
        else 
            msg[#msg + 1] = string.format("\n\t no file '%s'", fname)
        end 
    end 
    return nil, table.concat(msg)       -- 没有找到
end 

而对于搜索所有文件主要通过package.searchers会列出require使用的所有搜索器,主要有三部分:

1. 预加载搜索,通过package.preload来进行

2. Lua中搜索,通过package.path获取搜索路径,成功后会调用loadFile加载

3. C库中搜索,通过package.cpath获取搜索路径,成功后,会调用loadlib来加载

加载成功后,会添加到package.loaded中。