Skip to content

6.3 – 模块

包相关库提供了Lua中加载模块相关的基础工具。其直接将一个函数require导出到了全局环境中,其他都导出到了表 package 中。

require (modname)

加载给定的模块。此函数一开始会在表package.loaded中确认名为参数 modname 模块是否已经加载。如果是,那么 require 会返回存在 package.loaded[modname] 中的值。(这种情况下是没有第二个返回值的,以表示此次调用没有加载任何模块。)否则将尝试查找模块的加载器 loader

package.searchers会引导查找加载器。该表中的每一个元素都是一个查找函数,通过其各自的方式查找模块。所以通过改变该表我们就可以改变 require 查找模块的方式。下边解释了默认配置下的 package.searchers 。

首先 require 会查询 package.preload[modname]。如果找到了,那么其对应的值(一定是个函数)就是加载器。否则 require 会使用另一个加载器,这个加载器使用存在 package.path 的路径查找模块。如果又失败了,则会使用一个C加载器,其会在 package.cpath 中寻找。如果还是失败了,则会尝试使用 all-in-one 加载器(参见package.searchers)。

对于每个找到的加载器, require 都会使用两个参数调用它:modname 和一个扩展值(是一个 加载器数据 loader data,也是由查找器返回的) 。这个加载器数据是模块所用到的一个任意值;对于默认的查找器,它是加载器被查找到的位置。(例如,如果是一个来自文件中的加载器,这个值就是文件路径。)如果加载器返回了任何非空的值,require 会将其赋值给 package.loaded[modname] 。如果加载器没有返回有效值并没有任何 package.loaded[modname] 的赋值,那么 require 会将该条目置为true。任何情况下,require 都会返回 package.loaded[modname] 最终的值。除这个值以外,require 还会将查找器返回的加载器数据作为第二个结果返回,其表明了 require 是如何找到的模块。

如果在加载或运行模块的过程中发生了任何错误,或者没有找到任何加载器,那么 require 都会抛出一个错误。

package.config

该字符串描述了关于包的一些编译时设置。此字符串由这些行构成:

  • 第一行是目录的分割符。Windows下默认是 '' ,其他系统默认是 '/' 。
  • 第二行是各个路径的分割符。默认为 ';' 。
  • 第三行是标记匹配替换的符号。默认为 '?' 。
  • 第四行是表示在Windows中会被替换为可执行文件所在的目录的符号。默认为 '!' 。
  • 第五行是一个标记,会使得在构建 luaopen_ 函数名时忽略其后边的文本。默认为 '-' 。

package.cpath

该字符串是用于reqire中查找 C 加载器的路径。

Lua初始化package.cpath的方式和package.path是一样的,使用环境变量 LUA_CPATH_5_4 ,或使用环境变量 LUA_CPATH,或者使用定义在 luaconf.h 文件中的默认路径。

package.loaded

用于require的一个表,可以用来确认哪些模块已经加载了。当你需要一个名为 modname 的模块时,如果 package.loaded[modname] 存在,那么require就会之间返回存在此处的值。

这个全局变量只是真正的 loaded 表的引用;给这个变量赋值并不会改变require的行为。真正的 loaded 表是存在C注册表中的(参见4.3),索引为 LUA_LOADED_TABLE, 一个预定义的字符串。

package.loadlib (libname, funcname)

将一个名为 libname 的C库动态链接到宿主程序中。

如果 funcname 为 "*" ,那么只会链接这个库,将其符号导出给其他的动态链接库。否则,会在这个库中查找名为 funcname 的函数并找到的函数作为Lua定义的C函数形式返回。所以, funcname 函数必须符合lua_CFunction的约定类型。

这是一个底层接口。其完全绕开了包和模块系统。与require不同,其不会执行任何路径查找和紫红添加扩展名。参数 libname 必须是C库的完整文件名,其包括包括必要的路径和扩展名。参数 funcname 必须是C库中已经导出的确切名称(可能取决于所使用的C编译器和链接器)。

ISO标准C并不支持此函数。因为函数的功能只在部分支持动态链接的操作系统上可用(Windows、Linux、Mac OS X、Solaris、BSD以及其他支持 dlfcn 的类Unix系统)。

