Lua学习笔记:探究package

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 前言 *** ** * ** *** 本篇在讲什么 理解Lua的package *** ** * ** *** 本篇需要什么 对Lua语法有简单认知 对C++语法有简单认知 依赖Visual Studio工具 *** ** * ** *** 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 |


|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ★提高阅读体验★ ## 👉 ♠ 一级标题 👈 ### 👉 ♥ 二级标题 👈 #### 👉 ♣ 三级标题 👈 ##### 👉 ♦ 四级标题 👈 |


目录

  • [++♠ 前言++](#♠ 前言)
  • [++♠ 前瞻++](#♠ 前瞻)
  • [++♠ 注册标准库++](#♠ 注册标准库)
    • [++♥ luaL_openlibs++](#♥ luaL_openlibs)
    • [++♥ luaopen_package++](#♥ luaopen_package)
  • [++♠ package的参数++](#♠ package的参数)
    • [++♥ loaders++](#♥ loaders)
    • [++♥ cpath++](#♥ cpath)
    • [++♥ path++](#♥ path)
    • [++♥ loaded++](#♥ loaded)
    • [++♥ loadlib++](#♥ loadlib)
    • [++♥ seeall++](#♥ seeall)
  • [++♠ 推送++](#♠ 推送)
  • [++♠ 结语++](#♠ 结语)

++♠ 前言++

本篇文章简单了解一下Lua的全局表package,及其表内字段功能


++♠ 前瞻++

阅读本篇文章需要准备编译Lua源码的工程,详情可参考下面文章

Lua学习笔记:在Visual Studio中调试Lua源码和打断点

阅读本篇文章前最好提前了解C/C++和Lua的交互原理,详情可参考下面文章

Lua学习笔记:C/C++和Lua的相互调用


++♠ 注册标准库++

首先我们要知道,在创建一个新的Lua虚拟机后,其环境内是没有定义任何函数的,我们需要注册一下标准库以供使用,代码如下

c 复制代码
lua_State* L = luaL_newstate();
luaL_openlibs(L);

我们通过函数luaL_openlibs向Lua环境注册一些标准函数


++♥ luaL_openlibs++

函数原型在在脚本linit.c当中,代码如下所示

c 复制代码
static const luaL_Reg lualibs[] = {
  {"", luaopen_base},
  {LUA_LOADLIBNAME, luaopen_package},
  {LUA_TABLIBNAME, luaopen_table},
  {LUA_IOLIBNAME, luaopen_io},
  {LUA_OSLIBNAME, luaopen_os},
  {LUA_STRLIBNAME, luaopen_string},
  {LUA_MATHLIBNAME, luaopen_math},
  {LUA_DBLIBNAME, luaopen_debug},
  {NULL, NULL}
};


LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib = lualibs;
  for (; lib->func; lib++) {
    lua_pushcfunction(L, lib->func);
    lua_pushstring(L, lib->name);
    lua_call(L, 1, 0);
  }
}

可以看到在for循环当中以此把lualibs数组内的函数和函数名字注册到了lua_State


++♥ luaopen_package++

函数luaopen_package是注册package表的核心函数,其源码定义在脚本loadlib.c当中,如下所示

c 复制代码
LUALIB_API int luaopen_package (lua_State *L) {
  int i;
  /* create new type _LOADLIB */
  luaL_newmetatable(L, "_LOADLIB");
  lua_pushcfunction(L, gctm);
  lua_setfield(L, -2, "__gc");
  /* create `package' table */
  luaL_register(L, LUA_LOADLIBNAME, pk_funcs);
#if defined(LUA_COMPAT_LOADLIB) 
  lua_getfield(L, -1, "loadlib");
  lua_setfield(L, LUA_GLOBALSINDEX, "loadlib");
#endif
  lua_pushvalue(L, -1);
  lua_replace(L, LUA_ENVIRONINDEX);
  /* create `loaders' table */
  lua_createtable(L, 0, sizeof(loaders)/sizeof(loaders[0]) - 1);
  /* fill it with pre-defined loaders */
  for (i=0; loaders[i] != NULL; i++) {
    lua_pushcfunction(L, loaders[i]);
    lua_rawseti(L, -2, i+1);
  }
  lua_setfield(L, -2, "loaders");  /* put it in field `loaders' */
  setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT);  /* set field `path' */
  setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */
  /* store config information */
  lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n"
                     LUA_EXECDIR "\n" LUA_IGMARK);
  lua_setfield(L, -2, "config");
  /* set field `loaded' */
  luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2);
  lua_setfield(L, -2, "loaded");
  /* set field `preload' */
  lua_newtable(L);
  lua_setfield(L, -2, "preload");
  lua_pushvalue(L, LUA_GLOBALSINDEX);
  luaL_register(L, NULL, ll_funcs);  /* open lib into global table */
  lua_pop(L, 1);
  return 1;  /* return 'package' table */
}

我们挑重点来看,在开始向环境当中注册了一个名为package的表,并且在表中注册了两个名为loadlibseeall的函数

c 复制代码
static const luaL_Reg pk_funcs[] = {
  {"loadlib", ll_loadlib},
  {"seeall", ll_seeall},
  {NULL, NULL}
};


/* create `package' table */
luaL_register(L, LUA_LOADLIBNAME, pk_funcs);

随后依次为表package设置了loaders、path、cpath、config、loaded、preload参数

c 复制代码
lua_setfield(L, -2, "loaders");  /* put it in field `loaders' */
setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT);  /* set field `path' */
setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */
lua_setfield(L, -2, "config");
lua_setfield(L, -2, "loaded");
lua_setfield(L, -2, "preload");

最后又向loaded中的全局表_G中注册了两个名为requiremodule的函数

c 复制代码
static const luaL_Reg ll_funcs[] = {
  {"module", ll_module},
  {"require", ll_require},
  {NULL, NULL}
};


lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_register(L, NULL, ll_funcs);  /* open lib into global table */

所以经过了上述一通操作,我们的全局表package最终变成了下面这个样子

{
	["config"]   = "...",
	["cpath"]    = "...",
	["loaded"]   = {
		["_G"]   = {
			["require"] = function,
			["module"]  = function,
		}
	},
	["loaders"]  = {},
	["loadlib"]  = function,
	["path"]     = "...",
	["preload"]  = {},
	["seeall"]   = function,
}

++♠ package的参数++

从上边创建package的时候我们其实已经知道,其中所包含的字段,这里我们简单了解一下这些个字段都是干嘛用的

通过pairs遍历package的key值,也可以很直观的看到package中所有的参数


++♥ loaders++

存储加载器的表,打印出来如下图所示

其对应的四个加载器,定义在loadlib.c中,代码如下图所示

c 复制代码
static const lua_CFunction loaders[] =
  {loader_preload, loader_Lua, loader_C, loader_Croot, NULL};

这个加载器可以理解为解析文件的方式,比如我们require了一个lua文件,那么会通过loader_Lua方法去解析文件,如果require了一个c文件,那么会通过loader_C方法去解析文件

后续得空专门为require的流程再写一遍笔记


++♥ cpath++

c加载器的搜索路径,loader_C方法会从package.cpath路径下搜索对应的文件


++♥ path++

Lua文件加载器的搜索路径,Luader_Lua方法会从package.path下搜索对应的Lua文件

我们在D盘某个路径下有个lua文件test3.lua,我们从c盘的一个lua文件正常是require不到他的,现在只要补充搜索路径即可,如下所示

lua 复制代码
package.path = package.path .. ";D:\\lua_src\\?.lua"
require "test3"

++♥ loaded++

管理全局函数和已经加载的标准库,在loaded内存在表__G,全局函数和全局表都会存其中管理

管理一些已经require的模块,require的时候首先会判断package.loaded内是否已经加载过了,如果加载过了直接返回


++♥ loadlib++

加载c库中的方法,返回的是一个lua_CFunction,只加载不执行


++♥ seeall++

为模块设置一个元表,其__index字段引用_G,以便该模块继承全局环境的值。作为功能模块的选项,功能等同于下面代码

lua 复制代码
setmetatable(M, {__index = _G});

++♠ 推送++


++♠ 结语++

若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
👉 本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处👈

相关推荐
alibaba_张无忌43 分钟前
金融学期末速成笔记
笔记·金融
Back~~1 小时前
MFC1(note)
学习
engchina1 小时前
Oracle ADB 导入 BANK_GRAPH 的学习数据
数据库·学习·oracle·graph
Komorebi.py2 小时前
【Linux】-学习笔记03
linux·笔记·学习
程序员劝退师_3 小时前
Kafka学习笔记
笔记·学习·kafka
帅比九日3 小时前
【HarmonyOS NEXT】实战——登录页面
前端·学习·华为·harmonyos
李笠^_^3 小时前
Python学习------第八天
学习
Lotay_天天3 小时前
删库跑路,启动!
学习
爱吃生蚝的于勒3 小时前
C语言最简单的扫雷实现(解析加原码)
c语言·开发语言·学习·计算机网络·算法·游戏程序·关卡设计
麻花20133 小时前
WPF学习之路,控件的只读、是否可以、是否可见属性控制
服务器·前端·学习