lua c api

4.9k 词

registry

registry 是一个单独的表,用来保存 lua 中的全局变量,同时 lua 代码不能直接访问(只能通过 c 函数接口访问)。registry 通过伪索引(LUA_REGISTRYINDEX)访问,伪索引类似于栈上的索引,但是它的关联数据并未在栈中

registry 的存在是用来解决一个问题:在 c 动态库中如何保存一个 lua 状态机的全局变量。如果使用 c 的全局变量、静态变量,相同进程内的所有 lua 状态机会共享此变量。如果将值保存在 lua 中的全局 table 中,lua 代码是可以直接访问、修改的;没有限制措施,数据被修改后非常容易导致 c 动态库崩溃。

使用 registry 可以实现针对每个 state 保存全局变量,同时不同动态库直接可以通过约定的 keyregistry 中获得值,进行交互。

如果动态库(.so)不希望其他库修改自己设置的全局变量,一个好的方法是使用全局变量的地址作为 key。此时需要取全局变量地址,并转换为 lightuserdata 作为 registrykey

1. 示例代码

/*
 * 在 registry 全局表中保存一条记录 
 * registry[key] = value
 */
void 
state_store_global_var(lua_State *state, char *key, char *value)
{
    if (!state||!key||!value) return;
<span class="n">lua_pushstring</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>     <span class="c1">// 入栈

lua_pushstring(state, value); // 入栈
// 操作 registy table
// registy[key] = value
lua_settable(state, LUA_REGISTRYINDEX);
}

references

如果想在 registry 中添加记录而又不想费劲考虑如何分配 key,可以使用 reference 系统。reference 系统可以自动生成一个唯一 key

int r = luaL_ref(L, LUA_REGISTRYINDEX);

这个函数会从栈顶弹出一个元素,并保存到 registry 中,同时返回唯一数值 r 作为索引。数值 r 就叫做栈顶值的“引用”(reference)。

从这可以看出,用户不能在 registry 中使用数值作为 key,避免影响 reference 系统。

c 中无法直接使用指向 luatablefunction 类型指针(未提供此类接口),我们可以使用引用实现类似指针的功能。假设上面的例子中栈顶元素为 table,我们已经创建了它的一个引用,下面我们将其再次放入栈顶:

lua_rawgeti(L, LUA_REGISTRYINDEX, r);

在引用使用结束后,我们需要使用 luaL_unref 释放引用值与引用自身:

luaL_unref(L, LUA_REGISTRYINDEX, r);

Upvalues

Upvalue 存在的目的是为了在 c 中实现闭包功能。Upvalue 仅在函数内可见,并且在每个函数中是相互独立的。示例代码:

// 定义函数
static int counter(lua_State *L);

int newCounter(lua_State *L) {
lua_pushnumber(L, 0); // 栈顶增加数字,后续作为闭包的值
// 创建闭包
// &counter 是闭包基函数;注意是函数名取地址
// 1 告诉闭包 upvalue 的数目
lua_pushcclosure(L, &counter, 1);
return 1;
}

static int counter(lua_State L) {
// lua_upvalueindex 同样适用伪索引技术,取 counter 函数的第一个 upvale
double val = lua_tonumber(L, lua_upvalueindex(1));
lua_pushnumber(L, ++val); // 栈顶值为 upvalue 值加 1
lua_pushvalue(L, -1); // 栈顶增加一个相同值,用来更新 upvalue 值
lua_replace(L, lua_upvalueindex(1)); // 更新 upvalue 值
return 1;
}

1. 闭包示例代码

// gcc -fPIC -I/usr/local/lua5.1.5/include  -g -c counter.c -Wall
// gcc -shared -I/usr/local/lua5.1.5/include -L/usr/local/lua5.1.5/lib -llua -o counter.so counter.o

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

static int
counter(lua_State
L) {
// lua_upvalueindex 同样适用伪索引技术,取 counter 函数的第一个 upvale
double val = lua_tonumber(L, lua_upvalueindex(1));
lua_pushnumber(L, ++val); // 栈顶值为 upvalue 值加 1
lua_pushvalue(L, -1); // 栈顶增加一个相同值,用来更新 upvalue 值
lua_replace(L, lua_upvalueindex(1)); // 更新 upvalue 值
return 1;
}

static int
newCounter(lua_State *L) {
lua_pushnumber(L, 0); // 栈顶增加数字,后续作为闭包的值
// 创建闭包
// &counter 是闭包基函数;注意是函数名取地址
// 1 告诉闭包 upvalue 的数目
lua_pushcclosure(L, &counter, 1);
return 1;
}

static const
struct luaL_reg counter_lib[] = {
{"newCounter", newCounter},
{NULL, NULL}
};

/******************************************************************************

  • 注册函数

  • 注意,在 lua 中 require 时,必须与 luaopen_xxxx 相同
    *********************************************************************/
    int
    luaopen_counter(lua_State
    l) {
    luaL_openlib(l, "counter", counter_lib, 0);
    return 1;
    }

newCounterC 中创建一个闭包,返回给 lua 使用。

– use package.loadlib or require to dynamic library
– l,e = package.loadlib("./counter.so", "luaopen_counter")
require "counter"
c1 = counter.newCounter()
print(type(c1)) –> function
c2 = counter.newCounter()
print(c1()) –> 1
print(c1()) –> 2
print(c2()) –> 1

2. 程序说明

#define luaI_openlib luaL_openLib
typedef struct luaL_Reg {
const char
name;
lua_CFunction func;
} luaL_Reg;
/
luaL_register 实际调用的也是 luaI_openlib 函数 /
LUALIB_API void
(luaL_register) (lua_State
L, const char libname, const luaL_Reg l) {
luaI_openlib(L, libname, l, 0);
}
LUALIB_API void
luaI_openlib (lua_State L, const char libname, const luaL_Reg l, int nup) {
if (libname) {
int size = libsize(l);
/ check whether lib already exists /
luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
lua_getfield(L, -1, libname); /
get _LOADED[libname] /
if (!lua_istable(L, -1)) { /
not found? /
lua_pop(L, 1); /
remove previous result /
/
try global variable (and create one if it does not exist) /
if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
luaL_error(L, "name conflict for module " LUA_QS, libname);
lua_pushvalue(L, -1);
lua_setfield(L, -3, libname); /
_LOADED[libname] = new table /
}
lua_remove(L, -2); /
remove _LOADED table /
lua_insert(L, -(nup+1)); /
move library table to below upvalues /
}
for (; l->name; l) {
int i;
for (i=0; i<nup; i) /
copy upvalues to the top /
lua_pushvalue(L, -nup);
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, -(nup+2), l->name);
}
lua_pop(L, nup); /
remove upvalues */
}

luaL_registerluaI_openlibluaL_openlib 三个函数都提供了注册函数列表到 table 的功能。luaL_register 是通过调用 luaI_openlib 函数来实现,提供了无闭包函数的功能。luaI_openlibluaL_openlib 是同一个函数都会将函数注册到 table 中,nup 指定”上值“数量。 luaI_openlib 思路是操作栈,将需要注册的函数保存到栈底的 table 中,函数执行完毕后栈底保留原先 table

四 参考链接