快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧 (2)

Q:什么是”registry”?

A:有时候,我们需要在程序中使用一些非局部的变量。在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式。首先,这些变量中无法存储Lua的值。其次,这些变量如果在多个Lua状态机中被使用,则很可能造成非预期的结果。

一个替代方案是,将这些值存储在Lua的全局变量中。这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量。但这依旧不是最好的方式,因为是Lua的全局变量,Lua程序可以随意的修改变量的值,这很可能对C库中的函数在使用这些变量时造成影响。

为了进一步避免上述情况,Lua提供了一张特殊的”table”,它可以供C代码随意使用。但是对于Lua代码,访问却是被禁止的。这个特殊的”table”便是”registry”,

Q:什么是”pseudo-index”?

A:”pseudo-index”类似于虚拟栈中正常的索引,但其与正常索引的区别在于,虽然使用它也是通过虚拟栈,但是其所对应的值并不是存储在虚拟栈中。

LUA_REGISTRYINDEX就是一个”pseudo-index”,定义在”lua.h”中,它用于通过虚拟栈访问”registry”(但”registry”并非实际存储在虚拟栈中),使用时按照虚拟栈中正常索引的使用方式使用。

例如,你可以通过如下方式获取”registry”中索引为”Key”的元素的值,

lua_pushstring(L, "Key");
lua_gettable(L, LUA_REGISTRYINDEX);

Q:如何保证”registry”中的索引唯一?

A:”registry”就是一个普通的Lua的”table”,因此你可以像访问其他”table”一样使用非nil的值作为”key”存取它的元素。然而,由于所有的C库共享相同的”registry”,你必须注意使用什么样的值作为”key”才不会导致命名冲突。

一个防止命名冲突的方法是使用”static”变量的地址作为”key”,这样C链接器就会保证这个地址在所有的库中唯一。

“mylib.c”文件中(lua_pushlightuserdata将一个代表C指针的值放到虚拟栈内,之后的章节会详细介绍):

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

static int l_test_registry(lua_State *L)
{
    // "key"变量存储什么值不重要,因为用到的是它本身的地址。
    static const char key = ‘k‘;
    static const char key1 = ‘k‘;

    int my_number = 0;

    lua_pushlightuserdata(L, (void *)&key);    // 将"key"入栈。
    lua_pushnumber(L, 9);    // 将"value"入栈。
    lua_settable(L, LUA_REGISTRYINDEX);    // "registry[key] = value"。

    // 不同的"key",操作"registry"中不同的元素。
    lua_pushlightuserdata(L, (void *)&key1);
    lua_pushnumber(L, 10);
    lua_settable(L, LUA_REGISTRYINDEX);

    // 相同的"key",操作"registry"中相同的元素。
    lua_pushlightuserdata(L, (void *)&key);
    lua_pushnumber(L, 11);
    lua_settable(L, LUA_REGISTRYINDEX);

    lua_pushlightuserdata(L, (void *)&key);    // 将"key"入栈。
    lua_gettable(L, LUA_REGISTRYINDEX);    // "registry[key]"。
    my_number = lua_tonumber(L, -1);    // 获得虚拟栈顶的结果。
    printf("%d\n", my_number);    // 11

    lua_pushlightuserdata(L, (void *)&key1);
    lua_gettable(L, LUA_REGISTRYINDEX);
    my_number = lua_tonumber(L, -1);
    printf("%d\n", my_number);    // 10

    return 0;    // 没有返回值。
}

