再说C模块的编写(2)
【前言】
在《再说C模块的编写(1)》中主要总结了Lua调用C函数时,对数组和字符串的操作,而这篇文章将重点总结如何在C函数中保存状态。
什么叫做在C函数中保存状态?比如你现在使用Lua调用了C函数Func1,但是Func1中有一些数据在调用完以后保存下来,供以后使用。而这些数据就是所谓的状态,也就是我们需要保存的东东。根据目前总结的所有内容,是无法做到在C函数中保存状态的。有人就会说了,Lua调用C时,把所有的需要保存的状态都返回到Lua中,当调用下一个函数时,将需要的状态当做参数再传进去,不错,是一个办法,但是很麻烦。而这里我将总结3种比较方便,但是稍微有点难理解的方法在C函数中保存状态。
- 方法一:注册表;
- 方法二:环境;
- 方法三:upvalue。
注册表是一个全局的table,它只能被C代码访问。通常,可以用它来保存那种需要在几个模块中共享的数据;但是,如果需要保存一个模块的私有数据,那么应该使用环境,与Lua函数一样,每个C函数都有自己的环境table,通常情况下,一个模块内的所有函数共享同一个环境table,由此它们可以共享数据。最后,C函数也可以拥有upvalue,upvalue是一种与特定函数相关联的Lua值。现在我就逐一开始分析,总结。Let’s go.
【注册表】
注册表总是位于一个“伪索引”上,这个索引值由LUA_REGISTRYINDEX定义。伪索引就像是一个栈中的索引,但它所关联的值不在栈中;所完这句话,你想到了什么?C++中,使用new开辟空间,这个空间是在堆上开辟的,而指向这个堆的变量却是存放在栈上的。伪索引和这个意思差不多。Lua API中的大多数函数都能接受伪索引,但像lua_remove和lua_insert这种操作栈本身的函数却只能使用普通索引。
注册表是一个普通的Lua table,可以使用任何Lua值(nil除外)来索引它。例如,要获取注册表中key为“JellyThink”的值,可以这么做:
lua_getfield(L, LUA_REGISTRYINDEX, "JellyThink");
现在就出现了一个很棘手的问题,由于所有的C模块共享同一个注册表,为了避免使用冲突,必须谨慎的选择key的值,为了保证key的唯一性,避免冲突,建议使用UUID作为key值。
在注册表中,不要使用数字类型的key,因为这种key是被“引用系统”所保留的。“引用系统”是由辅助库中的一系列函数组成的,它可以在向一个table存储value时,忽略如何创建一个唯一的key。例如以下调用:
int r = luaL_ref(L, LUA_REGISTRYINDEX);
会从栈中弹出一个值,然后用一个新分配的整数key来将这个值保存到注册表中,最后返回这个整数key。这个key被称为“引用”。
你问我什么情况下使用注册表?注册表是一个全局的table,它可以在多个C模块中共享数据,在一个C模块中注册了一个Lua值的引用,在其它C模块照样可以使用这个引用。光说不练,雁过还的拔毛呢,下面就来一个实例,做以下试验:
- 准备CModule和CModule2两个C模块;
- Lua调用CModule,传入一个table,在CModule中注册,然后在CModule2中打印这个table中的值;
- Lua调用CModule,传入一个function,在CModule中注册,然后在CModule2中调用这个Lua function;
- Lua调用CModule,传入一个string值,在CModule中注册,然后在CModule2中打印这个string的值。
引用系统将nil视为一种特殊情况。为一个nil值调用luaL_ref时,并不会创建新引用,而是返回一个常量引用LUA_REFNIL。对LUA_REFNIL使用lua_rawgeti时,会向栈中压入一个nil。下面就拿注册一个function为例子,简单分析以下,主要还是要理解代码:
static int RegisterFunc(lua_State *L)
{
// 第一个参数是function,检查参数
luaL_checktype(L, , LUA_TFUNCTION); // 复制一份到栈顶
lua_pushvalue(L, );
lua_pushvalue(L, ); // 将这个function注册到注册表中
int iRef = luaL_ref(L, LUA_REGISTRYINDEX); // 再用key来建立索引,key是JellyThink_Function
lua_setfield(L, LUA_REGISTRYINDEX, "JellyThink_Func"); // 将这个iRef压入栈返回,在CModule2中根据这个ref引用得到对应的function
lua_pushinteger(L, iRef); // 返回一个参数
return ;
}
Lua代码传递一个function变量进来,首先检查参数是否正确(貌似这个是通用做法);然后复制两份(为什么?传递进来的变量,在函数中一般都要做一个拷贝,不要修改传递进来的参数,当然了,out类型参数另说。)。接下来,看代码中的注释就能搞定了。单击这里下载代码CModule.zip。
【环境】
从5.1开始,在Lua中注册的所有C函数都有自己的环境table。一个函数可以像访问注册表那样,通过一个伪索引来访问它的环境table。环境table的伪索引是LUA_ENVIRONINDEX。
这种使用环境的方法与在Lua模块中使用环境的方法差不多,都是先为模块创建一个新的table,然后使模块中的所有函数都共享这个table。只不过,在Lua中使用了一个setfenv函数,而在C模块中,只不过是设置table为LUA_ENVIRONINDEX。下面就来看一段环境的代码。
int luaopen_EnvironIndexDemo(lua_State *L)
{
lua_newtable(L);
lua_replace(L, LUA_ENVIRONINDEX);
luaL_register(L, "CModule", arrayFunc);
return ;
}
这个注册函数比以前写的注册函数要多两行代码,先要创建一个新的table,然后调用lua_replace将新的table作环境table。然后调用luaL_register时,所有新建的函数都会继承当前环境。
static int SetValue(lua_State *L)
{
// Lua传递的值,先检查参数
luaL_checkinteger(L, );
lua_pushvalue(L, );
lua_setfield(L, LUA_ENVIRONINDEX, "JellyThink");
return ;
} // 从环境中取出对应的值
static int GetValue(lua_State *L)
{
lua_getfield(L, LUA_ENVIRONINDEX, "JellyThink");
return ;
}
上面先将值设置到模块环境table中。然后再从中取出来。这个和上面说的注册表有很多的相似之处。尽管可能使用环境来代替注册表,但是如果没有在不同模块之间共享数据的需要,就尽可能的不要使用注册表;使用环境创建的引用,只是在本模块中可见,这样缩小了数据的使用范围了,减小了数据被错改的可能,增加了数据的安全性。可以单击这里下载完整代码EnvironIndexDemo.zip。
【upvalue】
注册表提供了全局变量的存储,环境提供了模块变量的存储,而upvalue机制则实现了一种类似于C语言中静态变量的机制。对upvalue不熟悉的伙计,可以看看《Lua中的闭包》这篇文章。而这种upvalue机制,可以让我们定义一个只在特定的函数中可见的变量。每当在Lua中创建一个函数时,都可以将任意数量的upvalue与这个函数相关联。每个upvalue都可以保存一个Lua值。以后,在调用这个函数时,就可以通过伪索引来访问这些upvalue了。
将这种C函数与upvalue的关联称为closure(也叫闭包,多么熟悉的名字)。一个C closure类似于Lua closure。closure可以用同一个函数代码来创建多个closure,每个closure可以拥有不同的upvalue。接下来,来一个简单的实例,上代码:
static int count(lua_State *L)
{
int iValue = lua_tointeger(L, lua_upvalueindex());
lua_pushinteger(L, ++iValue);
lua_pushvalue(L, -);
lua_replace(L, lua_upvalueindex());
return ;
} static int newCount(lua_State *L)
{
lua_pushcclosure(L, &count, );
return ;
}
以上是部分重要代码,在Lua中,调用newCount,就能得到一个闭包函数count,在Lua中,就可以像使用普通变量一样来使用这个count函数了,在Lua中每次调用count函数,它每次会从upvalue中得到之前保存的upvalue,返回给Lua。单击这里下载完整工程Upvalue1。
再说C模块的编写(2)的更多相关文章
- Layui 是一款采用自身模块规范编写的国产前端UI框架(5600个Star)
采用自身模块规范编写的前端UI框架,遵循原生HTML/CSS/JS的书写形式,极低门槛,拿来即用. http://www.layui.com Layui 是一款采用自身模块规范编写的国产前端UI框架, ...
- Python turtle 模块可以编写游戏,是真的吗?
1. 前言 turtle (小海龟) 是 Python 内置的一个绘图模块,其实它不仅可以用来绘图,还可以制作简单的小游戏,甚至可以当成简易的 GUI 模块,编写简单的 GUI 程序. 本文使用 tu ...
- 再说C模块的编写(1)
[前言] 在<Lua“控制”C>中对Lua调用C函数做了初步的学习,而这篇才是重中之重,这篇文章会重点的总结C模块编写过程中遇到的一些问题,比如数组操作.字符串操作和C函数的状态保存等问题 ...
- 【译】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一开始就提出利用管理单元来实现 ...
随机推荐
- SoapUI 学习总结-01 环境配置
遇到的问题 1,怎么SoapUI的Request URL不支持大写怎么办? 问题:在SoapUI的Request URL中,每次输入的URL中含有的大写字母会自动转换为小写字母,导致请求不了对应的地址 ...
- 在pycharm中查看内建函数源码
鼠标放在内建函数上,Ctrl+B,看源码
- 如何搭建SVN的客户端和使用
1.下载安装TortoiseSVN 首先我们需要从官方网站下载TortoiseSVN客户端工具 可以选择32位和64位.也可以直接使用搜索引擎搜索TortoiseSVN 也会出现直接的下载方式.这里不 ...
- Windows 通过批处理自动执行 linux服务器上面命令的办法
1. 使用putty 下载地址 https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html 直接使用 exe版本就可以 https:/ ...
- Spring Cloud微服务Ribbon负载均衡/Zuul网关使用
客户端负载均衡,当服务节点出现问题时进行调节或是在正常情况下进行 服务调度.所谓的负载均衡,就是当服务提供的数量和调用方对服务进行 取舍的调节问题,在spring cloud中是通过Ribbon来解决 ...
- C#中字符串的字面值(转义序列)
在程序开发中,经常会碰到在字符串中字面值中使用转义序列,下面表格收集了下转义序列的完整列表,以便大家查看引用: 转义序列列表 转义序列 产生的字符 字符的Unicode值 \' 单引号 0x0027 ...
- 数据分析之Matplotlib和机器学习基础
一.Matplotlib基础知识 Matplotlib 是一个 Python 的 2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形. 通过 Matplotlib,开发者可以仅需 ...
- Nim积解法小结
由于某毒瘤出题人 redbag 不得不学习一下这个史诗毒瘤算法. 本文参考了 Owaski 的 GameTheory 的课件. 定义 我们对于一些二维 \(\mathrm{Nim}\) 游戏(好像更高 ...
- 在Ubuntu上使用离线方式快速安装K8S v1.11.1
在Ubuntu上使用离线方式快速安装K8S v1.11.1 0.安装包文件下载 https://pan.baidu.com/s/1nmC94Uh-lIl0slLFeA1-qw v1.11.1 文件大小 ...
- Java【第一篇】基本语法之--关键字、标识符、变量
关键字 定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词)特点:关键字中所有字母都为小写 标识符 Java 对各种变量.方法和类等要素命名时使用的字符序列称为标识符凡是自己可以起名字的地 ...