0

Lua语言中的模块调用

 2 years ago
source link: https://cjjkkk.github.io/require-in-lua/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在Lua语言中,可以使用require函数来加载模块和运行库,用于调用其他文件中的方法。

0x01 require函数的使用方法

-- 文件名为 test_module.lua
-- 定义一个名为 module 的模块
module = {}

-- 定义一个常量
module.constant = "这是一个常量"

-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end

local function func2()
print("这是一个私有函数!")
end

function module.func3()
func2()
end

return module

在文件中使用require("<文件名>")即可调用另一个Lua文件,同时引用其内部的模块

-- test_require.lua 文件
-- module 模块为上文提到到 test_module.lua
require("test_module")

print(module.constant)

module.func3()

还有另一种写法

-- 文件名为 test_module.lua
local _M = {}
function _M.test()
ngx.say("hello test!")
end
return _M
-- test_require.lua 文件
local test = require("test_module")
test:test()

这两种写法均可使用require调用文件。

require使用的路径是一个模式列表,每一个模式指明一种由虚文件名转化成是文件的名的方法

0x02 require函数的加载方式

在lua5.3的参考手册中如下

加载一个模块。这个函数首先查找package.loaded表,检测 modelname 是否被加载过。如果被加载过,require 返回 package.loaded[modname]中保存的值。否则,它试着为模块寻找 加载器

require 遵循package.searchers序列的指引来查找加载器。如果改变这个序列,我们可以改变 require 如何查找一个模块。下列说明基于package.searchers的默认配置。

首先 require 查找package.preload[modname]。 如果这里有一个值,这个值(必须是一个函数)就是那个加载器。 否则require使用Lua加载器去查找package.path的路径。如果查找失败,接着使用 C 加载器去查找package.cpath的路径。 如果都失败了,再尝试 一体化 加载器。

每次找到一个加载器,require 都用两个参数调用加载器: modelname 和一个在获取加载器过程中得到的参数。 (如果通过查找文件得到的加载器,这个额外参数是文件名。) 如果加载器返回非空值, require 将这个值赋给 package.loaded[modname]。 如果加载器没能返回一个非空值用于赋给 package.loaded[modname], require 会在那里设入 true 。 无论是什么情况,require 都会返回 package.loaded[modname] 的最终值。

如果在加载或运行模块时有错误, 或是无法为模块找到加载器,require 都会抛出错误。

也就是说,使用 require 函数加载模块时,默认会按照package.searchers方法来加载模块:首先会现在已有的模块中搜索,也就是package.preload这个表中搜索是否存在要加载的模块;然后会查找Lua库的加载库,也就是package.path;其次是加载C库的加载库package.cpath;最后会使用一个一体化加载器,可以C路径中查找指定模块的跟名字,然后查找子模块的加载函数。

具体的处理流程如下

require(modelname)
  • a. 首先在package.loaded查找modelname,如果该模块已经存在,就直接返回它的值;
  • b. 在package.preload查找modelname, 如果preload存在,那么就把它作为loader,调用loader(L);
  • c. 根据package.path的模式查找lua库modelname,这个库是通过module函数定义的,对于顶层的lua库,文件名和库名是一样的而且不需要调用显式地在lua文件中调用module函数(在ll_require函数中可以看到处理方式),也就是说lua会根据lua文件直接完成一个loader的初始化过程;
  • d. 根据package.cpath查找c库,这个库是符合lua的一些规范的(export具有一定特征的函数接口),lua先已动态的方式加载该c库,然后在库中查找并调用相应名字的接口,例如luaopen_hello_world;
  • e. 以第一个”.”为分割,将模块名划分为:(main, sub)的形式,根据package.cpath查找main,如果存在,就加载该库并查询相应的接口:luaopen_main_sub,例如:先查找hello库,并查询luaopen_hello_world接口
  • f. 得到loader后,用modelname作为唯一的参数调用该loader函数。当然参数是通过lua的栈传递的,所以loader的原型必须符合lua的规范:int LUA_FUNC(lua_State *L)

ll_require会将这个loader的返回值赋给package.loaded[modelname],如果loader不返回值同时package.loaded[modelname]不存在时,ll_require就会把package.loaded[modelname]设为true。最后ll_reuqire把package.loaded[modelname]返回给调用者。

require路径是一个模式列表,每一个模式指明一种由虚文件名(参数)转成实文件名的方法,每一个模式是一个包含可选的问号的文件名。匹配时,Lua会首先将问号用虚文件名替换,然后查找文件是否存在,如果不存在的话就换下一个模式。

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

