快速掌握lua5.3——编写提供给lua使用的c库函数的技巧(代码片段)

VermillionTear VermillionTear     2022-12-13     635

关键词:

Q:如何在C库函数中灵活的操作Lua的”table”?

A:

--[[ void lua_settable(lua_State *L, int index);
     从虚拟栈中"index"处获得"table",栈顶获得"value",
     栈顶下面一个元素获得"key"。相当于在Lua环境中执行"table[key] = value"命令,
     设置"table[key]"的过程有可能触发"metamethods"(__newindex)。
     函数在执行结束后,会弹出"key""value"。]]

--[[ int lua_gettable(lua_State *L, int index);
     从虚拟栈中"index"处获得"table",栈顶获得"key",
     从Lua环境中获取"table[key]"的值。
     获取"table[n]"的过程有可能触发"metamethods"(__index)。
     函数在执行结束后,会弹出"key",并将结果放在虚拟栈上。]]

--[[ void lua_rawset(lua_State *L, int index);
     功能类似于"lua_settable()",但过程中不会触发"metamethods"(__newindex)。]]

--[[ int lua_rawget(lua_State *L, int index);
     功能类似于"lua_gettable()",但过程中不会触发"metamethods"(__index)。]]

--[[ void lua_rawseti(lua_State *L, int index, lua_Integer i);
     从虚拟栈中"index"处获得"table",栈顶获得"value",
     相当于在Lua环境中执行"table[i] = value"命令。
     设置"table[i]"的过程不会触发"metamethods"(__newindex)。
     函数在执行结束后,会弹出"value"。
     其实现相当于:
         lua_pushnumber(L, i);
         lua_insert(L, -2);    -- 将"key"放到"value"的下面。
         lua_rawset(L, index);    -- 当"index"是正索引时。
         lua_rawset(L, index - 1);    -- 当"index"是负索引时(因为"key"入栈了)。]]

--[[ int lua_rawgeti(lua_State *L, int index, lua_Integer n);
     从虚拟栈中"index"处获得"table",从Lua环境中获取"table[n]"的值,
     并将结果放在虚拟栈上。获取"table[n]"的过程不会触发"metamethods"(__index)。
     其实现相当于:
         lua_pushnumber(L, n);
         lua_rawget(L, index);]]

下面来看一个例子,该C库函数接收两个参数,第一个参数为一个”table”,第二个参数为一个函数。其功能将”table”中每一个元素作为调用函数的参数,之后将函数的返回结果替换”table”中对应的元素。
“mylib.c”文件中:

#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

int l_map(lua_State *L)

    int i = 0, n = 0;

    // 因为具有独立的虚拟栈,所以检查"index"为1的位置是否是"table"。
    luaL_checktype(L, 1, LUA_TTABLE);

    // 因为具有独立的虚拟栈,所以检查"index"为2的位置是否是函数。
    luaL_checktype(L, 2, LUA_TFUNCTION);

    /* 获取"index"为1的位置的元素("table")的长度。
     * 对于"table",它在不触发"metamethod"(__index)的情况下,
     * 获取"table"中元素的个数。
     */
    n = lua_rawlen(L, 1);
    for(i = 1; i <= n; ++i)
    
        // 以下三步为C中调用Lua函数的规则。
        lua_pushvalue(L, 2);    // 将Lua函数(f)入栈。
        lua_rawgeti(L, 1, i);    // 将Lua函数的参数(t[i])入栈。
        lua_call(L, 1, 1);    // (以非保护模式)调用Lua函数(f(t[i]))。

        // 将函数的返回值存储到"table"中对应的位置(t[i] = result)。
        lua_rawseti(L, 1, i);
    

    return 0;    // C库函数本身没有返回值。


static const struct luaL_Reg mylib[] = 
    "mymap", l_map,
    NULL, NULL
;

extern int luaopen_mylib(lua_State* L)

    luaL_newlib(L, mylib);

    return 1;

将“mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua

“a.lua”文件中:

local mylib = require "mylib"    -- 加载C库。

local t = 1, 3, 5
local f = function (n)    -- 计算传入参数的平方值。
    return n * n
end

mylib.mymap(t, f)    -- 调用C库中的函数。函数的执行结果会替换"table"中对应的元素。
for k, v in pairs(t) do
    print(k, v)
