C语言没有类似 lua_pushtable 和 lua_totable 的方法,这也很容易理解,因为C语言中没有一种数据类型可以和lua中的table相对应的,那C函数怎么操作table类型的参数或者返回table类型的结果呢,lua API中提供了一系列的操作table的函数。

void lua_createtable (lua_State *L, int narr, int nrec);  //创建一个空的table并压入栈中,并预分配narr个array元素的空间和预分配nrec个非array元素的空间
void lua_newtable (lua_State *L); // lua_createtable的特例版,相当于调用 lua_createtable(L, 0, 0)

以上两个方法用于创建一个lua table并压入栈中,所以,想要返回一个table,就可以如下操作:

static int returntable(lua_State *L)
{
	lua_newtable(L);
	return 1;
}

上面代码返回的是一个空的table,如果需要向这个table中添加元素,API提供了几个用于设置table元素的方法:

void lua_settable (lua_State *L, int index);
void lua_rawset (lua_State *L, int index);

void lua_setfield (lua_State *L, int index, const char *k);
void lua_rawseti (lua_State *L, int index, int n);

lua_settable在调用之前需要先压入你要设置的 key 和 value,例如你要设置table["foo"] = "bar",先把 "foo" 压入栈中,再把 "bar" 压入栈中,然后再调用lua_settable,index取你要设置的table在栈中的索引,调用完该方法后,压入的key和value会自动被弹出栈。lua_rawset除了设置时不会触发元表操作外和lua_settable基本相同,修改示例代码为table添加元素:

static int returntable(lua_State *L)
{
	lua_newtable(L);
	
	lua_pushstring(L, "foo");
	lua_pushstring(L, "bar");
	
	lua_settable(L, -3);  //由于压入了key和value,所以此时的table索引为-3
	//lua_rawset(L, -3); 也可以使用lua_rawset
	
	return 1;  //设置后,会弹出 "foo"和 "bar", 所以此时table仍旧处在栈顶
}

lua_setfield简化了lua_settable的调用,key不再需要压入栈中,而是直接当作函数的第三个参数传入,所以只需要压入value然后调用方法即可,调用后同样会把value从栈中弹出,lua_rawseti 和 lua_setfield类似,但是只能设置整数类型的key,可以对于纯数字索引的数组使用。

static int returntable(lua_State *L)
{
	lua_newtable(L);
	
	lua_pushstring(L, "foo");
	lua_pushstring(L, "bar");
	
	lua_settable(L, -3);  //由于压入了key和value,所以此时的table索引为-3
	//lua_rawset(L, -3); 也可以使用lua_rawset
	
	lua_pushstring(L, "Tom");
	lua_setfield(L, -2, "username");  //由于只压入了value,所以table此时的索引为-2
	
	return 1; 
}

接下来实现一个用指定字符切割字符串返回数组的函数:

static int split(lua_State *L)
{
        int len;
        const char *str = lua_tolstring(L, 1, &len);
        const char *sep = lua_tostring(L, 2);
        int lastpos = -1, i = 0, key = 1;

        lua_newtable(L); //创建一个table作为返回值
        for(;i < len; i++)
        {
                if (str[i] == *sep)
                {
                        lua_pushlstring(L, str + lastpos + 1, i - lastpos - 1);  //压入子串
                        lua_rawseti(L, -2, key);  //把子串添加进table
                        lastpos = i;
                        key++;
                }
        }
        //处理最后一个子串
        lua_pushlstring(L, str + lastpos + 1, len - lastpos - 1);
        lua_rawseti(L, -2, key);
        return 1;
}

上面代码使用了lua_tolstring 和 lua_pushlstring 两个方法,可以使用第三个参数,来获取或设置字符串的长度,这个方法可以在lua中像 split("aaa,bbb,ccc", ",") 这样调用,并返回一个字符串数组。

同样,C API也提供了获取table值得方法:

