本篇在讲什么 理解Lua的package 本篇需要什么 对Lua语法有简单认知 对C++语法有简单认知 依赖Visual Studio工具 本篇的特色 具有全流程的图文教学 重实践,轻理论,快速上手 提供全流程的源码内容 |
★提高阅读体验★ 👉 ♠ 一级标题 👈👉 ♥ 二级标题 👈👉 ♣ 三级标题 👈👉 ♦ 四级标题 👈 |
目录
- ♠ 前言
- ♠ 前瞻
- ♠ 注册标准库
- ♥ luaL_openlibs
- ♥ luaopen_package
- ♠ package的参数
- ♥ loaders
- ♥ cpath
- ♥ path
- ♥ loaded
- ♥ loadlib
- ♥ seeall
- ♠ 推送
- ♠ 结语
♠ 前言
本篇文章简单了解一下Lua的全局表package
,及其表内字段功能
♠ 前瞻
阅读本篇文章需要准备编译Lua源码的工程,详情可参考下面文章
Lua学习笔记:在Visual Studio中调试Lua源码和打断点
阅读本篇文章前最好提前了解C/C++和Lua的交互原理,详情可参考下面文章
Lua学习笔记:C/C++和Lua的相互调用
♠ 注册标准库
首先我们要知道,在创建一个新的Lua虚拟机后,其环境内是没有定义任何函数的,我们需要注册一下标准库以供使用,代码如下
lua_State* L = luaL_newstate();
luaL_openlibs(L);
我们通过函数luaL_openlibs
向Lua环境注册一些标准函数
♥ luaL_openlibs
函数原型在在脚本linit.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
当中,如下所示
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");
#endiflua_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
的表,并且在表中注册了两个名为loadlib
和seeall
的函数
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
参数
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
中注册了两个名为require
和module
的函数
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
中,代码如下图所示
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不到他的,现在只要补充搜索路径即可,如下所示
package.path = package.path .. ";D:\\lua_src\\?.lua"
require "test3"
♥ loaded
管理全局函数和已经加载的标准库,在loaded内存在表__G
,全局函数和全局表都会存其中管理
管理一些已经require的模块,require的时候首先会判断package.loaded
内是否已经加载过了,如果加载过了直接返回
♥ loadlib
加载c库中的方法,返回的是一个lua_CFunction
,只加载不执行
♥ seeall
为模块设置一个元表,其__index字段引用_G
,以便该模块继承全局环境的值。作为功能模块的选项,功能等同于下面代码
setmetatable(M, {__index = _G});
♠ 推送
- Github
https://github.com/KingSun5
♠ 结语
若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。