lua C API(4) - 编写C函数其他技术

2018-08-31 19:22:39

数组处理

数组处理可以用处理表格的方法来处理比如 lua_settable,lua_gettable,因为数组也是表格。

但是出于性能考虑和简便性,lua api 提供了额外几个专门处理数组的函数。

void lua_rawgeti (lua_State *L, int index, int key);
void lua_rawseti (lua_State *L, int index, int key);

index 为表格在栈里的索引,key就是数组的下标。

lua_rawgeti(L, t, key)

//等效于

lua_pushnumber(L, key);
lua_rawget(L, t);
lua_rawseti(L, t, key)

//等效于

//栈顶为值,栈顶下面为 key
lua_pushnumber(L, key);
lua_insert(L, -2);
lua_rawset(L, t);

raw 操作不会调用元方法。


registry、environment、upvalues

C API提供了3个基本地方来存储非本地数据,registry、environments、upvalues。

registry 是一个全局表格,只能被 C 访问,一般用来在多个模块间共享。

如果需要存储私有模块数据,则需要用 environments,environments 在一个模块里各个函数间共享。

c 函数可能也有 upvalues,这些是 lua 值,只是跟特定的 C 函数绑定在一起。

registry

registry 总是存在于伪索引,由 LUA_REGISTRYINDEX 定义。伪索引跟现实的索引差不多,只是它不存在于栈里。大多数 lua api 函数不但能接受正常栈索引当做参数,也可以接受伪索引当做参数。除了那些操作栈本身的函数比如 lua_remove 和 lua_insert。

从 registry 里获取 key 为 "name" 的值。

lua_getfield(L, LUA_REGISTRYINDEX, "name");

因为 registry 在各个模块间共享,所以键名最好带上模块名前缀,或者通过唯一的 uuid 来当做键名,千万不可用数字当做键名,因为它被保留给"引用系统"使用,比如

int r = luaL_ref(L, LUA_REGISTRYINDEX);

弹出一个元素当做值存入 registry,键名会选一个未使用的数字,并返回这个key。

我们不能通过指针来指向 lua 对象,如果真的需要,我们可以通过上面这种方式把引用存到c里。

把引用对应的值压入栈中

lua_rawgeti(L, LUA_REGISTRYINDEX, r);

下面这句释放引用和对应的值

luaL_unref(L, LUA_REGISTRYINDEX, r);

引用系统对待 nil 比较特殊,当栈顶的值为 nil,我们调用 luaL_ref 将返回 LUA_REFNIL,下面把 nil 压入栈中。

lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL);

常量 LUA_NOREF 代表无效值,所有试图获取 LUA_NOREF 返回 nil,试图释放它没影响。

另一种万无一失的方式是用静态变量地址当做key。因为 C 链接器会保证该地址在各个类库之间唯一。

/* variable with an unique address */
static const char Key = ’k’;

//存字符串
lua_pushlightuserdata(L, (void *)&Key); //压入地址
lua_pushstring(L, myStr); //压入值
lua_settable(L, LUA_REGISTRYINDEX); /* registry[&Key] = myStr */

//获取字符串
lua_pushlightuserdata(L, (void *)&Key); /* push address */
lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */
myStr = lua_tostring(L, -1); /* convert to string */

environments

从 lua 5.1 开始,每个注册的 C 函数都有其自己的 environment 表格。一个函数访问该表格的方式跟 registry 一样,通用利用伪索引,该伪索引为 LUA_ENVIRONINDEX。

一般来说,我们使用这些 environments 方式跟 lua 模块一样。我们为模块创建一个表格,然后使所有的函数共享这个表格。在C里面设置该共享 environments 和在 lua里一样,只要简单的在主块里改变 environment,那么创建的所有的函数都会继承。在C里,代码看起来类似于下面

int luaopen_foo (lua_State *L) {
    lua_newtable(L);
    lua_replace(L, LUA_ENVIRONINDEX);
    luaL_register(L, <libname>, <funclist>);
    ...
}

创建一个表格,并把该表格设置为自己的环境表格,那么当调用 luaL_register,所有新创建的函数都会继承。

 upvalues

我们知道,registry 提供了全局变量,environments 提供了模块变量,而 upvalues 只能在特定的函数里可见。

每次要在lua里创建新的C函数,可以关联任意个 upvalues,每个 upvalue 可以存储单个 lua 值。当后面该函数被调用时,可以通过伪索引自由访问这些 upvalue 值。

此行为我们称之为闭包,一个有趣的事实是我们可以通过同一个函数代码创建不同的闭包,不同的 upvalues。

static int counter (lua_State *L); /* forward declaration */