void lua_gettable (lua_State *L, int index); //获取索引为index的table中指定key的value,key要预先压入栈,函数调用结束key会被弹出栈,并把value压入栈中
void lua_rawget (lua_State *L, int index);  // lua_gettable类似,但不涉及元表

void lua_getfield (lua_State *L, int index, const char *k); //key不需要压入栈,直接当作第三个参数传入,得到的value同样会被压入栈中
void lua_rawgeti (lua_State *L, int index, int n); // 除了不涉及元表,并且key只能为整数

下面实现一个求数组所有元素和的方法:

static int array_sum(lua_State *L)
{
        //检查参数是否为一个table, 如果不是返回nil
        if (!lua_istable(L, 1))
        {
                lua_pushnil(L);
                return 1;
        }
        int sum = 0;
        int len = lua_objlen(L, 1);  //返回数组的长度

        int i = 1;  //lua table 索引从1开始
        for(; i <= len; i++)
        {
                lua_rawgeti(L, 1, i);
                sum += lua_tointeger(L, -1);
                lua_pop(L, 1);  //将刚刚获取的元素值从栈中弹出,其实也可以不用弹栈 因为table的索引始终是1,新读取的值始终在栈顶,也就是索引是-1,不过这是一个好的习惯
        }

        lua_pushinteger(L, sum);
        return 1;
}

lua_istable 可以检查一个值是否为table,是返回1,否则返回0, lua_objlen 可以获取字符串或数组的长度

以上就是C API中关于table操作的一些方法,接下来介绍一下在C代码中调用lua函数。

假设现在lua脚本中有这样一种需求,要求有一个array_map方法,该方法接受两个参数,第一个参数为一个数组,第二个参数为一个函数f,用数组里的所有元素当作参数调用函数f,用返回值组成一个新的数组并返回,大致的lua脚本像这样:

local function double(num)
	return num * 2
end

local arr = {1, 2, 3, 4, 5}

local res = array_map(arr, double) --期望得到 {2, 4, 6, 8, 10}

那该如何用C实现这个array_map方法呢,这就需要在C代码中调用lua函数,为此,C API提供了两个用来调用lua函数的方法:

void lua_call (lua_State *L, int nargs, int nresults);
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

这两个方法都相对比较复杂,lua_call要求首先压入要调用的lua函数,然后依次按顺序压入nargs个参数, 这时调用lua_call,之后这个函数和所有的参数都会从栈中弹出,最后把函数返回的nresults个结果依次压入栈中。lua_pcall在保护模式下执行lua函数,当执行出错时这个错误会被捕获,而不会直接终止程序运行,lua_pcall的返回值是一个错误码,当返回0时表示没有错误,此时效果和lua_call一样,当返回不为0时表示有错误,并将错误消息压入栈中,第四个参数还可以指定一个错误处理函数的索引,如果不指定可以传入0值。

下面实现前面提到的array_map方法:

static int array_map(lua_State *L)
{
        //校验参数类型
        if (!lua_istable(L, 1) || !lua_isfunction(L, 2))
        {
                lua_pushnil(L);
                return 1;
        }
        int len = lua_objlen(L, 1); //数组的大小
        int i = 1;

        for(; i <= len; i++)
        {
                //由于方法调用之后会被弹出,所以调用之前先复制一份,pushvalue会将指定索引的值复制一份到栈顶
                lua_pushvalue(L, 2);
                lua_rawgeti(L, 1, i); //获取数组元素值并会压入栈中
                lua_call(L, 1, 1);    //调用函数,一个参数 一个返回值,此时复制的函数和压入的参数都会弹出,然后压入结果
                lua_rawseti(L, 1, i);  //再把结果替换掉数组中的原值。
        }
        lua_pushvalue(L, 1);  //把数组复制一份放到栈顶 当作返回值
        return 1;
}

现在把本文中的所有方法放到上节中的clib.c中,重新编译成clib.so

#include <lauxlib.h>

