LUA调用C库和闭包(OpenWrt中UCI-LUA的学习)

LUA调用C库和闭包(OpenWrt中UCI-LUA的学习)

不是为了学习UCI, 而是学习UCI中C代码和LUA的交互使用, 可以不用了解UCI具体是干什么的
uci.c的代码, 与LUA交互相关的代码

说明

  介绍一下再LUA中以面对对象的方式调用C代码, 以及LUA-C中的闭包概念; LUA调用C代码是C编译为.so动态库, 注册函数供LUA调用.

注册函数

  LUA5.1中提供了module的方式来定义模块(LUA中), 不过应当别抵制(抵制module原因); 这里是LUA代码被LUA代码调用.
  对于C代码来说, 主要是luaL_registerluaL_setfuncs函数, 前者已经被废弃, 因为会产生一个全局变量, 后者是往一个表中注册函数.

UCI介绍

不是为了介绍UCI, 仅仅是介绍一些UCI怎么调用这些C中的函数

local uci = require "uci".cursor()
local test = uci:get("system","main","test")

cursor,get都是C代码中提供的函数, 我们可以看到是以一种类似于面对对象的方式直接调用C中的函数的

lua中的面对对象

lua实质是不存在面对对象的说法的, 实质以metatable的方式为table(或userdata)提供面对对象的方法的(:方式调用)

  当对对象操作的成员不存在时, 就会去元表中查询, __newindex 元方法用来对表更新,__index则用来对表访问(这两个成员可以是函数或者是表), 这里不做特别详细介绍; 如果我们要实现LUA中以面对对象的方式调用C函数, 也是要创建类似环境. C中的结构体在LUA中就是一个userdata

C库注册函数

函数介绍

int luaL_newmetatable (lua_State *L, const char *tname);
lua_newtable类似, 也是用来生成一个指定名字的表, 但是只有前者能够作为userdatametatable

void lua_setfield (lua_State *L, int index, const char *k);
将堆栈L中的第index值, 设置到栈顶的tablek成员

void *lua_newuserdata (lua_State *L, size_t size);
根据指定的大小, 生成一块地址

void luaL_getmetatable (lua_State *L, const char *tname);
获取指定名字的metatable

void lua_settable (lua_State *L, int index);
t[k] = v, t即为有效索引为index的值, 栈顶作为v, 栈顶的下一个作为index, 最后弹出栈

思路

   为C对象添加一个壳,lua 的 userdata 中仅仅保存 C 对象指针。然后给 userdata 设置 gc 元方法,在被回收时,正确调用 C 对象的销毁函数。 所有的函数均放在改userdata的元表中, 调用时去元表中查找(可能还有其他方式, 但我感觉这已经挺好用的了)

生成对象

//1045
int
luaopen_uci(lua_State *L)
{
    /* create metatable */
    luaL_newmetatable(L, METANAME);

    /* metatable.__index = metatable */
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");

    /* fill metatable */
    luaL_setfuncs(L, uci, 0);
    lua_pop(L, 1);

    /* create module */
    lua_newtable(L);
    lua_pushvalue(L, -1);
    luaL_setfuncs(L, uci, 0);
    lua_setglobal(L, MODNAME);

    return 1;
}