static const struct luaL_Reg mylib[] = {
    {"my_test_registry", l_test_registry},
    {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"

mylib.my_test_registry()
--[[ results:
11
10
]]

当然你也可以使用字符串作为”registry”的”key”,只要你保证这些字符串唯一。当你打算允许其他的独立库访问你的数据时,字符串的”key”会非常有用(因为其他独立库可以明确的知道”key”的名字)。

最后,不要使用数值作为”registry”的”key”,因为他们是供”reference system”使用的。

Q:什么是”reference system”?

A:”reference system”由一对儿定义在辅助库中的函数组成。使用他们,你可以无需关心如何创建唯一的”key”,便可以在”registry”中自由的存取数据。

/* 生成一个唯一的"key",将栈顶的值出栈作为"value",
 * 从虚拟栈的索引"t"处获得"table",之后做相当于"table[key] = value"的操作。
 * 函数返回生成的"key"。
 * 如果"value"为"nil",则不会生成"key",同时函数返回"LUA_REFNIL"。
 * 同时Lua还定义了"LUA_NOREF",它代表一个与"luaL_ref"所返回的任何值都不同的值。
 */
int luaL_ref(lua_State *L, int t);

/* 从虚拟栈的索引"t"处获得"table",之后做相当于"table[ref] = nil"的操作。
 * "ref"同时也被释放(可以再次供"luaL_ref"使用)。
 * 如果"ref"是"LUA_NOREF"或是"LUA_REFNIL",则函数不做任何操作。
 */
void luaL_unref(lua_State *L, int t, int ref);

luaL_ref所返回的”key”称之为”reference”。

static int l_test_registry(lua_State *L)
{
    int ref = 0, value = 0;

    lua_pushinteger(L, 9);    // 数据入栈。
    // 将数据存入"registry",并得到数据所对应的"reference"。
    ref = luaL_ref(L, LUA_REGISTRYINDEX);
    // 从"registry"中获取"reference"所对应的数据,并将其入栈。
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    value = lua_tointeger(L, -1);    // 获取栈顶的数据。
    printf("%d\n", value);    // 9
    luaL_unref(L, LUA_REGISTRYINDEX, ref);    // 释放"reference"。

    return 0;
}

Q:如何在C库函数中实现”Closure”?

A:”registry”为C库函数提供了一种实现全局变量的合理有效的方式,而”Closure”将为C库函数提供一种实现静态变量的合理有效的方式。

与Lua中的”Closure”类似,你可以为C库函数绑定多个”upvalue”,每一个”upvalue”都可以存储一个Lua值。之后当C库函数的这个”Closure”被调用时,它就可以自由的访问它的”upvalues”(通过”pseudo-indices”访问到)。

/* 创建一个C中的"Closure",将栈顶的"n"个值作为其"upvalues"。
 * 弹出栈顶作为"upvalues"的"n"个值,最后将"Closure"入栈。
 */
void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n);

// 返回当前运行的函数的第"i"个"upvalue"的"pseudo-indice"。
int lua_upvalueindex(int i);

接下来的例子,我们将在C库函数中重新实现newCounter(之前在快速掌握Lua 5.3 —— 函数的“什么是”Closures”?”部分使用Lua代码实现过)。

“mylib.c”文件中:

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

// "Closure"的函数代码部分。
static int counter(lua_State *L)
{
    // 获取"Closure"的第一个"upvalue"。
    double val = lua_tonumber(L, lua_upvalueindex(1));
    /* 将该"upvalue"加1,然后入栈。
     * (这里还没有实际的将"Closure"的"upvalue"更新)
     * 该值将作为"Closure"的返回值。
     */
    lua_pushnumber(L, ++val);
    /* 将新的"upvalue"的值复制一份儿,然后入栈。
     * (因为下面"lua_replace"会弹出此值)
     * 该值将用于实际更新"Closure"的"upvalue"值。
     */
    lua_pushvalue(L, -1);
    /* 获取"Closure"的"upvalue",并实际更新它。
     * (同时会弹出栈顶用于更新"upvalue"的值)
     */
    lua_replace(L, lua_upvalueindex(1));

    return 1;    // 返回最新的"upvalue"值。
}

// 创建一个C中的"Closure"。
static int l_newCounter(lua_State *L)
{
    lua_pushnumber(L, 0);    // "upvalue"入栈,初始值为0。
    // "counter"函数作为"Closure"的函数代码部分,它有一个"upvalue"。
    lua_pushcclosure(L, &counter, 1);

    return 1;    // 将创建的"Closure"作为返回值返回。
}

static const struct luaL_Reg mylib[] = {
    {"my_newCounter", l_newCounter},
    {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"

newCounter1 = mylib.my_newCounter()
print(newCounter1())    --> 1.0
print(newCounter1())    --> 2.0
newCounter2 = mylib.my_newCounter()
print(newCounter2())    --> 1.0
print(newCounter1())    --> 3.0

附加:

1、Lua API中大部分函数可以接受”pseudo-index”,除了一些会操作虚拟栈本身的函数,例如lua_removelua_insert等。

2、当使用字符串作为”registry”的”key”时,一些好的习惯可以避免”key”的冲突。比如使用库的名称作为前缀,或是使用”universal unique identifier(uuid)”。很多系统都有专门的工具来产生”uuid”,比如Linux系统下的”uuidgen”。

3、实际上,”reference system”可以应用于任何的”table”,但是他们典型的应用是在”registry”中。

4、可以通过相同的函数代码,绑定不同的”upvalues”,从而创建不同的”Closures”。C库函数中的”Closure”同样如此。

5、C中的”Closure”与Lua中的”Closure”不同之处在于,C中的”Closure”不能共享”upvalues”,每一个”Closure”都有自己独立的”upvalues”。然而,我们可以设置不同”Closure”的”upvalues”指向同一个”table”,这样这个”table”就变成了一个所有”Closure”共享数据的地方。

时间: 2024-09-30 19:43:23

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧 (2)的相关文章

Lua的模块编写与module函数

本文转载于:http://www.benmutou.com/archives/1786 1.编写一个简单的模块 Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个table里有一些变量.一些函数… 等等,这不就是我们所熟悉的类吗? 没错,和类很像(实际上我说不出它们的区别). 我们来看看一个简单的模块,新建一个文件,命名为game.lua,代码如下: game = {} function game.play() print("那么,开始吧"); end function

Lua与C++交互初探之Lua调用C++

Lua与C++交互初探之Lua调用C++ 上一篇我们已经成功将Lua的运行环境搭建了起来,也成功在C++里调用了Lua函数.今天我来讲解一下如何在Lua里调用C++函数. Lua作为一个轻量级脚本语言,他只包含了一些必要的系统库函数,当有需要时还得自己去写.有一次我要做一个两数异或的操作发现函数库里居然没有异或运算.不得不非常苦逼的自己去写.后来接触Lua深了之后才知道将这种"缺陷"可以由C函数来弥补.但要做到这一点对于一个对C只知道if else的学生来说确实还是有不少难度. 在学习

win平台下搭建cocos2dx 3.1.1 lua开发环境 VS2012+sublime text+lua 5.2.3

安装vs2012 安装python 2.7.3和环境配置 下载cocos2dx 3.1.1 用vs2012打开test项目 右键运行生成解决方案  cocos2dx3.1.1 新建lua项目 cocos2dx 3.1.1引擎目录依次找到tools cocos2d-console bin,  把cocos.py拖到在dos的command(cmd)中 然后继续输入新建项目的信息: new game -p com.test.app  -l lua 我们这就可以在屏幕中提示的路径找到新建的项目game

Lua 与C/C++ 交互系列: Lua调用C/C++函数(4-1)

1.本文将继续讲解在Lua Code中调用注册的C函数.偶在学习本文知识点时,由于知识点的遗漏,在这个上面浪费了大量时间和精力.一直都没有没明白,Lua 通过面向对象的方式是如果调用注册的C函数.在Programming In Lua一书,有对这个方面的讲解.但是当时看书,就是不理解.因为在前面的章节中,有一个重要的知识点被遗漏.在Lua 元方法中,有两个特别重要的.__index 和__newindex被我遗漏.这两个元方法特别重要,对于定义和拓展Lua的机制,基本依靠这两个. 2.本文首先由

Lua 与C/C++ 交互系列: Lua调用C/C++函数(3)

1.Lua API中提供注册C函数关键在lua_pushcclouse()函数.该函数可以在Lua Code中定义C函数. 但是Lua 提供了几个常用的宏定义,用于注册C函数. 这几个宏定义为: /#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) LUALIB_API vo

Lua学习之加载其他lua文件

Lua 中提供了模块的概念,模块类似一个封装库或者 C++ 中的一个类,可以将公用的部分提到一个文件中,以 API 的形式供其他 lua 文件调用. Lua 中的模块其实就是包含变量.函数等已知元素组成的 table, 本质上是一个 table. 一.模块的声明 创建一个名称为 LearnModule.lua 的文件,并在此文件中实现下列代码: -- 定义一个名为ModuleT的模块,模块的本质就是一个table,内部包含变量和函数等 ModuleT = {} -- 定义一个变量 ModuleT

Lua 学习之基础篇七&lt;Lua Module,Package介绍&gt;

Lua 之Module介绍 包管理库提供了从 Lua 中加载模块的基础库. 只有一个导出函数直接放在全局环境中: [require]. 所有其它的部分都导出在表 package 中. require (modname) 加载一个模块. 这个函数首先查找 [package.loaded] 表, 检测 modname 是否被加载过. 如果被加载过,require 返回 package.loaded[modname] 中保存的值. 否则,它会为模块寻找加载器. require 遵循 [package.

使用lua给wireshark编写uTP的Dissector

lonelycastle做uTP的实验,使用wireshark捕包,但是最初没有找到wireshark下的uTP的dissector,每次都需要比对文档,这样做实验理解报文含义,效率非常低.作为程序猿就想写一个uTP的dissector来实现这些工作.说干就干,查了一下发现wireshark可以使用lua来实现dissector,这样就简单过了,不用编写C的dissector了.本身是lua盲,又不了解wireshark的dissector开发,中间遇到了很多问题,还好逻辑比较简单,折腾了一个晚

crm软件是提供给IDC服务商,还是需要本地部署?

crm是一种管理客户关系的软件.crm软件部署方式有两种:一种是将crm软件在厂商的云服务器上进行安装,称为"云crm系统",另一种是服务器本地安装,称为"本地crm系统".本地安装需要用户自己购买服务器,自己部署稳定的网络环境:而"在线crm系统"则不需要用户购买服务器,部署完成,出差在外也可以查看客户信息. 直 到今天,云计算的全球化使得本地传统crm软件已逐渐被云crm系统(又称为"在线CRM"."托管型CRM