lua是一种可扩展的语言,因为它的解释器是C的一个应用程序。这就允许了C可以调用lua写的库,lua也可以调用C为它扩展的库;网络游戏中常用的是lua作为C的插件来使用的。本文主要是针对lua为C写插件的用法做一个小的总结。
需要用到的头文件:
lua.h: 定义了lua的基础函数,包括创建lua环境、调用lua函数,读写lua环境中全局变量,以及注册供lua调用的新函数。
lauxlib.h:定义了辅助库(auxiliary library, auxlib)提供的函数。它的所有定义都是以luaL_开头的。
lualib.h:定义了打开标准库的函数。
C月lua的数据交互主要依靠的是lua的虚拟栈。虚拟栈的存在是为了解决lua的垃圾回收机制;几乎所有的API都会用到这个虚拟栈;这个栈也严格遵守先进后出的原则。
查询这个栈的数据一般分为从头部查找和从尾部查找两种方式。 默认情况下top的指针是指向索引为0的位置的,所以如果要从头开始查找的话一般用负数表示栈顶的元素,如-1表示栈顶,-2表示栈顶下方的一个元素;如果要从尾部开始查找的话,一般用正数表示;因为第一个压入栈的原素索引为1,即栈底的元素索引为1,第二个压入栈的元素索引为2,以此类推。
在为C写插件的时候,需要做的事情有:
1. 打开lua
2. 打开lua的标准库
3. 调用lua的函数
4. 传参数
5. 获取结果(结果会自动保存在虚拟栈中),将结果从虚拟栈中弹出。
下面是一个用lua为C写配置文件的一个例子:
config.lua是协议对应的数字的表:
#!/usr/bin/lua
protocols={
HOPOPT = 0,
ICMP = 1,
IGMP = 2,
GGP = 3,
IP = 4,
ST = 5,
TCP = 6,
CBT = 7,
EGP = 8,
IGP = 9
}
protocols={
HOPOPT = 0,
ICMP = 1,
IGMP = 2,
GGP = 3,
IP = 4,
ST = 5,
TCP = 6,
CBT = 7,
EGP = 8,
IGP = 9
}
lua_protocol.lua负责将传入进来的字符串如"protocol=tcp"解析并查表得到tcp对应的值并传回:
#!/usr/bin/lua
function lua_protocol(str)
local index = string.find(str, "=")
local p_t = {}
local res;
if index then
local _, _, key, value = string.find(str, "(%a+)%s*=%s*(%w+)")
p_t.key = key
p_t.value = value
else
p_t.value = value
end
if string.find(p_t.value, "%a+") then
local t = p_t.value
if assert(loadfile("configs.lua")) then
dofile("configs.lua")
res = protocols[""..t..""]
else
res = nil
end
elseif string.find(p_t.value, "%d+") then
if tonumber(p_t.value)>255 then
res = nil
end
end
return res;
end
local index = string.find(str, "=")
local p_t = {}
local res;
if index then
local _, _, key, value = string.find(str, "(%a+)%s*=%s*(%w+)")
p_t.key = key
p_t.value = value
else
p_t.value = value
end
if string.find(p_t.value, "%a+") then
local t = p_t.value
if assert(loadfile("configs.lua")) then
dofile("configs.lua")
res = protocols[""..t..""]
else
res = nil
end
elseif string.find(p_t.value, "%d+") then
if tonumber(p_t.value)>255 then
res = nil
end
end
return res;
end
可以看到lua_protocol的方法做了一个字符串个匹配,找到”tcp“这个字符串,然后再查configs.lua中的表;这里用到了访问lua的table的一种方法,如
local a = {};
a.k="hello"
a.k和a['k']都是可以正确访问的。
lua_protocol方法中表中原素是变量,所以可以用第二种访问表的方法取到值。
以下是C中调用lua的方式:
get_protocols函数实现了调用config.lua文件,返回从lua文件中获取传入的协议所对应的号码。
int get_protocols(lua_State *l, char * str)
{
int dofile;
int res;
dofile = luaL_dofile(l, "lua_protocol.lua");
if(dofile == 0){
lua_getglobal(l, "lua_protocol");
lua_pushstring(l, str);
lua_call(l, 1, 1);
res = lua_tonumber(l, -1);
lua_pop(l, 1);
}else{
res = -1;
}
return res;
}
{
int dofile;
int res;
dofile = luaL_dofile(l, "lua_protocol.lua");
if(dofile == 0){
lua_getglobal(l, "lua_protocol");
lua_pushstring(l, str);
lua_call(l, 1, 1);
res = lua_tonumber(l, -1);
lua_pop(l, 1);
}else{
res = -1;
}
return res;
}
以上的函数中虚拟栈l实现了数据传递的功能,它是lua_State类型的;可以看到以上所用的所有lua的API函数都需要传入这个虚拟栈。
luaL_dofile(l, "lua_protocol.lua"),dofile是解释并调用lua_pcall执行传入的lua文件,如果被正确执行返回0,否则返回非0值,且错误的消息被压入栈,可以通过访问栈顶来获取错误消息。
lua_getglobal(l, "lua_protocol"); 将lua_protocol方法压入栈中,因为之前的栈是空的,所以这个方法在栈底,也在目前的栈顶。
lua_pushstring(l, str); 将str作为lua_protocol的参数传递进去。
lua_call(l, 1, 1); 调用方法,这个函数需要传入虚拟栈,参数的个数,以及返回的结果的个数。
res = lua_tonumber(l, -1); 从栈顶取得返回值
lua_pop(l,1); 清空栈。
lua_pop的定义原型是:
#define lua_pop(L, n) lua_settop(L, -(n)-1)
lua_pop(l,1), 即把栈顶的指针指向了栈底
main函数中调用get_protocols方法之前需要打开lua的库,代码如下:
int main()
{
int int_protocol;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
int_protocol = get_protocols(L, "protocol=TCP");
if(int_protocol == -1){
printf("param is not valid!\n");
}else{
printf("protocol is :%d\n", int_protocol);
}
lua_close(L);
return 0;
}
最后需要关闭lua。
就这么简单!