C语言开发lua模块入门 --- 虚拟栈和基本代码结构
程序员文章站
2022-05-12 20:33:45
lua是一门小巧高效的脚本语言,核心代码不到500kb,由于要保持小巧,所以lua的核心库功能不可能很复杂,只实现一些基本功能,甚至没有操作目录的API,但由于lua良好的设计,并...
lua是一门小巧高效的脚本语言,核心代码不到500kb,由于要保持小巧,所以lua的核心库功能不可能很复杂,只实现一些基本功能,甚至没有操作目录的API,但由于lua良好的设计,并且是用标准C开发,所以很容易用C语言来扩展lua的功能。
用C编写lua模块需要遵循一定的规则,由于C和lua中的数据结构是完全不一样的,并且lua函数可以返回多个结果,所以要有一种在lua脚本和C函数之间交换数据的机制,这个机制就是虚拟栈,这个栈可以储存任何类型的数据,供lua调用的C函数从这个栈中获取调用参数,然后将结果再压入栈中返回到lua,这个虚拟栈用索引来操作,栈底(第一个入栈)的元素索引为1, 第二个入栈的索引为2,依次类推...,这个虚拟栈也支持逆向索引,即栈顶(最后一个入栈)的元素索引为-1,倒数第二个入栈的为-2,依次类推,这两种索引方式依据实际情况都很常用。
lua的C API提供了很多的从栈中读取数据以及向栈中添加数据的基本方法,下面列出一些常用方法:
void lua_pushboolean (lua_State *L, int b); //向栈中压入一个bool值 void lua_pushinteger (lua_State *L, lua_Integer n); //压入一个整数 void lua_pushnil (lua_State *L); //压入一个nil void lua_pushnumber (lua_State *L, lua_Number n); //压入一个双精度数 void lua_pushstring (lua_State *L, const char *s); //压入一个字符串 int lua_toboolean (lua_State *L, int index); //从栈中读取一个bool值,index为读取的数据在栈中的索引 lua_Integer lua_tointeger (lua_State *L, int index); //读取一个整数 lua_Number lua_tonumber (lua_State *L, int index); //读取一个浮点数 const char *lua_tostring (lua_State *L, int index); //读取一个字符串
以上方法第一个参数都是 lua_State *L, 我们可以认为该参数代表了虚拟栈, int index是需要读取的元素在栈中的索引。
由于为lua编写的C函数从虚拟栈中读取参数,然后再把返回结果添加到栈中,所以为lua编写的C函数都只需要一个参数,就是这个虚拟栈 lua_State *L, 下面是C函数的函数原型:
int funcname(lua_State *L);
int类型的返回值表示该函数返回值的个数,下面是一个求两个数和的C函数示例:
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表示该方法只有一个返回值 }
函数写好了,那lua怎么才能调用该函数呢,第一种方法是使用lua_pushcfunction方法在lua的源码中直接注册这个函数,然后重新编译lua就可以使用这个方法了,第二种方法把这个函数编译进一个lua C module,然后放入lua加载C模块的路径中,代码中require这个模块即可。由于第一种方法需要修改源码,并且每次修改C函数都需要重新编译lua,显然很麻烦,所以这里主要介绍第二种方法。
编写C模块有固定的代码结构:
//加载lua头文件 #include //编写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表示该方法只有一个返回值 } //声明一个luaL_Reg结构体数组 static const struct luaL_Reg funcs[] = { {"sum", sum}, //该结构体第一个成员是一个字符串,表示C模块中该函数的名称,第二个成员是指向该函数的指针 {NULL, NULL} // 该数组最后一个元素始终是 {NULL, NULL} }; //最后注册这个数组,并给这个C模块定义一个名字,注意,这个函数名不是固定的,如果你的模块名叫xxx,那么这个函数名就应该是 luaopen_xxx int luaopen_clib(lua_State *L) { luaL_register(L, "clib", funcs); //第二个参数要和你的模块名一致 return 1; }
把这个C文件编译成动态链接库,动态链接库的文件名命名规则为 xxx.so, 其中xxx就是你在C文件中定义的模块名
gcc -fPIC -c clib.c -I/usr/include # -I指定lua头文件的位置 gcc -shared clib.o /usr/lib64/liblua-5.1.so -o clib.so # /usr/lib64/liblua-5.1.so 为你的lua库路径,根据你的实际路径修改
把编译好的clib.so放到你的lua C模块路径下,那怎么知道自己lua加载C模块的路径呢,在lua脚本中打印一下 package.cpath 就可以获取到这个路径
[root@localhost lua-c-module]# lua Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio > print(package.cpath) ./?.so;/usr/lib64/lua/5.1/?.so;/usr/lib64/lua/5.1/loadall.so >可以看到 我这里的cpath路径有当前目录 ./ 和 /usr/lib64/lua/5.1/ , 所以 我把 clib.so 移动到/usr/lib64/lua/5.1/ 目录下
编写lua测试脚本
local clib = require "clib" local s = clib.sum(3, 99) print(s)
执行结果:
[root@localhost lua]# lua test.lua 102
这样,一个简单的clib模块就开发好了,是不是也很简单呢,想要更深入的了解lua C模块的编写技术,欢迎关注后续文章:)。