end
--[[ results: 
1    1
2    9
3    25
]]

Q:如何在C库函数中灵活的操作Lua的字符串?

A:在“快速掌握Lua 5.3 —— Lua与C之间的交互概览”的“如何将元素入栈与出栈”问题中已经介绍了几个操作Lua字符串的函数。
接下来再介绍一个在C库函数中判定参数是否为字符串的函数,以及一个在C库函数中快速连接Lua传递的字符串的函数,

/* 检查虚拟栈中索引"arg"处的元素是否为字符串,如果是则返回字符串,否则返回"NULL"。
 * 函数内部通过"lua_tolstring()"获取结果。
 */
const char *luaL_checkstring(lua_State *L, int arg);

/* 按照Lua中".."的功能,连接从栈顶开始的"n"个值。
 * 函数会将被连接的值出栈,之后将结果入栈。如果"n"0,则将一个空串入栈。
 */
void lua_concat(lua_State *L, int n);

下面来看一个例子。Lua程序提供碎片化的字符串,C库函数负责拼接这些字符串,然后按照Lua程序指定的分隔符重新分割字符串,最后将结果传递给Lua。
“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

static int l_concat(lua_State *L)

    int i = 0, n = 0;

    luaL_checktype(L, 1, LUA_TTABLE);    // 检查第一个参数是否为"table"。

    n = lua_rawlen(L, 1);    // 获取"table"中元素的个数。
    for(i = 1; i <= n; ++i)
    
        lua_rawgeti(L, 1, i);    // 逐一将"table"中的元素入栈。
    
    // 将从栈顶开始的"n"个元素("table"中的元素)连接为一个字符串。
    lua_concat(L, n);

    return 1;    // 返回字符串。


static int l_split(lua_State *L)

    // 检查前两个参数是否为字符串,并获得它们(待分割字符串以及分隔符)。
    const char *s = luaL_checkstring(L, 1);
    const char *sep = luaL_checkstring(L, 2);

    const char *e = NULL;
    int i = 1;

    /* 创建一个Lua的"table"并入栈(用于存储结果)。
     * 由于函数的参数在虚拟栈"index"为1和2的位置,
     * 所以新创建的"table"将在虚拟栈"index"为3的位置。
     */
    lua_newtable(L);

    // 获取每一个分割出来的字符串,并将他们存储上面创建的"table"中。
    while((e = strchr(s, *sep)) != NULL)
    
        /* 将被分割出来的字符串入栈。
         * 由于字符串没有结束符('\\0'),
         * 所以使用"lua_pushlstring()"而非"lua_pushstring()"。
         */
        lua_pushlstring(L, s, (e - s));
        lua_rawseti(L, -2, i++);    // "t[i] = 字符串"。
        s = e + 1;    // 跳过分隔符。
    
    // 将最后一个分隔符后面的字符串入栈。
    lua_pushstring(L, s);
    lua_rawseti(L, -2, i);

    return 1;    // 返回存储结果的"table"。


static const struct luaL_Reg mylib[] = 
    "myconcat", l_concat,
    "mysplit", l_split,
    NULL, NULL
;

extern int luaopen_mylib(lua_State* L)

    luaL_newlib(L, mylib);

    return 1;

将“mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua

“a.lua”文件中:

local mylib = require "mylib"

local t = "hi,,there", ",", "ax", "bd,xx,y", "y"
local s = mylib.myconcat(t)    -- 将"t"中的所有字符串拼接为一个字符串。
print(string.format("\\"%s\\"", s))
print()    -- 换行。
t = mylib.mysplit(s, ',')    -- 使用','分割字符串。
for k, v in pairs(t) do
    print(string.format("\\"%s\\"", v))
end
--[[ results: 
"hi,,there,axbd,xx,yy"

"hi"
""
"there"
"axbd"
"xx"
"yy"
]]

Q:如何在C库函数中使用缓存来操作字符串?

A:当我们连接少量的字符串时,lua_concatlua_pushfstring非常有用。然而,如果我们需要连接大量的字符串(或者字符),这种一个接一个的连接方式的效率将会非常低,正如我们在“快速掌握Lua 5.3 —— 数据结构”,“附加3”中看到的那样。
我们可以使用字符串缓存来解决这个问题,Lua的辅助库为我们提供了luaL_Buffer以及相关的函数,