//编写C函数 static使外部无法直接访问这个函数
static int sum(lua_State *L)
{       
        int a = lua_tointeger(L, 1); //第一个加数,函数的第一个参数总是索引1
        int b = lua_tointeger(L, 2);  //第二个加数
        lua_pushinteger(L, a + b);  //压入结果          
        return 1;                                       //返回1表示该方法只有一个返回值
}

static int split(lua_State *L)
{       
        size_t len;
        const char *str = lua_tolstring(L, 1, &len);
        const char *sep = lua_tostring(L, 2);
        int lastpos = -1, i = 0, key = 1;
        
        lua_newtable(L); //创建一个table作为返回值
        for(;i < len; i++)
        {       
                if (str[i] == *sep)
                {       
                        lua_pushlstring(L, str + lastpos + 1, i - lastpos - 1);  //压入子串
                        lua_rawseti(L, -2, key);  //把子串添加进table
                        lastpos = i;
                        key++;
        //处理最后一个子串
        lua_pushlstring(L, str + lastpos + 1, len - lastpos - 1);
        lua_rawseti(L, -2, key);
        return 1; //返回table
}

static int array_sum(lua_State *L)
{
        //检查参数是否为一个table, 如果不是返回nil
        if (!lua_istable(L, 1))
        {
                lua_pushnil(L);
                return 1;
        }
        int sum = 0;
        int len = lua_objlen(L, 1);  //返回数组的长度

        int i = 1;  //lua table 索引从1开始
        for(; i <= len; i++)
        {
                lua_rawgeti(L, 1, i);
                sum += lua_tointeger(L, -1);
        }

        lua_pushinteger(L, sum);
        float aver = (float)sum / len; //平均值
        lua_pushnumber(L, aver);

        return 2;
}


static int array_map(lua_State *L)
{
        //校验参数类型
        if (!lua_istable(L, 1) || !lua_isfunction(L, 2))
        {
                lua_pushnil(L);
                return 1;
        }
        int len = lua_objlen(L, 1); //数组的大小
        int i = 1;

        for(; i <= len; i++)
        {
                //由于方法调用之后会被弹出,所以调用之前先复制一份,pushvalue会将指定索引的值复制一份到栈顶
                lua_pushvalue(L, 2);
                lua_rawgeti(L, 1, i); //获取数组元素值并会压入栈中
                lua_call(L, 1, 1);    //调用函数,一个参数 一个返回值,此时复制的函数和压入的参数都会弹出,然后压入结果
                lua_rawseti(L, 1, i);  //再把结果替换掉数组中的原值。
        }
        lua_pushvalue(L, 1);  //把数组复制一份放到栈顶 当作返回值
        return 1;
}


//声明一个luaL_Reg结构体数组
static const struct luaL_Reg funcs[] = {
        {"sum", sum},
        {"split", split},
        {"array_sum", array_sum},
        {"array_map", array_map},
        {NULL, NULL}    // 该数组最后一个元素始终是 {NULL, NULL}
};

int luaopen_clib(lua_State *L)
{
        luaL_register(L, "clib", funcs);        //第二个参数要和你的模块名一致
        return 1;
}

编写lua测试脚本:

local clib = require "clib"

local s = clib.sum(3, 99)
print(s)

print("---------------------")

local str = "aaa,bbbb,cccc,dddd,eeee"
local arr = clib.split(str, ",")

for i = 1, #arr do
        print(arr[i])
end

print("---------------------")

local arr1 = {1, 2, 3, 10}
local sum = clib.array_sum(arr1)
print("sum=", sum)

print("---------------------")

local function double(num)
        return num * 2
end
local arr2 = {1, 2, 3, 4, 5}
local res = clib.array_map(arr2, double)

for i = 1, #res do
        print(res[i])
end

执行结果:

102
---------------------
aaa
bbbb
cccc
dddd
eeee
---------------------
sum=	16
---------------------
2
4
6
8
10