lua _G 实现

 

在Lua脚本层,Lua将所有的全局变量保存在一个常规的table中,这个table被称为全局环境,并且将这个table保存在一个全局变量_G中,也就是说在脚本中可以用_G获取这个全局table,并且有_G._G == _G,在默认情况,Lua在全局环境_G中添加了标准库比如math、函数比如pairs等。可以通过下面代码,可以递归打印_G中的所有信息:

_G = {_G,........}

[cpp]  view plain  copy
  1. function treaverse_global_env(curtable,level)  
  2.     for key,value in pairs(curtable or {}) do  
  3.     local prefix = string.rep(" ",level*5)  
  4.     print(string.format("%s%s(%s)",prefix,key,type(value)))  
  5.   
  6.     --注意死循环  
  7.     if (type(value) == "table" ) and key ~= "_G" and (not value.package) then  
  8.         treaverse_global_env(value,level + 1)  
  9.     elseif (type(value) == "table" ) and (value.package) then  
  10.         print(string.format("%sSKIPTABLE:%s",prefix,key))  
  11.     end   
  12.     end   
  13. end  
  14.   
  15. treaverse_global_env(_G,0)  
注意Lua虚拟机本身是不会使用_G这个变量的,在脚本中,可以任意改变这个变量_G的值,不会影响任何环境或副作用。比如下面代码:

[cpp]  view plain  copy
  1. local cf = loadstring(" local i=0  i=i+1 print(i) ")  
  2.   
  3. --从后面两个输出我们可以看出,生成的函数的环境就是全局_G  
  4. print(cf,getfenv(cf),_G)  -- function: 0025AF58      table: 00751C68 table: 00751C68  
  5.   
  6. --改变_G的值  
  7. _G = {}  
  8. cf()  --1  
  9.   --cf中存储了_G的地址   _G 可以看做一个指针  _G = {} 相当于 将_G指向一个新表  但是cf中存储的内容并没有变
  10. --虽然改变了_G的值,但函数的的环境仍然是全局环境table地址仍然是00751C68  
  11. print(cf,getfenv(cf),_G)  -- function: 0075AF58      table: 00751C68 table: 0075B468  
默认情况下,在Lua中当compiles a chunk时,都是以_G作为环境的,当然可以通过函数load或loadfile,改变compiles a chunk时的环境。在C中,可以使用lua_load(类似有luaL_load*作为前缀的辅助函数)
来load a lua chunk,load的后得到的函数,默认情况下,它的第一个upvalue就是_G,我们可以改变第一个upvalue,来改变得到的函数执行环境

变量_G是在C中注册的(源码linit.c, lbaselib.c中),在C中,可以直接调用lua_pushglobaltable把这个全局环境压入栈中,在lua5.2 该函数实质就是从注册表中获取这个全局环境,即lua_pushglobaltable用下面宏定义的:

[cpp]  view plain  copy
  1. #define lua_pushglobaltable(L) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)  
这里的LUA_REGISTRYINDEX是Lua注册表(注册表是lua虚拟机范围内是全局唯一的)的伪索引,LUA_RIDX_GLOBALS是全局环境在注册表中的索引(也就说,全局环境_G是虚拟机范围内是全局唯一的)。


在创建注册表 向注册表中添加_G表

最后可以通过源码,来了解一下_G全局环境的变量的注册。在一个Lua虚拟机中,用一个全局结构global_State来管理多个lua_State。在调用luaL_newstate()时,在创建一个全局结构global_State和一个lua_State后,
luaL_newstate会调用f_luaopen,然后f_luaopen调用init_registry来初始化注册表,函数init_registry代码如下:

[cpp]  view plain  copy
  1. /* 
  2. ** 创建注册表和表中预定义的值 
  3. */  
  4. static void init_registry (lua_State *L, global_State *g) {  
  5.   TValue mt;   
  6.   /*创建注册表,初始化注册表数组部分大小为LUA_RIDX_LAST*/  
  7.   Table *registry = luaH_new(L);  //创建一个空的table
  8.   sethvalue(L, &g->l_registry, registry);    
  9.   luaH_resize(L, registry, LUA_RIDX_LAST, 0);   //重构 table 的大小
  10.   
  11.   /*把这个注册表的数组部分的第一个元素赋值为主线程的状态机L(这里所说的线程并非是os的线程,而是lua的状态机概念)*/  
  12.   /*即 registry[LUA_RIDX_MAINTHREAD] = L */  
  13.   setthvalue(L, &mt, L);   
  14.   luaH_setint(L, registry, LUA_RIDX_MAINTHREAD, &mt);  
  15.   
  16.   /*把注册表的数组部分的第二个元素赋值为全局表,即registry[LUA_RIDX_GLOBALS] = table of globals */  
  17.   sethvalue(L, &mt, luaH_new(L));  
  18.   luaH_setint(L, registry, LUA_RIDX_GLOBALS, &mt);  
全局表注册到脚本中的,这样在脚本就可以使用变量_G来获取全局表了,代码如下:
[cpp]  view plain  copy
  1. static const luaL_Reg loadedlibs[] = {   
  2.   {"_G", luaopen_base},  
  3.   {LUA_LOADLIBNAME, luaopen_package},  
  4.   /* 
  5.   **省略了一些代码 
  6.          */  
  7.   {LUA_DBLIBNAME, luaopen_debug},  
  8.   {NULL, NULL}  
  9. };  
  10.   
  11. LUALIB_API void luaL_openlibs (lua_State *L) {  
  12.   const luaL_Reg *lib;  
  13.   /* 从'loadedlibs'中调用函数,并把调用的结果res除了package.loaded[modname]=res */  
  14.   /*同时设置到全局变量modname中,供脚本层调用*/  
  15.   for (lib = loadedlibs; lib->func; lib++) {  
  16.     luaL_requiref(L, lib->name, lib->func, 1);   
  17.     lua_pop(L, 1);  /* remove lib */  
  18.   }  
  19.   /* 
  20.   **省略了一些代码 
  21.          */  
  22. }  

在函数luaopen_base中会把脚本用到的函数注册到全局表, 代码如下:

[cpp]  view plain  copy
  1. static const luaL_Reg base_funcs[] = {   
  2.   {"assert", luaB_assert},  
  3.   {"collectgarbage", luaB_collectgarbage},  
  4.   /* 
  5.   **省略了一些代码 
  6.          */  
  7.   {"xpcall", luaB_xpcall},  
  8.   {NULL, NULL}  
  9. };  
  10.       
  11. LUAMOD_API int luaopen_base (lua_State *L) {  
  12.   /* 设置_G._G = _G*/  
  13.   lua_pushglobaltable(L);  
  14.   lua_pushglobaltable(L);  
  15.   lua_setfield(L, -2, "_G");  // _G._G =_G 
  16.   /*在全局表中添加脚本中用到的全局函数*/  
  17.   luaL_setfuncs(L, base_funcs, 0);   
  18.   lua_pushliteral(L, LUA_VERSION);  
  19.   lua_setfield(L, -2, "_VERSION");  /* set global _VERSION */  
  20.   return 1;   

总结:

_G为加载的每个模块的默认环境变量,第一个upvalue就是_G,_G保存再注册表中,

1.当创建注册表的时候创建_G同时向注册表中添加_G(注册表的创建是在lua_newstate时)

2.在openlib时 取出注册表中创建的_G table 将其压入lua栈中 

3.luaopen_base将一些全局函数放入_G表中




<think>我们正在分析一个名为`lua_call_luci`的C函数,它用于与Lua脚本进行交互。根据引用资料和代码,我们可以推断这个函数的作用是调用特定的Lua函数(可能是LuCI框架中的函数),并处理返回结果。 ## 函数功能分析 1. **调用Lua函数并处理错误**: - 使用`lua_pcall`调用栈顶的Lua函数(参数数量为1,期望返回值数量为1)[^3]。 - 如果调用失败(返回值`iii`非0),则打印错误信息并清理栈(`lua_pop`),返回错误码。 2. **验证返回结果结构**: - 期望返回一个表(`lua_istable`检查),否则报错并返回。 - 检查表中是否包含`success`字段且为真(`lua_toboolean`),否则报错返回。 - 检查表中是否包含`data`字段且为表或`nil`(`lua_istable`或`lua_isnil`),否则报错。 3. **清理栈并返回结果**: - 成功获取`data`字段后,移除原返回表(`lua_remove`),将`data`表留在栈顶作为有效结果。 - 返回0表示成功。 ## 关键点说明 - **LuCI上下文**:根据引用[1][4][5],LuCI是OpenWrt的Web配置框架,允许C/Lua与系统命令交互。此函数可能是LuCI框架中C模块调用Lua函数的接口。 - **错误处理**:使用`lua_pcall`(保护模式调用)捕获Lua运行时错误[^3]。 - **结果格式**:要求Lua函数返回特定结构的表(包含`success`和`data`字段),这是LuCI常见的返回格式[^1][^5]。 ## 函数执行流程 ```mermaid graph TD A[调用lua_call_luci] --> B[打印进程状态] B --> C[lua_pcall调用Lua函数] C --> D{调用成功?} D -- 失败 --> E[打印错误并返回] D -- 成功 --> F{返回值为表?} F ----> G[报错并返回] F ----> H[检查success字段] H -- 非真 --> I[报错并返回] H ----> J[检查data字段] J -- 非表且非nil --> K[报错并返回] J -- 是表或nil --> L[移除原表,保留data] L --> M[返回成功] ``` ## 总结 该函数的作用是: 1. **安全调用**:以保护模式调用Lua函数(参数1个,期望返回1个值)。 2. **结果验证**:严格验证返回值的结构(包含`success`和`data`字段的表)。 3. **错误处理**:对调用失败或返回值格式错误进行详细日志记录。 4. **数据提取**:最终将`data`字段留在栈顶供后续使用,并返回成功状态。 这种模式常见于LuCI框架中C模块与Lua脚本的交互,用于执行系统命令或获取配置数据[^1][^5]。 §§相关问题§§ 1. LuCI框架中C模块与Lua交互的常见模式有哪些? 2. `lua_pcall`与`lua_call`在错误处理机制上有何区别? 3. 如何设计Lua函数以满足`lua_call_luci`的返回值要求?</think>### `lua_call_luci`函数功能分析 该函数是C语言与Lua脚本交互的关键接口,主要用于安全调用LuCI框架中的Lua函数并处理返回结果。核心功能如下: #### 1. **执行Lua函数调用** ```c int iii = lua_pcall(L, 1, 1, 0); ``` - **作用**:以保护模式调用栈顶的Lua函数(LuCI相关函数)[^3] - **参数**: - `1`:函数需要1个参数(已预先压入栈) - `1`:期望返回1个结果 - `0`:不使用错误处理函数 - **关键特性**:捕获Lua运行时错误,防止崩溃[^3] #### 2. **错误处理机制** ```c if (iii != 0) { printf("ERROR: pcall: %s\r\n", lua_tostring(L, -1)); lua_pop(L, 1); return ERROR; } ``` - 检测`lua_pcall`返回值(非0表示错误) - 打印Lua栈顶的错误信息[^3] - 清理栈空间(`lua_pop`)避免状态污染 #### 3. **验证返回数据结构** ```c if (!lua_istable(L, -1)) {...} // 必须返回表 lua_getfield(L, -1, "success"); // 检查success字段 if (!lua_toboolean(L, -1)) {...} // 必须为true lua_getfield(L, -1, "data"); // 提取data字段 ``` - **强制约束**:Lua函数必须返回包含特定字段的表: - `success`:布尔值(操作是否成功) - `data`:表或nil(实际返回数据) - 这是LuCI框架的标准返回格式[^1][^5] #### 4. **结果提取与栈清理** ```c lua_remove(L, -2); // 移除原始返回表 return 0; // 保留data在栈顶 ``` - 清理中间结果(`success`字段等) - 将`data`字段保留在栈顶供调用者使用 - 维持Lua栈平衡(关键内存管理) --- ### 典型应用场景 此函数用于OpenWrt/LuCI系统中实现: 1. **Web配置与系统命令交互** 通过`luci.sys.call`执行Linux命令并获取结果[^1][^5] 2. **安全调用Lua扩展模块** 如`luci.controller.admin.index`等控制器模块[^4] 3. **跨语言错误处理** 将Lua运行时错误转换为C可处理的错误码 > **设计特点**: > - 严格验证返回结构,确保接口可靠性 > - 完善的错误日志(`TDDP_MSG1`调试输出) > - 遵循LuCI框架数据规范[^1][^5] --- ### 执行流程总结 ```mermaid graph TD A[调用lua_call_luci] --> B[lua_pcall执行LuCI函数] B --> C{调用成功?} C -- 失败 --> D[打印错误并清理栈] C -- 成功 --> E[验证返回表结构] E --> F[检查success字段] F --> G[提取data字段] G --> H[清理中间结果] H --> I[返回data到栈顶] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值