再说C模块的编写(1)
【前言】
在《Lua“控制”C》中对Lua调用C函数做了初步的学习,而这篇才是重中之重,这篇文章会重点的总结C模块编写过程中遇到的一些问题,比如数组操作、字符串操作和C函数的状态保存等问题。现在就开始吧。
【数组操作】
在Lua中应该不能叫数组,而是一种table的东西;而在C语言中,没有table这种东西,只有数组。Lua中的table可以使关联的,也就是key=>value键值对,而C中,数组不是关联的,下标是从0开始的。当然了,Lua中的数组表示,只是table的一个子集,就是因为这种关系,就有了C数组和Lua table的交互关系了。
比如lua_settable和lua_gettable这种操作table的API(其实之前我一直用的都是lua_setfield和lua_getfield),也可以操作数组。然而,API为数组操作提供了专门的函数,出于以下两个原因:
- 性能;我们一般使用C语言来扩展Lua,都是用来做一些Lua难以做到,而C却非常容易做到的事情,比如一些追求效率的算法;如果提高了访问数组的效率,那就能提高整个算法的性能了;
- 便利;整数key是非常常用的,所以提供专门的API也会非常便利的。
API为数组操作提供了两个函数:
void lua_rawgeti(lua_State *L, int index, int key);
void lua_rawseti(lua_State *L, int index, int key);
lua_rawgeti和lua_rawseti的参数中涉及到两个索引,index表示table在栈中的位置,key表示元素在table中的元素。这两个函数都是原始操作,比涉及元表的table访问更快。通常,作为数组使用的table很少会用到元表。
下面就来一个实例,看看如何使用上面的两个API函数,不知道你会不会PHP,在PHP中,有一个array_walk函数,这个函数允许用户定义一个函数,然后对数组中的每个函数都应用这个函数。我现在就来实现这个功能。把重点代码贴上来:
static int array_walk(lua_State *L)
{
// 和写别的函数一行,先检查参数的合法性
// 第一个参数必须是一个table
luaL_checktype(L, , LUA_TTABLE); // 第二个参数必须是一个用户定义的函数
luaL_checktype(L, , LUA_TFUNCTION); // 获取table的大小
int iLen = lua_objlen(L, ); for (int i = ; i <= iLen; ++i)
{
// 将用户定义的函数压入栈
lua_pushvalue(L, ); // 将参数table的所以i对应的值压入栈
lua_rawgeti(L, , i); // 调用用户定义的函数
lua_call(L, , );
lua_rawseti(L, , i);
}
// 没有返回值压入栈中
return ;
}
代码比较简单,不多说,哪里不懂的地方,可以留言。对于代码中出现的luaL_checktype和lua_call函数,这里说一下。luaL_checktype用来检查给定的参数符合特定的类型,从而防止由于参数类型错误而引起的后续错误;如果参数不正确,这个函数就会引发一个错误。
lua_call运行在无保护的模式下,这个是它和lua_pcall最大的区别,所以它在发生错误时,会传播错误,而不是简单的返回一个错误代码。在我们的实际编程开发中,在一个应用程序中编写主函数时,不应该使用lua_call,因为这样需要捕获所有的错误;而编写C函数时,通常可以用lua_call,当错误发生时,就应该让错误显示出来。
上面只是贴出了关键代码,可以点击这里下载完整工程。
【字符串操作】
实际开发中,我们都是在和各种字符串打交道,现在我们就来完成这个功能,Lua传进一个字符串到C模块中,C模块进行字符串处理。
当一个C函数从Lua接收到一个字符串参数时,必须遵守两条规则:
- 不要在访问字符串时,从栈中弹出它;
- 不要修改字符串。
当一个C函数需要创建一个字符串返回给Lua时,C代码还必须处理字符串缓冲的分配和释放等问题。Lua API也提供了一些函数来帮助完成这些任务。
标准API为两种常用的字符串操作提供了支持:提取子串和字符串连接。lua_pushlstring支持提取子串,它接受一个额外的字符串长度参数,这就好比我们在压入栈时,对字符串进行了一个截取操作。下面我先来完成一个简单的功能,根据指定的切割符号来切割字符串,将子串保存在一个table中,然后向Lua返回这个table。来吧!!!
static int split(lua_State *L)
{
// 传进来两个参数,先检查参数的合法性
const char *pSrc = luaL_checkstring(L, );
const char *pSep = luaL_checkstring(L, );
lua_newtable(L);
int index = ;
char *pLocation = NULL;
while ((pLocation = strchr(pSrc, *pSep)) != NULL)
{
// 压入字符串
lua_pushlstring(L, pSrc, pLocation - pSrc); // 设置结果表
lua_rawseti(L, -, index++); // 跳过分隔符
pSrc = pLocation + ;
} // 把最后一部分压入table中
// eg.abc,def,cg
// 现在把cg放到结果表中
lua_pushstring(L, pSrc);
lua_rawseti(L, -, index);
return ;
}
把重点代码贴上来了。无需多解释,慢慢看,能看懂的。Lua测试代码如下:
require "split" local str = "abc,de,fg"
local strsep = ","
local tbRet = MySplit.split(str, strsep)
for _, v in pairs(tbRet) do
print(v)
end
单击这里下载完整项目代码。
为了连接字符串,Lua API提供了一个叫lua_concat的函数。它类似于Lua中的“..”操作符。不过,它可以同时连接多个字符串,调用lua_concat(L, n)连接(并弹出)栈顶的n个值,然后压入结果。此外,这个函数会将数字转换为字符串,并在需要的时候调用元方法(__tostring)。还有另外一个有用的函数是lua_pushfstring,这个函数和C中的sprintf有点类似,它们都会根据一个格式字符串和一些额外的参数来创建一个新字符串;但是与sprintf不同的是,无需提供这个新字符串的缓冲。Lua会动态的创建一个足够大的缓冲区来存放字符串,确保不会有缓冲溢出的问题。这个函数会将结果字符串压入栈中,并返回一个指向它的指针,当前这个函数接受的指示符只有以下几种:
- %%,表示字符%;
- %s,表示字符串;
- %d,表示整数;
- %f,表示Lua中的数字, 即双精度浮点数;
- %c,接受一个整数,并将它格式化为一个字符,和string.char功能类似。
除了上述列出的指示符以外,它不接受任何其它选项。
如果只是连接一些字符串的话,这样简单的工作,lua_concat和lua_pushfstring就能够很简单的完成;但是,如果要连接很多字符串的话,为了提高效率,我们可以使用辅助库,也就是lauxlib.h中定义的API函数来完成这项工作。辅助库提供了什么呢?它提供了一种缓冲机制,包含了两个层面的缓冲:
- 在本地缓冲区中收集较小的字符串,并在本地缓冲区满了以后,将结果传递给Lua(通过lua_pushlstring);
- 使用lua_concat或其它算法来连接多次缓冲区填满后的结果。
为了更好的描述辅助库的缓冲机制,来看一段string.upper的源代码,可以去Lua源代码中的lstrlib.c文中查看。
static int str_upper (lua_State *L) {
size_t l;
size_t i;
luaL_Buffer b;
const char *s = luaL_checklstring(L, , &l);
luaL_buffinit(L, &b);
for (i=; i<l; i++)
luaL_addchar(&b, toupper(uchar(s[i])));
luaL_pushresult(&b);
return ;
}
不要惊讶,Lua的代码你可以随心所欲的阅读,伟大的开源,分享的力量。使用缓冲区分为以下几步:
- 声明一个luaL_Buffer变量;
- 使用luaL_buffinit来初始化它;
- 调用luaL_add*系列函数向缓冲区添加字符或字符串;
- 调用luaL_pushresult更新缓冲区,将最终的结果字符串留在栈顶。
在调用luaL_buffinit初始化以后,这个变量中就会保留一份状态L的副本,所以在后续调用luaL_add*系列函数时,就不用传递lua_State参数了。
通过使用这些函数,就可以使用缓冲机制,我们也不用再去关心缓冲的分配、溢出等细节了。另外,这种连接算法也非常高效。用str_upper函数处理大型的字符串也不会有什么问题。
再说C模块的编写(1)的更多相关文章
- 再说C模块的编写(2)
[前言] 在<再说C模块的编写(1)>中主要总结了Lua调用C函数时,对数组和字符串的操作,而这篇文章将重点总结如何在C函数中保存状态. 什么叫做在C函数中保存状态?比如你现在使用Lua调 ...
- Layui 是一款采用自身模块规范编写的国产前端UI框架(5600个Star)
采用自身模块规范编写的前端UI框架,遵循原生HTML/CSS/JS的书写形式,极低门槛,拿来即用. http://www.layui.com Layui 是一款采用自身模块规范编写的国产前端UI框架, ...
- Python turtle 模块可以编写游戏,是真的吗?
1. 前言 turtle (小海龟) 是 Python 内置的一个绘图模块,其实它不仅可以用来绘图,还可以制作简单的小游戏,甚至可以当成简易的 GUI 模块,编写简单的 GUI 程序. 本文使用 tu ...
- 【译】GNU Radio How to write a block 【如何开发用户模块及编写功能块】
本文讲解如何在GNU Radio中添加用户开发的信号处理模块,译文如有不当之处可参考原文地址:http://gnuradio.microembedded.com/outoftreemodules Ou ...
- 开启Android Apk调试与备份选项的Xposed模块的编写
本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80963610 在进行Android应用程序逆向分析的时候,经常需要进行Andro ...
- fl2440hello world模块驱动编写
许多语言,例如C,C++,JAVA等等都是从hello world开始的,因此我们的驱动程序的开发也要从hello world入手. 首先来看下我们的代码: /******************** ...
- idea中模块累积编写
idea中新建Empty Project名为myproject,新建模块mymodel1 要想复制该模块,再在该模块的基础上开发怎么弄? 选中该模块右键Copy,在Project空白区域右键Paste ...
- 【php增删改查实例】第八节 - 部门管理模块(编写PHP程序)
首先,在同级目录新建一个query.php文件: 接着,去刷新页面,打开F12,NetWork,看看当前的请求能不能走到对应的php文件? 这就说明datagrid确实能够访问到query.php 只 ...
- 【转】PowerShell入门(十二):编写PowerShell管理单元和二进制模块
转至:http://www.cnblogs.com/ceachy/archive/2013/03/13/PowerShell_SnapIn.html PowerShell一开始就提出利用管理单元来实现 ...
随机推荐
- Spring Cloud 入门教程(七): 熔断机制 -- 断路器
对断路器模式不太清楚的话,可以参看另一篇博文:断路器(Curcuit Breaker)模式,下面直接介绍Spring Cloud的断路器如何使用. SpringCloud Netflix实现了断路器库 ...
- 再看ExpressionTree,Emit,反射创建对象性能对比
[前言] 前几日心血来潮想研究着做一个Spring框架,自然地就涉及到了Ioc容器对象创建的问题,研究怎么高性能地创建一个对象.第一联想到了Emit,兴致冲冲写了个Emit创建对象的工厂.在做性能测试 ...
- JS-数组操作3
1. 找出数组 arr 中重复出现过的元素 function duplicates(arr) { var result = []; var count = []; for (var i=0;i< ...
- 更改电脑名称后, Cnario无法播放画面和声音, 开机后停留在桌面, Cnario Player软件界面的停止按钮为蓝色可选状态
症状描述 Cnario Player正常工作期间, 更改了电脑的Windows系统计算机名称(不是登录Windows的用户名), 重启后, 新计算机名生效. 此时Cnario自动启动, 但没有进入播放 ...
- #Leetcode# 997. Find the Town Judge
https://leetcode.com/problems/find-the-town-judge/ In a town, there are N people labelled from 1 to ...
- mybatis乱码
单个字段 <property name="url" value="jdbc:mysql://127.0.0.1:3306/db?characterEncoding= ...
- 如何伪造IP(转)
要明白伪装IP的原理,首先要回顾一下TCP的三次握手. 总所周知在链接初始化的阶段, 需要一次三次握手来建立链接, 之后客户端和服务端会依据初始的这个IP地址来通信. 从这个角度上来说, 想真正的伪装 ...
- JS/JQuery 设置input等标签设置和取消只读属性
<input type="text" id="HouseName" value="" align="left"/& ...
- 如何查看kernel社区的变更历史
kernel社区稳定版本的地址为: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ 如果我们想查找某一个文件,比如 ...
- Python——递归函数
1.定义:在自己的函数,调用自己 2.递归的最大内存不能超过997层 import sys sys.setrecursionlimit(1000000) 可以达到电脑理论的最大次 import s ...