/* 专门用于在C库函数中缓存零碎的字符串。
 * 有两种使用方式:
 * 1、结果字符串的长度未知:
 * (1) 定义一个"luaL_Buffer"类型的缓存(例如,luaL_Buffer b;)。
 * (2) 调用"luaL_buffinit"初始化缓存(例如,luaL_buffinit(L, &b);)。
 *     初始化之后,"buffer"保留了一份状态"L"的拷贝,
 *     因此当我们调用其他操作"buffer"的函数的时候不需要再传递"L"。
 * (3) 调用"luaL_add*"这一组函数逐一的将字符串放入缓存(例如,luaL_addstring(L, s);)。
 * (4) 最后调用"luaL_pushresult"结束对缓存的使用,
 *     并将结果字符串入栈(例如,luaL_pushresult(&b);)。
 * 2、结果字符串的长度已知:
 * (1) 定义一个"luaL_Buffer"类型的缓存(例如,luaL_Buffer b;)。
 * (2) 调用"luaL_buffinitsize",为缓存预分配以及初始化一个指定大小的缓冲区
 *    (例如,char *p = luaL_buffinitsize(L, &b, sz);)。
 * (3) 逐一的将字符串放入缓冲区(例如,p[0] = 字符串;)。
 * (4) 最后调用"luaL_pushresultsize"将缓冲区中的字符串放入缓存,
 *     结束对缓存的使用,并将结果字符串入栈(例如,luaL_pushresultsize(&b, sz))。
 *     这里的"sz"是指已经复制到缓存中的字符串长度。
 */
typedef struct luaL_Buffer luaL_Buffer;

// 向缓存"B"中添加一个字节"c"。
void luaL_addchar(luaL_Buffer *B, char c);

// 向缓存"B"中添加一个以'\\0'结尾的字符串"s"。
void luaL_addstring(luaL_Buffer *B, const char *s);

// 向缓存"B"中添加一个长度为"l"的字符串"s"。"s"中可以包含'\\0'。
void luaL_addlstring(luaL_Buffer *B, const char *s, size_t l);

// 向缓存"B"中添加一个在缓冲区中长度为"n"的字符串。
void luaL_addsize(luaL_Buffer *B, size_t n);

// 将栈顶的值放入缓存"B",随后将该值出栈。
void luaL_addvalue(luaL_Buffer *B);

/* 为缓存"B"分配一段大小为"sz"的缓冲区。函数返回缓冲区的地址。
 * 你可以向缓冲区中存入字符串,之后必须调用"luaL_addsize"才能真正的将字符串放入缓存。
 */
char *luaL_prepbuffsize(luaL_Buffer *B, size_t sz);

// 等价于"luaL_prepbuffsize",预定义的缓冲区大小为"LUAL_BUFFERSIZE"(在哪儿定义的!!!)。
char *luaL_prepbuffer(luaL_Buffer *B);

/* 初始化缓存"B"。
 * 这个函数不会分配任何空间,缓存必须以一个变量的形式声明。
 */
void luaL_buffinit(lua_State *L, luaL_Buffer *B);

/* 
 * 等价于先调用"luaL_buffinit",再调用"luaL_prepbuffsize"。
 */
char *luaL_buffinitsize(lua_State *L, luaL_Buffer *B, size_t sz);

// 结束对缓存"B"的使用,将结果字符串入栈。
void luaL_pushresult(luaL_Buffer *B);

/* 将于缓存"B"关联的缓冲区中长度为"sz"的字符串放入缓存"B",
 * 之后结束对缓存"B"的使用,将结果字符串入栈。
 * 等价于先调用"luaL_addsize",再调用"luaL_pushresult"。
 */
void luaL_pushresultsize(luaL_Buffer *B, size_t sz);

接下来我们来看一个例子,Lua标准库中string.upper的实现(”lstrlib.c”文件中),其中就用到了luaL_Buffer的第二种使用方式,

