Analyze the global environment, package and module organization structure in Lua

Time:2021-10-27

modularIs a library, andpackageIs a series of modules.LuaYou can load the module through require, and then get a global variable representing a table. Lua keeps all its global variables in a regular table called “environment”. This paper first introduces some practical technologies of environment, and then introduces how to reference modules and the basic methods of writing modules.

1. Environment
Lua saves the environment table in a global variable_ G, you can access and set it. Sometimes we want to operate on a global variable, but its name is stored in another variable, or it can be obtained through runtime calculation, which can be obtained through value =_ G [Varname] to get the global variable with dynamic name.

A big problem with “environment” is that it is global, and any modification to it will affect all parts of the program. Lua 5 allows each function to have a subset of environment to find global variables. You can change the environment of a function through setfenv. If the first parameter is 1, it means the current function, 2 means the function calling the current function (and so on), and the second parameter is a new environment table.

?
1
2
3
a = 1
setfenv(1, {})
Print (a) -- an error will be reported. Print is a nil. This is because once the environment is changed, all global accesses will use the new table

To avoid the above problems, you can use setfenv (1, {u g = _g}) to save the original environment, and then use_ G. Print. Another way to assemble a new environment is to use inheritance. The following code inherits print and a from the source environment, and any assignment occurs in the new table.

?
1
2
3
4
5
a = 1
local newgt = {}
setmetatable(newgt, {__index = _G})
setfenv(1, newgt)
print(a)

2. Modules and packages
2.1 calling module

To call the foo method in the module mod, you can load it with the require function, such as:

?
1
2
3
4
5
require "mod"
mod.foo()
--Or
local m = require "mod"
m.foo()

Behavior of the require function: (the path lookup strategy used by require will not be described in detail)
Check whether the module has been loaded in the package.loaded table
=>Once loaded, the corresponding value will be returned (it can be seen that a module will be loaded only once)
=>If it is not loaded, try to query the incoming module name in package.preload
===>Find a function and use it as the loader of the module
===>If not found, try loading the module from the Lua file or the C library
=====>Find the Lua file and load the file through LoadFile
=====>Find the C library and load the file through loadlib

2.2 service environment

The following code illustrates how to create a complex module using the environment:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--Module settings
local modname = "complex"
local M = {}
_G[modname] = M
package.loaded[modname] = M
 
--Declare everything the module needs from the outside
local _ G = _ G -- keep the reference of the old environment, which needs to be like_ G. Print works like this
local io = io
 
--After running this sentence, the environment changes
setfenv(1, M)
 
function new(r, i) return {r=r, i=i} end
 
function add(c1, c2)
  return new(c1.r + c2.r, c1.i + c2.i)
end

When you declare add the function is complex.add, the first mock exam is not required to add other functions to the same module.

2.3 module function

Lua 5.1 provides a new function module, which includes the above series of functions to define the environment. When you start writing a module, you can directly replace the previous setup code with module (“module name”, package. SeeAll). After this call is made at the beginning of a module file, all subsequent codes do not need to limit the module name and external name, nor do they need to return the module table.

2.4 sub modules and packages

Lua supports module names with levels, and uses a point to separate the levels in the name. For example, a module named mod.sub is a sub module of mod. A package is a complete module tree, which is the unit in Lua. Note that when searching for a sub module file, require will search with the point number as the directory separator, that is, calling require “A.B” will try to open. / A / b.lua, / usr / local / Lua / A / b.lua, / usr / local / Lua / A / B / init.lua. Through this loading strategy, all modules of the package can be organized into a directory.

2.5 loading Lua modules in a custom manner
Since Lua 5.1, Lua has a standard module management library. Therefore, all module loading is completed through require. The design of require is quite extensible. It will try to load new modules one by one from several defined loaders. Four loaders are provided in the system library to implement the loaded module, Lua module and C extension module respectively (two loaders are used to load the C extension module). These loaders are placed in a table in the required environment in the form of cffunction.

If we want to change the loading form of lua module, we only need to replace or add a new loader.

All you need to do is imitate the loader in loadlib. C_ For example, in our project, it is allowed to load an encrypted Lua code file from a user-defined format packet. Then write a few lines of C code to obtain the required environment (using lua_getfenv), take out the “loaders” table, and insert the new custom loader into index 2.

The specific code will not be detailed. Read it carefully LL_ The implementation of require (in loadlib. C) is easy to understand. Our whole work did not take more than two hours from analysis to implementation, which is really due to Lua’s good design: d even if you want to load Lua module from a network connected data stream or download it through HTTP / ftp protocol, it’s OK.