Lua和C语言的交互3【转】

来源:互联网 发布:ubuntu 14 32位下载 编辑:程序博客网 时间:2024/06/03 18:16
原文地址:


http://www.grati.org/?p=670


自定义数据类型:

我们通过使用C语言实现一个Lua数组来演示Lua实现自定义用户数据。数组的结构如下所示:

typedef struct NumArray{
int size; //表示数组的大小
double values[]; //此处的values仅代表一个double*类型的指针,values指向NumArray结构后部紧跟的数据的地址
} NumArray;

我们实现四个函数new,get,set和size,分别用来完成数组的新建,读取,写入和获取大小。在Lua中用来实现自定义数据结构的类型叫做userdata。Lua提供了如下接口用来创建userdata: void *lua_newuserdata(lua_State *L, size_t size),该函数分配size大小的内存作为userdata并将其压入栈,函数的返回值为新建立的userdata,可以自由转换为所需的数据结构。

实现自定义数据结构的C代码如下:

/* 新建array */
static int newarray (lua_State *L){
int n = luaL_checkint(L, -1); //检查参数,数组的个数必须是整数
size_t nbytes = sizeof(NumArray) + n*sizeof(double); //计算C语言结构所需的内存空间,nbytes的长度包括数组结构头和其后部的数据
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes); //新建一个大小为nbytes的userdata并压入堆栈。
a->size = n; //初始化NumArray的大小
return 1;
}

/* 设置array中的数值 */
static int setarray(lua_State *L) {
NumArray *a = (NumArray *)lua_touserdata(L, -3); //将堆栈中的userdata读取出来
int index = luaL_checkint(L, -2); //读取索引
double value = luaL_checknumber(L, -1); //读取数值

luaL_argcheck(L, NULL != a, 1, "'array' expected"); //检查参数的返回,如果第二个表达式为假,则抛出最后一个参数指定的错误信息
luaL_argcheck(L, index >= 0 && index <= a->size, 1, "index out of range");

a->values[index - 1] = value; //将lua中写入的数值设置到C数组中

return 0;
}

/* 读取array中的数值 */
static int getarray(lua_State *L) {
NumArray *a = (NumArray *)lua_touserdata(L, -2); //前面的步骤和setarray中的相同
int index = luaL_checkint(L, -1);

luaL_argcheck(L, NULL != a, 1, "'array' expected");
luaL_argcheck(L, index >= 1 && index <= a->size, 1, "index out of range");

lua_pushnumber(L, a->values[index - 1]); //将C数组中的数值压入堆栈

return 1;
}

/* 获取array的大小 */
static int getsize(lua_State *L) {
NumArray *a = (NumArray *)lua_touserdata(L, -1);
luaL_argcheck(L, NULL != a, 1, "'array' expected");

lua_pushnumber(L, a->size);

return 1;
}

/* 将我们定义的四个函数写成数组的形式,在主函数中可以使用luaL_openlib将四个函数一口气注册到lua空间 */
static const struct luaL_Reg arraylib[] = {
{"new", newarray},
{"set", setarray},
{"get", getarray},
{"size", getsize},
{NULL, NULL}
};

int main(int argc, char* argv[])
{
......
luaL_openlib(L, "array", arraylib, 0); //注册刚才实现的四个函数到全局变量array下,其名称分别为new,set,get和size。
......
return 0;
}

对应的Lua程序为:

a = array.new(1000)
print(a)
print(array.size(a))
for i=1,999 do
array.set(a, i, 1/i)
end

print ("Print first 10 elements")
for i=1,10 do
print(array.get(a, i) )
end

运行结果是:

pi@raspberrypi ~/Programming/article_lua $ ./a.out userdata3.lua
userdata: 0x20237b0
1000
Print first 10 elements
1
0.5
0.33333333333333
0.25
0.2
0.16666666666667
0.14285714285714
0.125
0.11111111111111
0.1

但是,上述程序有两个问题:1、参数中仅检查了用户的输入参数是否是userdata,并没有区分实际的类型,如果用户传递array.get(io.stdin, 200),就会造成Sagement fault,这样的行为是不可接受的。2、用户仅能使用array.size(a),和array.get(a,40)的形式访问内容,能否使用类似于面向对象特性的方式如a:size()和a:get(40)的形式呢? 答案是肯定的,因此必须引入metatable的机制。
我们假定读者已经对lua语言本身和在Lua中使用metatable有一定的了解,这里仅介绍在C语言中如何为userdata添加metatable。在对于userdata的metatable有如下几点需要注意:
1、metatable在lua中仅是一个普通的table,但是在lua提供给C语言的接口中,metabtable需要使用专门的接口来创建和读取。(原因暂时不详)
2、lua的metatable和javascript的prototype不同。如果访问对象缺失的方法,javascript会直接从prototype指向的对象中查找缺失的方法,但lua不同,他不会直接从metatable中查找,而是会从metatable的__index域所指向的对象中查找。如果我们设置object.metatable.__index = object.metatable,这样就和javascript类似了。对于对象缺失的方法,会直接从metatable中查找。
3、对于userdata而言其什么方法都没有,因此对他的成员的访问都是通过访问metatable.__index来实现的(如果设置metatable.__index = metatable,那么访问userdata的成员就相当于访问userdata的metatable)。