static int str_upper(lua_State *L)

    size_t l;
    size_t i;
    luaL_Buffer b;    // 申请一个缓存。
    /* 检查传递的参数是否是字符串,"l"获得字符串的长度。
     *(因为不会修改字符串,所以"l"就是结果字符串的长度)
     */
    const char *s = luaL_checklstring(L, 1, &l);
    char *p = luaL_buffinitsize(L, &b, l);    // 为缓存预分配以及初始化一个指定大小的缓冲区。
    for(i = 0; i < l; i++)
        // 将字符强转为"unsigned char"型,然后转为大写,放入缓冲区。
        p[i] = toupper(uchar(s[i]));
    // 将缓冲区中的字符串放入缓存,结束对缓存的使用,并将结果字符串入栈。
    luaL_pushresultsize(&b, l);

    return 1;    // 返回结果字符串。

附加:

1、当Lua调用C函数时,Lua为其调用的每一个C函数提供独立的虚拟栈。
2、安全模式:遇到错误不报错,而是返回错误码和错误信息。
非安全模式:遇到错误直接报错。
3、当C函数接收到Lua传递的字符串参数时,有两个规则必须要遵守:
(1) 不要将字符串参数出栈(函数返回后,Lua会负责将函数的参数以及函数本身出栈,之后将函数的返回值入栈)。
(2) 不要修改该字符串参数。
而当C程序创建的字符串要向Lua传递时,需要注意的东西就会更多:
(1) 字符串缓存空间的分配以及释放。
(2) 字符串缓存溢出。
等等。
4、Lua辅助库所提供的缓存在被使用的过程中会在虚拟栈上存放一些中间结果,因此你不能假设栈顶一直保持在你使用缓存之前时的位置。此外,虽然你可以在使用缓存的过程中继续使用虚拟栈,但是在你每次访问缓存之前,这些入栈和出栈的操作一定要保持平衡(两种操作的数量一样多)。
5、当你需要将Lua传递的字符串放入缓存时,需要使用特殊的函数luaL_addvalue
你不能在将字符串放入缓存之前将其出栈,因为一旦你从栈中将Lua传递的字符串移出,你将再也不能使用该字符串。同时,你也不能在将字符串出栈之前将其放入缓存中,例如如下的代码,

luaL_addstring(&b, lua_tostring(L, 1));   /* BAD CODE!!! */

因为这样做会导致缓存异常(中间结果之间加入了一个你入栈的值)。
上述这两种情况正好构成了一对儿矛盾,所以Lua辅助库提供了luaL_addvalue帮助我们完成这项工作。

快速掌握lua5.3——字符串库

Q:什么情况下”pattern”会匹配空串?A:要小心的使用*和-,因为它们可以匹配零次。--如果你打算用"%a*"匹配单词,你会发现到处都是单词。print(string.find(";$%**#$hello13","%a*"))-->10print(string.find(";$%**#$hello13","%a*",6))-->65--使用"%a+... 查看详情

快速掌握lua5.3——userdata(代码片段)

Q:什么是”userdata”?A:”userdata”分为两类,”fulluserdata”和”lightuserdata”。Lua使用他们来表示C中一些特殊的类型。前面的章节中,我们看到了如何通过C编写新的函数来扩展Lua;使用”userdata”,... 查看详情

快速掌握lua5.3——userdata(代码片段)

Q:如何使用”userdata”的”metamethods”?A:我们继续来修改上一节中的例子,这次我们的目标是使用面向对象的方式调用”userdata”的方法。这个目标既可以在Lua中实现,也可以在C库中实现,我们先来看一... 查看详情

快速掌握lua5.3——调试库