int newCounter (lua_State *L) {
    lua_pushinteger(L, 0);
    lua_pushcclosure(L, &counter, 1);
    return 1;
}

lua_pushcclosure 用来创建闭包,第二个参数是基本函数,第三个参数为 upvalues 数量。此例子,只有一个 upvalue,为0。

 lua_pushcclosure 会包新创建的闭包留在栈上,所以闭包将作为 newCounter 函数返回。

static int counter (lua_State *L) {
    //获取第一个 upvalue 0.
    int val = lua_tointeger(L, lua_upvalueindex(1));

    lua_pushinteger(L, ++val); //压入1
    lua_pushvalue(L, -1); //再压入一个 1
    lua_replace(L, lua_upvalueindex(1)); //弹出1,并替换 upvalue 0 为 1
    return 1; //指定返回的参数个数
}

这里的 lua_upvalueindex(1) 其实是一个宏定义,返回第一个 upvalue 的伪索引。

upvalue 元组例子

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

//调用闭包函数,
int t_tuple (lua_State *L) {
    //获取第一个参数,并转化成int,如果没提供或者为nil,返回0
    int op = luaL_optint(L, 1, 0);
    
    //没有参数
    if (op == 0) {
        int i;
        
        //把所有有效的 upvalue 值压入栈(也就是当做函数返回值)
        for (i = 1; !lua_isnone(L, lua_upvalueindex(i)); i++)
            lua_pushvalue(L, lua_upvalueindex(i));
        return i - 1; //返回值个数
        
    }else {
        //第一个参数必须大于0
        luaL_argcheck(L, 0 < op, 1, "index out of range");
        
        //校验第op个 upvalue 是否为有效值
        if (lua_isnone(L, lua_upvalueindex(op)))
            return 0;
                        
        lua_pushvalue(L, lua_upvalueindex(op));
        return 1;
    }
}

//返回闭包函数 t_tuple,把所有参数当做 upvalue 绑定到该闭包
int t_new (lua_State *L) {
    lua_pushcclosure(L, t_tuple, lua_gettop(L));
    return 1;
}

static const struct luaL_Reg tuplelib [] = {
    {"new", t_new},
    {NULL, NULL}
};

int luaopen_tuple (lua_State *L) {
    luaL_register(L, "tuple", tuplelib);
    return 1;
}
#编译成lua模块
gcc mytuple.c -fPIC -shared -o tuple.so

编写 main.lua 脚本来测试

--添加动态库搜索路径
--如果tuple.so放在默认搜索路径则可以省略
package.cpath = "/root/c/?.so;"..package.cpath

tp = require("tuple")

--传入3个 upvalue 值并绑定到闭包函数
x = tp.new("freecls", "沧浪水", "lua")

--调用闭包函数并传入一个参数1
--此时该闭包函数就会找到第1个 upvalue 并返回
print(x(1))

--闭包函数找出所有 upvalue 并返回
print(x())
[root@192 c]# lua main.lua 
freecls
freecls	沧浪水	lua


备注:

1.本文只是对模块加载机制做简单的介绍,如果有疑问可以给我留言
2.lua的版本为5.1,运行环境centos7 64位
3.原文地址http://www.freecls.com/a/2712/103


©著作权归作者所有
收藏
推荐阅读
  • lua C API(3) - Lua 调用 C函数

    从Lua中调用C函数,必须遵循一些协议来传递参数和获得返回结果。另外,从Lua调用C函数我们必须注册函数,也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。每一个函数都有他自己的私有...

  • lua C API(2) - C 调用 Lua

    Lua 可以作为程序库用来扩展应用的功能。同时,Lua 程序中可以注册有其他语言实现的函数,这些函数可能由C语言(或其他语言)实现,可以增加一些不容易由 Lua 实现的功能。这使得 Lua 是可扩展的...

  • lua C API(1) - 函数汇总

    在调用C API时有几个重要的头文件:lua.h:基础函数库,lua_ 前缀。lauxlib.h辅助库,luaL_ 前缀,利用 lua.h 实现的更高层的抽象。lualib.h为了保持Lua的苗条,所...

  • lua模块安装

    lua模块安装可以利用 luarocks 命令,在centos7 上执行以下来安装。yum install epel-release yum install luarocks此时我们安装模块的的时候可...

  • lua coroutine(协程)

    协程相关的文章网上有很多,众说纷坛,相比其他技术概念理解起来没那么直接。因为协程涉及了很多底层高并发概念,没接触过操作系统调度,C语言等底层知识根本不可能完全理解。备注:以下我说的单进程就是指一个进程...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。