6.3 – 模块

包管理库提供了从 Lua 中加载模块的基础库。只有一个导出函数直接放在全局环境中:require。所有其它的部分都导出在表 package 中。


require (modname)

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

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

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

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

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


package.config

一个描述有一些为包管理准备的编译期配置信息的串。这个字符串由一系列行构成:

  • 第一行是目录分割串。对于 Windows 默认是 '\' ,对于其它系统是 '/' 。
  • 第二行是用于路径中的分割符。默认值是 ';' 。
  • 第三行是用于标记模板替换点的字符串。默认是 '?' 。
  • 第四行是在 Windows 中将被替换成执行程序所在目录的路径的字符串。默认是 '!' 。
  • 第五行是一个记号,该记号之后的所有文本将在构建 luaopen_ 函数名时被忽略掉。默认是 '-'。

package.cpath

这个路径被 require 在 C 加载器中做搜索时用到。

Lua 用和初始化 Lua 路径 package.path相同的方式初始化 C 路径 package.cpath 。它会使用环境变量 LUA_CPATH_5_3 或环境变量 LUA_CPATH 初始化。要么就采用 luaconf.h 中定义的默认路径。


package.loaded

用于 require 控制哪些模块已经被加载的表。当你请求一个 modname 模块,且package.loaded[modname] 不为假时,require 简单返回储存在内的值。

这个变量仅仅是对真正那张表的引用;改变这个值并不会改变 require 使用的表。


package.loadlib (libname, funcname)

让宿主程序动态链接 C 库 libname

funcname 为 "*",它仅仅连接该库,让库中的符号都导出给其它动态链接库使用。否则,它查找库中的函数 funcname ,以 C 函数的形式返回这个函数。因此,funcname 必须遵循原型 lua_CFunction (参见 lua_CFunction)。

这是一个低阶函数。它完全绕过了包模块系统。和 require 不同,它不会做任何路径查询,也不会自动加扩展名。libname 必须是一个 C 库需要的完整的文件名,如果有必要,需要提供路径和扩展名。funcname 必须是 C 库需要的准确名字(这取决于使用的 C 编译器和链接器)。

这个函数在标准 C 中不支持。因此,它只在部分平台有效( Windows ,Linux ,Mac OS X, Solaris, BSD, 加上支持dlfcn 标准的 Unix 系统)。


package.path

这个路径被 require 在 Lua 加载器中做搜索时用到。

在启动时,Lua 用环境变量 LUA_PATH_5_3或环境变量 LUA_PATH 来初始化这个变量。或采用 luaconf.h 中的默认路径。环境变量中出现的所有 ";;" 都会被替换成默认路径。


package.preload

保存有一些特殊模块的加载器(参见 require)。

这个变量仅仅是对真正那张表的引用;改变这个值并不会改变 require 使用的表。


package.searchers

用于 require 控制如何加载模块的表。

这张表内的每一项都是一个 查找器函数。当查找一个模块时,require 按次序调用这些查找器,并传入模块名(require 的参数)作为唯一的一个参数。此函数可以返回另一个函数(模块的 加载器)加上另一个将传递给这个加载器的参数。或是返回一个描述为何没有找到这个模块的字符串(或是返回 nil 什么也不想说)。

Lua 用四个查找器函数初始化这张表。

第一个查找器就是简单的在 package.preload 表中查找加载器。

第二个查找器用于查找 Lua 库的加载库。它使用储存在 package.path 中的路径来做查找工作。查找过程和函数 package.searchpath 描述的一致。

第三个查找器用于查找 C 库的加载库。它使用储存在 package.cpath 中的路径来做查找工作。同样,查找过程和函数 package.searchpath 描述的一致。例如,如果 C 路径是这样一个字符串

  1. "./?.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

第四个搜索器是 一体化加载器。它从 C 路径中查找指定模块的根名字。例如,当请求 a.b.c 时,它将查找 a 这个 C 库。如果找得到,它会在里面找子模块的加载函数。在我们的例子中,就是找 luaopen_a_b_c。利用这个机制,可以把若干 C 子模块打包进单个库。每个子模块都可以有原本的加载函数名。

除了第一个(预加载)搜索器外,每个搜索器都会返回它找到的模块的文件名。这和 package.searchpath 的返回值一样。第一个搜索器没有返回值。


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

在指定 path 中搜索指定的 name

路径是一个包含有一系列以分号分割的 模板 构成的字符串。对于每个模板,都会用 name 替换其中的每个问号(如果有的话)。且将其中的 sep (默认是点)替换为 rep(默认是系统的目录分割符)。然后尝试打开这个文件名。

例如,如果路径是字符串

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

搜索 foo.a 这个名字将依次尝试打开文件./foo/a.lua , ./foo/a.lc ,以及/usr/local/foo/a/init.lua

返回第一个可以用读模式打开(并马上关闭该文件)的文件的名字。如果不存在这样的文件,返回 nil 加上错误消息。(这条错误消息列出了所有尝试打开的文件名。)