Q:如何调试”Closure”的”upvalue”信息?A:--[[debug.getupvalue(f,up)返回函数("Closure")"f"的第"up"个"upvalue"的名字和值。Lua按照"upvalues"在匿名函数中出现的顺序对其编 查看详情

快速掌握lua5.3——从lua中调用c函数(代码片段)

Q:Lua调用C函数的两种方式?A:1、程序主体在C中运行,C函数注册到Lua中。C调用Lua,Lua调用C注册的函数,C得到函数的执行结果。2、程序主体在Lua中运行,C函数作为库函数供Lua使用。第一种方式看起... 查看详情

快速掌握lua5.3——调试库(代码片段)

Q:什么是活动函数?A:程序中被调用但还未执行完成的函数。functiong()--[[此时函数"g"被调用但还未执行完成,是活动函数。所以这里获取的是函数"g"的信息。"debug.getinfo(2)"获取的才是函数"f&#... 查看详情

快速掌握lua5.3——资源管理(代码片段)

Q:Lua的”finalizer”?A:在我们之前看到的使用”userdata”的例子中,我们只关心如何创建并使用”userdata”,从未关心何时以及如何释放我们创建的”userdata”,因为这些事都由Lua的垃圾回收器帮我们处理... 查看详情

高速掌握lua5.3——i/o库

Q:什么是”SimpleModel”?A:全部的文件操作都基于一个默认的输入文件和一个默认的输出文件。这就意味着同一时间对于输入和输出来说,仅仅可操作一个文件(默认的文件)。默认的输入文件初始化是stdin,默认的输出文件初... 查看详情

高速掌握lua5.3——字符串库

Q:模式匹配字符串的相关函数?A:--[[string.find(s,pattern[,init[,plain]])在字符串"s"中查找第一个与匹配模式"pattern"相匹配的子串,函数返回子串的開始位置和终止位置。假设未找到返回"nil"。假设在"pattern"中定义了捕获,捕获物也会... 查看详情

高速掌握lua5.3——扩展你的程序

Q:怎样在C中将Lua作为配置文件语言使用?A:“config.lua”文件里:--windowsizewidth=200height=300“main.c”文件里:#include<stdarg.h>#include<stdio.h>#include<stdlib.h>#include<lua.h>#include<lauxlib.h> 查看详情

visualstudio2015编译lua5.3.4遇到的坑

...成DLL或者LIB(动态/静态库),如果是应用程序那必须要提供main函数的主入口。网上提供的方法,比如改系统-子系统,各种方法都试过了,始终是报错。 冷静了一下,觉得是新建空项目那里有问题。然后就改创建一个win32的... 查看详情

Lua SHA256 库

...【问题描述】:我是Lua脚本的新手。尝试在我的Lua脚本中使用SHA256哈希机制。Lua5.3.5版本是否具有SHA256作为内置库,如字符串、数学等。或者我们需要使用luarocksinstallsha2安装吗?我在我的MAC中尝试了luarocksinstallsha2,但出现下面... 查看详情

RNN 代码的 luajit/lua5.1/lua5.2/lua5.3 内存问题

】RNN代码的luajit/lua5.1/lua5.2/lua5.3内存问题【英文标题】:luajit/lua5.1/lua5.2/lua5.3memoryissueforRNNcode【发布时间】:2016-11-2603:51:39【问题描述】:我一直在运行一段代码,train.lua,在这里找到:https://github.com/karpathy/char-rnn/blob/master/train... 查看详情

[原]win10下编译lua5.3.4

1、下载lua源码http://www.lua.org/ftp/2、打开vs2012工具命令提示3、cd到lua源码的src目录4、依次执行以下代码cl/MD/O2/c/DLUA_BUILD_AS_DLL*.crenlua.objlua.orenluac.objluac.olink/DLL/IMPLIB:lua5.3.0.lib/OUT:lua5.3.0.dll*.objlink/OUT:lua. 查看详情

lua5.3--sol2.0用户指南(代码片段)

SOL2.2是一个快速、简单的C++与LUA的绑定器。如果确定要在你的程序里面同时运行Lua和C++,SOL是一个高性能的绑定器,是一个API使用方便的GO-TO框架。 简单看一下特点:这个链接到(未链接)大部分API。您也可以直接浏览API或... 查看详情

lua快速入门

Lua是一个小巧的脚本语言,可以直接使用解析器进行解析。设计目的是为了嵌入应用程序中,从而为应用提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的... 查看详情

msvs命令行编译lua5.3.4

msvs命令行编译lua5.3.4 vslua.bat@echooffmdbinmdlibmdincludecdsrccl/c/nologo/W3/O2/Ob1/Oi/Gs/MD/D_CRT_SECURE_NO_DEPRECATEl*.crenlua.objlua.orenluac.objluac.olib/OUT:lua53.lib*.objcopylua53.lib..liblu 查看详情

lua中有快速清空table的函数或者方法没

  可以按tablename=nil或者tablename=清除。  Lua是一个小巧的脚本语言。是巴西里约热内卢天主教大学(PontificalCatholicUniversityofRiodeJaneiro)里的一个研究小组,由RobertoIerusalimschy、WaldemarCeles和LuizHenriquedeFigueiredo所组成并于1993年开... 查看详情