lua require加载机制详解:路径与loader解析

前言

从Lua5.1版本开始,就对模块和包添加了新的支持,可使用require和module来定义和使用模块和包。require用于使用模块,module用于创建模块。简单的说,一个模块就是一个程序库,可以通过require来加载,然后便得到了一个全局变量,表示一个table。这个table就像是一个命名空间,其内容就是模块中导出的所有东西,比如函数和常量,一个符合规范的模块还应使require返回这个table。

require函数

Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用require “<模块名>”就可以了。这个调用函数会返回一个由模块函数组成的table,并且还会定义一个包含该table的全局变量。但是,这些行为都是由模块完成的,而非require。所以,有些模块会选择返回其它值,或者具有其它的效果。那么require到底是如何加载模块的呢?

首先,要加载一个模块,就必须的知道这个模块在哪里。知道了这个模块在哪里以后,才能进行正确的加载。当我们写下require “mod”这样的代码以后,Lua是如何找这个mod的呢?

在搜索一个文件时,在windows上,很多都是根据windows的环境变量path来搜索。而require所使用的路径与传统的路径不同,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件,如果不存在,就会尝试下一项。路径中的每一项都是以分号隔开,比如路径为以下字符串:
代码如下:

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

那么,当我们require “mod”时,就会尝试着打开以下文件:

mod
mod.lua
c:\windows\mod
/usr/local/lua/mod/mod.lua

可以看到,require函数只处理了分号和问号,其它的都是由路径自己定义的。在实际编程中,require用于搜索的Lua文件的路径存放在变量package.path中,在我的电脑上,print(package.path)会输出以下内容:

;.\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?\init.lua;C:\Program Files (x86)\Lua\5.1\?.lua;C:\Program Files (x86)\Lua\5.1\?\init.lua;C:\Program Files (x86)\Lua\5.1\lua\?.luac;.\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?\init.lua;C:\Program Files (x86)\Lua\5.1\?.lua;C:\Program Files (x86)\Lua\5.1\?\init.lua;;D:\Game\5.1\lua\?.luac

如果require无法找到与模块名相符的Lua文件,那Lua就会开始找C程序库;这个的搜索地址为package.cpath对应的地址,在我的电脑上,print(package.cpath)会输出以下值:

.\?.dll;.\?51.dll;C:\Program Files (x86)\Lua\5.1\?.dll;C:\Program Files (x86)\Lua\5.1\?51.dll;C:\Program Files (x86)\Lua\5.1\clibs\?.dll;C:\Program Files (x86)\Lua\5.1\clibs\?51.dll;C:\Program Files (x86)\Lua\5.1\loadall.dll;C:\Program Files (x86)\Lua\5.1\clibs\loadall.dll

当找到了这个文件以后,如果这个文件是一个Lua文件,它就通过loadfile来加载该文件;如果找到的是一个C程序库,就通过loadlib来加载。

loadfile和loadlib都只是加载了代码,并没有运行它们,为了运行代码,require会以模块名作为参数来调用这些代码。

如果lua文件和C程序库都找不到,怎么办?我们试一下,随便require一个东西,比如:

require "demo"
 no field package.preload['demo']
 no file '.\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\lua\demo\init.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\demo\init.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.luac'
 no file '.\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\lua\demo\init.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\demo.lua'
 no file 'C:\Program Files (x86)\Lua\5.1\demo\init.lua'
 no file 'D:\Game\5.1\lua\demo.luac'
 no file '.\demo.dll'
 no file '.\demo51.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\demo.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\demo51.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\clibs\demo.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\clibs\demo51.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\loadall.dll'
 no file 'C:\Program Files (x86)\Lua\5.1\clibs\loadall.dll'

找不到就会提示报错!

package.loaded是什么?

require函数会将返回值存储到table package.loaded中;如果加载器没有返回值,require就会返回table package.loaded中的值。可以看到,我们上面的代码中,模块没有返回值,而是直接将模块名赋值给table package.loaded了。这说明package.loaded这个table中保存了已经加载的所有模块。

查看

require "main"

for k, v in pairs(package.loaded) do
  print("loaded:" .. k .. "")
end

输出:

loaded:string
loaded:debug
loaded:package
loaded:_G
loaded:io
loaded:os
loaded:table
loaded:math
loaded:coroutine
loaded:main     --这边出现了main文件名

现在我们就可以看看require到底是如何加载的呢?

在Lua编程中,通过`require`函数加载模块,这一过程涉及到搜索路径、lua和c库的加载机制。首先,我们来详细解析这两个关键方面。

1. 搜索路径

Lua的搜索路径由`package.path`变量管理,这是在Lua启动时根据环境变量`LUA_PATH`进行初始化的,多个路径之间用分号(`;`)隔开。例如,`package.path`可能设置为:
"./?.lua;./?.lc;/usr/local/?/init.lua"

这表示程序会首先尝试加载当前目录下的`.lua`和`.lc`文件,然后是`/usr/local`目录下的子目录及其`init.lua`文件。这个搜索顺序确保了Lua能够从常见的位置寻找所需的模块。

2. `require`加载过程

当调用`require`时,Lua会按照以下步骤寻找和加载模块:

