一、简介
和c语言通信的主要方法是一个无所不在的虚拟栈。所有的数据交换,无论是lua到c或者c到lua都是通过这个栈来完成,当Lua启动或调用c语言时,栈中至少会有20个空闲的槽,对于普通应用已经足够了。了解这个基本信息后才能更清楚的理解后面的API和实现。
要实现供Lua调用的C库,必须使用Lua提供的C API,需要包含如下头文件:
-
#include <lua.h>
-
#include <lualib.h>
-
#include <lauxlib.h>
头文件lua.h定义了Lua提供的基础函数,包括创建Lua环境、调用Lua函数(如lua_pcall)、读写Lua环境中全局变量、以及注册供Lua调用的新函数等。lua.h中定义的所有内容都有lua_前缀。
头文件lualib.h是Lua的标准库,为了使Lua保持小巧,所有的标准库都被组织到了不同的包中。头文件lualib.h中定义了打开这些库的函数。
头文件lauxlib.h定义了辅助库提供的函数,它的所有定义都以luaL_开头(如luaL_loadbuffer)。辅助库是一个使用lua.h中API编写出的一个较高的抽象层。
二、lua调用c库函数
介绍了基本信息后,我们开始构建能够提供给Lua调用的c库。
-
#define USRSTAT_META "usrstat"
-
#define USRSTAT_CLIENT_META "usrstat.clients"
-
-
LUALIB_API int luaopen_usrstat(lua_State *L)
-
{
-
luaL_register(L, USRSTAT_META, R_common);
-
luaL_newmetatable(L, USRSTAT_CLIENT_META);
-
luaL_register(L, NULL, R_common);
-
luaL_register(L, NULL, R_usrstat);
-
lua_pushvalue(L, -1);
-
lua_setfield(L, -2, "__index");
-
lua_setfield(L, -2, "clients");
-
return 1;
-
}
luaopen_***为库函数的入口,luaL_register函数为表注册函数,luaL_newmetatable函数创建一个新的元表,压入虚拟栈内,元表位于虚拟栈最顶端。后面再为新建的元表注册函数,当luaL_register第二个参数为NULL,则默认为虚拟栈顶端的表注册函数。
-
LUA_WRAP_STRING(clients,getClients)
-
LUA_WRAP_STRING(clients,getClientConnection)
-
-
static const luaL_reg R_usrstat[] = {
-
LUA_REG(clients,getClients),
-
LUA_REG(clients,getClientConnection),
-
{ NULL, NULL }
-
};
创建我们需要的函数,这里用到两个宏,LUA_REG和
LUA_WRAP_STRING
-
#define LUA_REG(type,op) \
-
{ #op, usrstat_L_##type##_##op }
-
-
#define LUA_WRAP_INT(type,op) \
-
static int usrstat_L_##type##_##op(lua_State *L) \
-
{ \
-
int rv; \
-
if( !type##_get_##op(&rv) ) \
-
lua_pushnumber(L, rv); \
-
else \
-
lua_pushnil(L); \
-
return 1; \
-
}
-
-
#define LUA_WRAP_STRING(type,op) \
-
static int usrstat_L_##type##_##op(lua_State *L) \
-
{ \
-
char rv[USRSTAT_BUFSIZE]; \
-
memset(rv, 0, USRSTAT_BUFSIZE); \
-
if( !type##_get_##op(L, rv) ) \
-
lua_pushstring(L, rv); \
-
else \
-
lua_pushnil(L); \
-
return 1; \
-
}
-
-
#define LUA_WRAP_STRUCT(type,op) \
-
static int usrstat_L_##type##_##op(lua_State *L) \
-
{ \
-
return usrstat_L_##op(L, type##_get_##op); \
-
}
LUA_REG,用来注册函数,LUA_WRAP_INT,LUA_WRAP_STRUCT和LUA_WRAP_STRING
则用于声明函数。
现在我们需要一个供lua调用的函数,这个函数的功能是在页面显示终端的基本信息,我们实现如下。
-
static int clients_get_getClients(lua_State *L, char *buf)
-
{
-
getAllUserInfo(buf);
-
return 0;
-
}
通过LUA_WRAP_STRING宏可以知道,最终是通过lua_pushstring
(L
, rv
);这一行来实现将字符串压入虚拟栈。而上面函数的buf指针就是预先分配好的一个数组,通过对这个数组赋值,并且最后通过lua_pushstring压栈,页面就能通过lua读取到c函数提供的信息。页面调用如下:
-
<%-
-
local usrstat = require "usrstat"
-
local clients = usrstat.clients.getClients()
-
-%>
htm页面中通过<% %>分隔出语句块,通知解释器这个语句块中的语言要用lua解释,lua通过require “usrstat”获取所需的表,再通过usrstat.clients.getClients()函数调用获取所需信息。
三、lua带参数调用
如果c库函数需要通过页面的某个值来决定返回值,就需要lua在调用c库函数的时候能将参数传下去,前面已经说过lua直接获取底层信息是通过c函数将值压入虚拟栈中提供给lua的,同理,lua将参数传给c函数也必须使用虚拟栈传递的方法。首先看页面的调用方式。
-
<%-
-
local usrstat = require "usrstat"
-
connection = usrstat.clients.getClientConnection(clientInfo)
-
-%>
可以看出与前面的区别在于调用函数的时候带入了参数,这个参数在lua调用c库函数时,就位于虚拟栈的顶端,然后我们再看c函数是怎么取出这个参数的。
-
static int clients_get_getClientConnection(lua_State *L, char *buf)
-
{
-
const char *ip = NULL;
-
-
if (lua_gettop(L)==1 && lua_isstring(L, 1))
-
{
-
ip = lua_tostring(L, 1);
-
getClientConnection(ip, buf);
-
return 0;
-
}
-
-
return 1;
-
}
首先判断参数合法性,lua_gettop函数取出所有参数的个数,我们需要的是字符串类型的参数,所有通过lua_isstring函数进行判断。参数合法后,就可以通过lua_tostring函数取出位于栈顶的参数。
四、总结
本文主要介绍了openwrt中luci页面调用c库函数的方法,无论是c库函数传值到页面还是页面传值到c函数,参数的传递都是通过lua提供的虚拟栈实现的。本文没有对lua的c api多做介绍,一方面是因为网络资源丰富,应该很容易就能找到相关的资料,另外因为lua语言还在发展,很多老版本的api在新版本中已经不存在。所以重点在于理解lua和c之间的调用方式。理解工作方式后再去结合api,相信就能解决多数问题。
阅读(3310) | 评论(0) | 转发(0) |