//986
static int
uci_lua_cursor(lua_State *L)
{
    struct uci_context **u;
    int argc = lua_gettop(L);

    u = lua_newuserdata(L, sizeof(struct uci_context *));
    luaL_getmetatable(L, METANAME);
    lua_setmetatable(L, -2);
    ......

以上包括初始化环境, 以及生成一个对象的代码, 主要包括如下几步

  • 1 create metatable 生成一个元表
  • 2 metatable.__index = metatable 将这个元表的元表指向自身, 这样userdata寻找方法时就会在这里寻找
  • 3 fill metatable 使用luaL_setfuncs将函数注册到该表中
  • 4 create module 对于这一段, 个人认为对于我们面对对象的调用方式可能用处不大了
  • 5 lua_newuserdata 创建一个指针大小的空间, 用于存放C对象的指针, 我们可以在返回值u中赋值存放结构体指针
  • 6 lua_setmetatable 将我们之前创建的元表设置为该结构体的元表
  • 7 最后我们创建的该userdata就可以以面对对象的方式调用我们最开始注册的那些函数了

注意:{ "__gc", uci_lua_gc },该函数在LUA回收该资源时调用的, 如果有动态创建资源那么必须在这里回收(LUA会自动回收资源)

ps: 调用函数时可以用luaL_checkudata(L, 1, METANAME)进行校验是否是正确的userdata, 调用函数时(比如之前的get)第一个参数就是该userdata

闭包

在看openwrt的uci的代码时看到为兼容lua5.1实现的luaL_setfuncs(注册一个函数到lua中)函数, 涉及到了闭包, 所以学习一下

static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup)
{
    luaL_checkstack(L, nup + 1, "too many upvalues");
    /* 调整栈的大小 */
    for (; l->name != NULL; l++)
    {  
        int i;
        lua_pushstring(L, l->name);
        /*设置函数名字*/
        for (i = 0; i < nup; i++)
        {           
            lua_pushvalue(L, -(nup + 1));
        }
        lua_pushcclosure(L, l->func, nup);
        /*生成闭包*/
        lua_settable(L, -(nup + 3));
        /*将闭包放入table中*/
    }
    lua_pop(L, nup);  
}

nup就是l中所有函数的闭包个数, 这些upvalues都必须在调用改函数之前push到栈上

闭包学习

在这里闭包是一个双向的概念, 既对C又对lua
例子
环境: lua5.1

贴上代码(注意:代码有问题):

//C代码

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <dlfcn.h>
#include <math.h>
 
static int mytest(lua_State *L) {
  //获取上值
  int upv = (int)lua_tonumber(L, lua_upvalueindex(1));
  printf("%d\n", upv);
  upv += 5;
  lua_pushinteger(L, upv);
  lua_replace(L, lua_upvalueindex(1));
 
  //获取一般参数
  printf("%d\n", (int)lua_tonumber(L,1));
 
  return 0;
}
 
int main(void) {
  lua_State *L = luaL_newstate();
  luaL_openlibs(L);
 
  //设置Cclosure函数的上值
  lua_pushinteger(L,10);
  lua_pushinteger(L,11);
  lua_pushcclosure(L,mytest,2);
  lua_setglobal(L,"upvalue_test");
  luaL_dofile(L, "./luatest.lua");
 
  //获取fclosure上值的名称(临时值, 不带env)
  lua_getglobal(L, "l_counter");
  const char *name = lua_getupvalue(L, -1, 1);
  printf("%s\n", name);
 
  //设置fclosure上值
  lua_getglobal(L, "l_counter");
  lua_pushinteger(L,1000);
  name = lua_setupvalue(L, -2, 1);
  printf("%s\n", name);
 
  lua_getglobal(L,"ltest");
  lua_pcall(L,0,0,0);
  lua_getglobal(L,"ltest");
  lua_pcall(L,0,0,0);
 
  //获取fclosure的上值(带env)
  lua_getglobal(L, "g_counter");
  lua_getupvalue(L, -1, 1);
   
  //通过settable重新设置env中对应的值
  lua_pushstring(L, "gloval_upvalue");
  lua_pushinteger(L,10000);
  lua_settable(L,-3);
   
  lua_pushstring(L, "gloval_upvalue1");
  lua_pushinteger(L,20000);
  lua_settable(L,-3);
   
  lua_getglobal(L,"gtest");
  lua_pcall(L,0,0,0);
  lua_close(L);
  return 0;
}

//LUA代码
gloval_upvalue = 10
gloval_upvalue1 = 20
local local_upvalue = 100
 
function l_counter()
   return function ()
      local_upvalue = local_upvalue + 1
      return local_upvalue
   end
end
 
function g_counter()
   return function ()
      gloval_upvalue = gloval_upvalue + 1
      return gloval_upvalue,gloval_upvalue1
   end
end
 
g_testf = g_counter()
l_testf = l_counter()
 
function gtest()
  print(g_testf())
end
 
 
function ltest()
  print(l_testf())
end
 
upvalue_test(1,2,3)
upvalue_test(4,5,6)

接下来针对代码说明

upvalue概念

  • 不是函数的局部变量,即在函数中没有用local修饰
  • 不是全局变量,即他在函数外部还是要用local修饰

对于C方面来说,