检查缓存:`package.loaded[modname]`首先检查是否存在已经加载过的模块,如果有,直接返回该模块的值。
查找预加载器:如果`package.preload[modname]`存在,它通常是一个函数,该函数会被当作loader来执行,尝试加载模块。

路径加载器:
搜索lua文件:如果预加载器不存在,搜索将从`package.path`定义的路径开始,逐个查找lua文件,直到找到为止。
搜索c库:如果lua文件没有找到,搜索会转到`package.cpath`定义的c库路径,尝试动态链接和查找c函数作为loader。

默认加载器
所有-in-one-loader:如果以上步骤都无法找到合适的loader,可能会使用一个预设的统一加载器(`tryall-in-oneloader3`)。
加载和执行loader:一旦找到loader,require将`modname`模块名传递给加载器,并执行加载器。加载器函数负责执行模块的初始化和加载逻辑,通常会返回一个值,loader返回的值会被存储在`package.loaded[modname]`中,作为已加载模块的引用。如果loader本身没有返回值,`package.loaded[modname]`将被赋值为`true`。

cloader机制:当找到c库时,Lua会使用动态链接功能将应用与库连接起来,然后在库中寻找用于加载的C函数。这个C函数将成为实际的loader,负责执行模块的初始化和加载逻辑。

返回模块:最后,`require`返回`package.loaded[modname]`中的值,即加载的模块

总结来说,Lua的`require`加载机制既考虑了性能优化(缓存机制和预加载),又保证了灵活性(通过搜索路径扩展和c库的支持)。理解这些细节对于编写高效的Lua脚本以及处理模块加载异常至关重要。

lua打印table内容实例

-- @function: 打印table的内容,递归
-- @param: tbl 要打印的table
-- @param: level 递归的层数,默认不用传值进来
-- @param: filteDefault 是否过滤打印构造函数,默认为是
-- @return: return
function PrintTable( tbl , level, filteDefault)
  local msg = ""
  filteDefault = filteDefault or true --默认过滤关键字(DeleteMe, _class_type)
  level = level or 1
  local indent_str = ""
  for i = 1, level do
    indent_str = indent_str.."  "
  end

  print(indent_str .. "{")
  for k,v in pairs(tbl) do
    if filteDefault then
      if k ~= "_class_type" and k ~= "DeleteMe" then
        local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v))
        print(item_str)
        if type(v) == "table" then
          PrintTable(v, level + 1)
        end
      end
    else
      local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v))
      print(item_str)
      if type(v) == "table" then
        PrintTable(v, level + 1)
      end
    end
  end
  print(indent_str .. "}")
end

for k, v in pairs(package) do
  print("package["..k.."] type is "..type(v));
end

print("---------------------------------------------")
for k, v in pairs(package.loaded) do
  print("package.loaded["..k.."] type is "..type(v));
end

print("---------------------------------------------")

PrintTable(package.loaded['table'])

封装一个函数,打印table数据的内容,这里打印package.loaded已加载所有模块及table的内容

package[preload] type is table
package[loadlib] type is function
package[loaded] type is table
package[loaders] type is table
package[cpath] type is string
package[config] type is string
package[path] type is string
package[seeall] type is function

--------------------------------------------- 

package.loaded[string] type is table
package.loaded[debug] type is table
package.loaded[package] type is table
package.loaded[_G] type is table
package.loaded[io] type is table
package.loaded[os] type is table
package.loaded[table] type is table
package.loaded[math] type is table
package.loaded[coroutine] type is table

---------------------------------------------
{
   setn = function: 00C387F0
   insert = function: 00C38B50
   getn = function: 00C38A90
   foreachi = function: 00C38A70
   maxn = function: 00C38AD0
   foreach = function: 00C38990
   concat = function: 00C38CB0
   sort = function: 00C38930
   remove = function: 00C38570
}

require加载模块实例

testRuire.lua

print("load file testRequest.lua")

function testRq(x)
    print("in file testRequire.lua, input param is: "..x)
end

global = {}

global["testRq"] = testRq;

return global;

 test.lua

#!/usr/local/bin/lua

print("package.loaded type is "..type(package.loaded));


local mytest = require("testRuire")

for key, value in pairs(package.loaded)
do
    print("package.loaded["..key.."] type is "..type(value));
end

print(mytest);

if type(mytest) == "table"
then
    if type(mytest["testRq"]) == "function"
    then
        mytest["testRq"]("this is a test");
    end
end
 

打印信息

package.loaded type is table
load file testRequest.lua
package.loaded[string] type is table
package.loaded[debug] type is table
package.loaded[package] type is table
package.loaded[_G] type is table
package.loaded[io] type is table
package.loaded[os] type is table
package.loaded[table] type is table
package.loaded[math] type is table
package.loaded[coroutine] type is table
package.loaded[testRuire] type is table
table: 00BFA0C0
in file testRequire.lua, input param is: this is a test

如果要清除模块

 前面说过,require函数加载时会先检查 package.loaded 表里是不是已经有 modName 的表项了,如果有则直接返回表项的值,那如果已经加载了,想要清除模块,通过 package.loaded 修改加载模块的值为nil即可。

package.loaded["testRequire"] = nil;

参考:
lua require 层级 lua中的require_mob6454cc6bf0b7的技术博客_51CTO博客

Lua require 函数使用-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值