使用metatable修改后的程序如下所示:

/* 将要注册的函数拆分为两部分
arraylib_f注册给全局变量array
arraylib_m注册给metatable,作为methord
*/
static const struct luaL_Reg arraylib_f[] = {
{"new", newarray},
{NULL, NULL}
};

static const struct luaL_Reg arraylib_m[] = {
{"set", setarray},
{"get", getarray},
{"size", getsize},
{NULL, NULL}
};

/* C函数的安装使用如下函数
整个函数中我们创建了一个匿名的metatable,后续注释中的metatable一词代指这个新建的metatable的实例
*/
static void install_func(lua_State *L)
{
luaL_newmetatable(L, "LuaBook.array"); //新建一个metatable,一方面压入堆栈,另一方面将metatable以"LuaBook.array"为key放入register中(register类似于一个全局哈希表)

lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3); //这三步的作用是将新建的metatable的__index字段赋值为他自己,相当于metatable.__index = metatable,尽是为了方便,否则还需要另外建立一个table2,并设置metatable.__index = table2
luaL_openlib(L, NULL, arraylib_m, 0); //将set、get、size这三个函数注册给metatable(给luaL_openlib函数传递NULL时,表示要操作的表已经放在堆栈中)

luaL_openlib(L, "array", arraylib_f, 0); //将new这个函数注册给全局变量array
}

static int newarray (lua_State *L){
int n = luaL_checkint(L, 1);
size_t nbytes = sizeof(NumArray) + n*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

luaL_getmetatable(L, "LuaBook.array");
lua_setmetatable(L, -2); //新建userdata的时候将register中名为LuaBook.array中metatable设置给新建的array对象,从此所有array共用一个metatable

a->size = n;
return 1;
}

static int setarray(lua_State *L) {
NumArray *a = (NumArray *)luaL_checkudata(L, -3, "LuaBook.array"); //调用函数的时候,除了检查参数是否为userdata之外,还要判断该userdata的metatable是否是"LuaBook.array",这样在使用io.stdin调用我们的函数时就可以检查出错误,不至造成Sagement fault
int index = luaL_checkint(L, -2);
double value = luaL_checknumber(L, -1);

luaL_argcheck(L, NULL != a, 1, "'array' expected");
luaL_argcheck(L, index >= 0 && index <= a->size, 1, "index out of range");

a->values[index - 1] = value;

return 0;
}

static int getarray(lua_State *L) {
NumArray *a = (NumArray *)luaL_checkudata(L, -2, "LuaBook.array"); //同样增加参数类型和metatable的检查
int index = luaL_checkint(L, -1);

luaL_argcheck(L, NULL != a, 1, "'array' expected");
luaL_argcheck(L, index >= 0 && index <= a->size, 1, "index out of range");

lua_pushnumber(L, a->values[index - 1]);

return 1;
}

static int getsize(lua_State *L) {
NumArray *a = (NumArray *)luaL_checkudata(L, -1, "LuaBook.array"); //同样增加参数类型和metatable的检查

luaL_argcheck(L, NULL != a, 1, "'array' expected");
lua_pushnumber(L, a->size);

return 1;
}

int main(int argc, char* argv[])
{
......
install_func(L); //使用install_func将函数注册到lua空间
......
}

对应Lua的例子变为:

a = array.new(1000)
print(a)
print(a:size())
for i=1,999 do
a:set(i, 1/i)
end

print ("Print first 10 elements")
for i=1,10 do
print(a:get(i))
end

文章中用到的示例程序:

最后给出一个main函数的例子,在这个函数中我们可以添加前面说到的示例代码从而组合出完整的示例程序。

int main(int argc, char* argv[])
{
char* filename;
double ret = 0;

//新建一个lua state
lua_State *L = luaL_newstate();

if (argc >=2 ){
filename = argv[1];
} else {
printf("usage: %s filename\n", argv[0]);
return 1;
}

//这个函数加载lua标准库
luaL_openlibs(L);

/* 在这里添加代码注册C语言实现的函数 */

/* 从指定的文件名加载lua代码(实际上代码被编译成chunk压入栈中) */
if (luaL_loadfile(L, filename)){
error(L, "cannot run file: %s", lua_tostring(L, -1));
}

/* 执行刚才读取的lua代码 */
if (lua_pcall(L, 0, 0, 0)){
error(L, "cannot run file: %s", lua_tostring(L, -1));
}

/* 如果调用lua函数,要放在这里 */

return 0;
}

写在最后:
本文假定读者对Lua的基本语法已经有了一定的了解。由于Lua是原型继承语言,和我们之前使用的基于类型的语言有些区别(倒是和Javascript类似,Javascript也是原型继承语言)。因此在开始学习的过程一定要跳出类和对象的思维才能真正理解Lua。最后推荐两个学习Lua的优秀材料:
官方手册:http://www.lua.org/manual/查起来很方便,如果用它来学习会很困难。
作者的著作:Programming in Lua,第一版免费基于Lua5.0的http://www.lua.org/pil/contents.html,网上可以搜到一个中译的版本,个人感觉学习足够了,当然也可以在amazon上购买英文第三版。

0 0