此函数本质上是不安全的,因为其允许Lua在系统中的任何可读动态库中调用任何函数。(Lua说的函数都是假设符合约定类型并遵守调用约定的,可以参见lua_CFunction。因此在任意库中调用任意函数通常会引起访问冲突。)

package.path

该字符串变量存储了供require查找加载器的路径。

在启动时Lua会初始化这个变量的值,其值来自于环境变量 LUA_PATH_5_4 或是 LUA_PATH ,如果没有找到这些环境变量,则使用定义在 luaconf.h 文件中的默认路径。环境变量中的 ";;" 会被替换为默认路径。

package.preload

存储了一些特殊模块的加载器的表(参见require)。

该变量只是引用了真正的表;给这个变量赋值不会改变require的行为。真正的表是存在C注册表中的(参见4.3),索引是一个预定义的字符串 LUA_LOADED_TABLE 。

package.searchers

控制了require如何查找模块的表。

该表中的每个条目都是一个查找函数。每当查找一个模块,require会先后调用其中的每个查找函数,并将模块名(由传进require的参数而来)作为唯一参数传入。查找器如果找到了模块,则返回另一个函数,即该模块的加载器,以及一个额外的值,即加载数据,将会传入加载器并作为require的第二个参数。如果没找到模块,会返回一个字符串以解释原因(或是返回nil以表示无话可说)。

Lua会使用四个函数来初始化这个表。

第一个函数会直接在package.preload表中查找加载器。

第二个函数会使用package.path中的路径来查找有没有相应的Lua库。搜索方式可以参照package.searchpath中的描述。

第三个函数会使用package.cpath中所给出的路径来查找有没有相应的C库。同样,其搜索方式可以参照package.searchpath中的描述。例如,如果有一个如下的C路径:

"./?.so;./?.dll;/usr/local/?/init.so"

对于名为 foo 的模块,查找器将会先后尝试打开这些文件:./foo.so、./foo.dll、以及 /usr/local/foo/init.so 。一旦找到了C库,查找器就会使用动态链接工具将这个库链接到程序中。然后在其中搜索一个C函数作为加载器。这个C函数的名称是一个以 "luaopen_" 开头、后跟模块名的字符串,其中模块名中的 '.' 要替换成下划线。此外,如果模块名中含有连字符 '-' ,那么第一个连字符本身以及其后的内容都要被移除,如果有模块名为 a.b.c-v2.1 ,那么函数名应当为 luaopen_a_b_c 。

第四个函数会尝试使用 all-in-one 加载器。其搜索给定模块的根名称的C库。例如导入 a.b.c 库是,其将会搜索名为 a 的库。如果找到了,会在其中搜索子模块的 open 函数;在本例中,这个 open 函数会是 luaopen_a_b_c 。通过这种方式,一个包可以将多个C子模块打包到一个库中,其中每个子模块都有各自的 open 函数。

除了第一个查找函数之外,所有的查找函数都会返回一个记录了模块文件路径的额外字符串,由package.searchpath返回。第一个查找函数始终返回字符串 ":preload:" 。

查找函数应该不会抛出错误并且对Lua没有副作用。(可能会因为将库连接到了程序,从而对C代码产生副作用。)

package.searchpath (name, path [, sep [, rep]])

在给定路径 path 中查找名称 name 。

路径是一个包含了多个模板 templates 的字符串,各模板之间使用分号隔开。对于每个模板,此函数会将每个模板中的问号替换由参数 name 得来的文件路径,其中会将 name 中的每个遇到的 sep(默认是 '.')替换为 rep (默认为系统中的目录分隔符),然后再尝试打开最后得到的各个文件路径。

例如一个路径为如下的字符串:

"./?.lua;./?.lc;/usr/local/?/init.lua"

如果在其中查找名称 foo.a ,则会先后尝试打开这些文件: ./foo/a.lua、./foo/a.lc、以及 /usr/local/foo/a/init.lua 。

如果成功了,则返回第一个遇到的可以用读模式打开的文件名(在此之后会关闭文件)。否则返回fail加上一个错误消息。(这个错误消息列出了每个文件打不开的原因。)