lua_pushinteger(L,10);
lua_pushinteger(L,11);
lua_pushcclosure(L,mytest,2);
lua_setglobal(L,"upvalue_test");

static int mytest(lua_State *L) {
  //获取上值
  int upv = (int)lua_tonumber(L, lua_upvalueindex(1));
  printf("%d\n", upv);
  upv += 5;
  lua_pushinteger(L, upv);
  lua_replace(L, lua_upvalueindex(1));
 
  //获取一般参数
  printf("%d\n", (int)lua_tonumber(L,1));
 
  return 0;
}

upvalue_test(1,2,3)
upvalue_test(4,5,6)

lua_pushinteger函数将两个upvalue放入堆栈中
lua_pushcclosure放入函数生成闭包, mytest是函数, 2表示upvalue的个数, 闭包相关2个upvalue会被弹出
这就生成了一个闭包, 这个闭包被设置为全局变量, 在lua中可以直接调用

使用(被lua调用)

lua_tonumber(L, lua_upvalueindex(1));
lua_pushinteger(L, upv);
lua_replace(L, lua_upvalueindex(1));

lua_upvalueindex获取第一个upvalue的索引(我进行过打印, 其值为10003, 感觉没有研究的必要)
lua_replace可以用来修改这个upvalue
lua_tonumber(L, lua_upvalueindex(1));: 获取第一个upvalue的值
lua_replace(L, lua_upvalueindex(1)): 修改第一个upvalue的值

很简单的使用方法

对于lua来说(C中操作lua)

lua中的闭包想必更多人用过吧

--lua
local local_upvalue = 100
 
function l_counter()
   return function ()
      local_upvalue = local_upvalue + 1
      return local_upvalue
   end
end

这里就是一个简单的闭包, local_upvalue就是函数的upvalue

//获取fclosure上值的名称(临时值, 不带env)
lua_getglobal(L, "l_counter");
const char *name = lua_getupvalue(L, -1, 1);
printf("%s\n", name);
 
//设置fclosure上值
lua_getglobal(L, "l_counter");
lua_pushinteger(L,1000);
name = lua_setupvalue(L, -2, 1);
printf("%s\n", name);

这里就是在C语言中获取和设置该函数的upvalue
name = lua_getupvalue(L, -1, 1): 返回栈中坐标-1的函数的第一个upvalue的名字, 将值放入栈顶
lua_setupvalue(L, -2, 1): 将栈顶的参数设置为为坐标为-2的函数的第1个upvalue
对于upvalue来说, 按照声明的顺序即为索引

ps: 返回值是变量的名字, 如果没有(索引超出), 则返回NULL

全局upvalue

我认为这个概念是错误的

--lua
function g_counter()
   return function ()
      gloval_upvalue = gloval_upvalue + 1
      return gloval_upvalue,gloval_upvalue1
   end
end

这是作者的说法:
如果你的fclosure中没有使用全局变量,那么其上值索引应该是按照local变量的声明顺序来的,但是一旦使用了全局变量,第一个上值必定是_ENV环境,也就是说你可以通过重新设置_ENV环境,修改全局的上值,这也是修改环境变量的一种方法。

在LUA5.1的测试中

//获取fclosure的上值(带env)
lua_getglobal(L, "g_counter");
lua_getupvalue(L, -1, 1);
   
//通过settable重新设置env中对应的值
lua_pushstring(L, "gloval_upvalue");
lua_pushinteger(L,10000);
lua_settable(L,-3);

但是

  • 1 从结果来看, 即尝试获取g_counter的上值, 返回值是NULL

  • 2 后续设置gloval_upvalue时报错: PANIC: unprotected error in call to Lua API (attempt to index a function value)

  • 3 对于全局变量来说应该不存在upvalue这个概念

  • 当执行了luaL_dofile(L, "./luatest.lua")这一句后, 堆栈中已经获取了全局的环境

lua_pushinteger(L,20000);
lua_setglobal(L, "gloval_upvalue");

通过了这种方式就已经可以了设置该变量了

以上是我在我自己的环境中实验得到的, 可能因为环境原因导致的结果不一致
感觉离开学校后只能从网上找资料, 很多时候很难获得很权威的结果, 只能进行自己的实验, 但是又很难深入, 甚至还可能走入误区

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342