目录

一、扩展库 - Lua扩展库的运用

二、扩展库 - 库加载机制luaL_requiref


我们前面几章节讲过,Lua的函数调用有三种类型:C语言闭包函数,C 扩展库API和Lua语言(二进制操作码)。

这一章我们主要讲解一下Lua的扩展库Require的实现。

一、扩展库 - Lua扩展库的运用


我们看几个Lua的示例,一般情况下格式都是:库名称.方法名称(例如:string.find)

//字符串库
string.find("Hello Lua user", "Lua", 1) 
string.reverse("Lua")
string.format("the value is:%d",4)

//table表操作
table.insert(fruits,2,"grapes")
table.insert(fruits,"mango")

Lua内置的库,我们在第一章的时候就展示过,可以看一下下面的表格。

文件 作用

lauxlib.c

库编写用到的辅助函数库 

lbaselib.c

基础库 

ldblib.c

Debug 库 

linit.c

内嵌库的初始化 

liolib.c

IO 库 

lmathlib.c

数学库 

loadlib.c

动态扩展库管理 

loslib.c

OS 库 

lstrlib.c

字符串库 

ltablib.c

表处理库

二、扩展库 - 库加载机制luaL_requiref


Lua的标准库,在pmain方法中进行初始化加载。pmain方法为Lua运行的核心函数。

/*
** Main body of stand-alone interpreter (to be called in protected mode).
** Reads the options and handles them all.
*/
static int pmain (lua_State *L) {  
..省..
    /* 打开常规Lua的标准库 */
  luaL_openlibs(L);  /* open standard libraries */
..省..

我们主要看一下luaL_openlibs函数的实现:

  • 该函数,主要遍历一个loadedlibs数组,然后循环调用luaL_requiref方法,进行扩展库的逐个加载
  • loadedlibs数组包含两个元素:扩展库名称(例如:LUA_STRLIBNAME=string),启动方法(例如:luaopen_string)
/*
** these libs are loaded by lua.c and are readily available to any Lua
** program
** LUA标准库常量
**
** 定义标准库名称 & 启动的方法
*/
static const luaL_Reg loadedlibs[] = {
  {"_G", luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_COLIBNAME, luaopen_coroutine},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},
  {LUA_OSLIBNAME, luaopen_os},
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_UTF8LIBNAME, luaopen_utf8},
  {LUA_DBLIBNAME, luaopen_debug},
#if defined(LUA_COMPAT_BITLIB)
  {LUA_BITLIBNAME, luaopen_bit32},
#endif
  {NULL, NULL}
};

/**
 * 遍历Lua标准库,压入L->top栈
 */
LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib;
  /* "require" functions from 'loadedlibs' and set results to global table */
  for (lib = loadedlibs; lib->func; lib++) {
    luaL_requiref(L, lib->name, lib->func, 1);
    lua_pop(L, 1);  /* remove lib */
  }
}

luaL_requiref函数定义了模块加载的方法。

  • 该函数首先会在全局注册表G(L)->l_registry上创建一个LUA_LOADED_TABLE的表。如果该表已经创建,则不重复创建。并将该表放入L->top栈顶。
  • 然后通过lua_getfield方法,拿着modname模块名称去取,是否存在该key值。如果不存在,则返回nil,如L->top栈顶。
  • 通过lua_toboolean方法,检查模块是否已经重新加载过。如果没有加载过(nil值返回false),则重新加载模块。
  • 清空栈顶的L->top-1=nil值,并入栈openf和modename,并调用lua_call函数,执行各个模块定义的openf的函数调用。
  • 我们调用lua_call函数的时候,入参个数1&出参个数1,但是我们发现openf函数里面,主要调用luaL_newlib函数,会去创建一个module数组作为返回值,压入栈顶。

  • 然后我们调用lua_pushvalue拷贝一个module的值;然后调用lua_setfield将LOADED[modname]值设置为module,并调整堆栈,将栈顶拷贝的module值pop弹出(L->top--)
  • 然后通过lua_remove(L, -2); 方法将LOADED表清除。
  • 最后,我们将该模块module设置到全局环境变量上去。
/*
 * 加载系统模块
 * modname:模块名称
 * openf:回调函数
*/
LUALIB_API void luaL_requiref (lua_State *L, const char *modname,
                               lua_CFunction openf, int glb) {
  /* 获取注册表&G(L)->l_registry,如果不存在则创建注册表 */
  luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
  lua_getfield(L, -1, modname);  /* LOADED[modname] */

  /* 如果包没有加载,则重新加载 */
  if (!lua_toboolean(L, -1)) {  /* package not already loaded? */
    lua_pop(L, 1);  /* remove field 栈顶设置为nil */
    lua_pushcfunction(L, openf); /* 将openf设置到栈顶L->top->f的方法上 */
    lua_pushstring(L, modname);  /* 将openf设置到栈顶L->top->gc上 */
    lua_call(L, 1, 1);  /* call 'openf' to open module */
    lua_pushvalue(L, -1);  /* make copy of module (call result) */
    lua_setfield(L, -3, modname);  /* LOADED[modname] = module */
  }
  lua_remove(L, -2);  /* remove LOADED table */

  /* glb=true 则注册到全局表;例如LUA标准库,则会注册到全局表 */
  if (glb) {
    lua_pushvalue(L, -1);  /* copy of module */
    lua_setglobal(L, modname);  /* _G[modname] = module */
  }
}

画了一张luaL_requiref操作的栈演变过程,帮助大家更好的理解整个栈变更过程。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