当调用test时会尝试打开这些文件
test
test.lua
c:\windows\test
/usr/local/lua/test/test.lua

为了确定路径,Lua首先检查全局变量LUA_PATH是否为一个字符串,如果是则认为这个串就是路径;否则require检查环境变量LUA_PATH的值,如果两个都失败;require使用固定的路径(典型的“?;?.lua”

require的另一个功能是避免重复加载同一个文件两次。Lua保留一张所有已经加载的文件的列表(使用table保存)。如果一个加载的文件在表中存在, 则require简单的返回;表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名require同一个文件两次,将会加载两次该文件。比如require "foo"require "foo.lua",路径为”?;?.lua”将会加载foo.lua两次。我们也可以通过全局变量 _LOADED访问文件名列表,这样我们就可以判断文件是否被加载过;同样我们也可以使用一点小技巧让require加载一个文件两次。比如,require "foo"之后 _LOADED[“foo”]将不为nil,我们可以将其赋值为nil,require "foo.lua"将会再次加载该文件。

一个路径中的模式也可以不包含问号而只是一个固定的路径,比如:

?;?.lua;/usr/local/default.lua

这种情况下,require没有匹配的时候就会使用这个固定的文件(当然这个固定的路径必须放在模式列表的最后才有意义)。在require运行一个chunk以前,它定义了一个全局变量 _REQUIREDNAME用来保存被required的虚文件的文件名。我们可以通过使用这个技巧扩展require的功能。举个极端的例子,我们可以把路径设为"/usr/local/lua/newrequire.lua",这样以后每次调用require都会运行newrequire.lua,这种情况下可以通过使用 _REQUIREDNAME的值去实际加载required的文件。


Recommend

  • 79

  • 32
    • studygolang.com 5 years ago
    • Cache

    Go 语言中的递归和尾调用操作

    曾几何时,我看过一段关于 Go 递归函数的简单例子,作者用了极快的速度简单的陈述了 Go 这门语言中并没有优化递归这一操作,即使是在尾调用(tail calls)非常明显的时间。我当时并不理解什么是尾调用(tail calls),我非常想知道这位作...

  • 42
    • www.tuicool.com 4 years ago
    • Cache

    Ionic之调用原生模块相机

    Ionic 在 cordova 的基础上面又进行了封装,可以让我们更方便的在 ionic 中使用 cordova 插件 使用方法: 找到对应的Api:

  • 7

    【原文链接: https://cloud.tencent.com/developer/article/1392328】   IronPython是一种在.NET上实现的Python语言,使用IronPython就可以在.NET环境中调用Python代码。

  • 2
    • zhuanlan.zhihu.com 3 years ago
    • Cache

    深入Lua:调用相关的指令

    深入Lua:调用相关的指令这一节我们来深入解析与调用相关的指令,这些指令是:OP_CALL 调用OP_TAILCALL 尾调用OP_VARARG 可变参数OP_RETURN 返回解析这些指令的过程中,最重要的是时刻跟踪栈的变...

  • 8
    • zhuanlan.zhihu.com 3 years ago
    • Cache

    深入Lua:调用信息

    深入Lua:调用信息Lua在调用每个函数时,都会生成一个CallInfo,并将它们链接成一个双向链表。通过这个链表,我们就可以知道整个调用链的情况。CallInfo最主要的作用是记录一个函数调用涉及到的栈引用,先看一下该结构的声明:

  • 8
    • www.phodal.com 3 years ago
    • Cache

    Rust + LLVM 调用 C/C++ 模块

    Rust + LLVM 调用 C/C++ 模块 Posted by: Phodal Huang Nov. 22, 2020, 3:52 p.m. 在上一篇文章『LLVM +...

  • 1
    • z-rui.github.io 2 years ago
    • Cache

    Lua语言中的“惊喜”

    Lua语言中的“惊喜” | 睿 首页 Lua在有些地方没有采用...

  • 3

    探秘Java语言中子类调用父类的构造方法的方式 精选 原创 孙卫琴书友会 ...

  • 5
    • blog.codingnow.com 5 months ago
    • Cache

    Lua 的 C 模块之间如何传递内存块

    Lua 的 C 模块之间如何传递内存块 Lua 的数据类型非常有限,用 C 编写的 Lua 模块也没有统一的生态。在不同模块间传递内存块就是件很头疼的事情。 ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK