最近正在做一个项目,web前端用的脚本是lua和javascript写的,但是后台却是是用C/C++写。那时比较纠结的是,这个lua是怎么调用C的,后来慢慢研究,并从网上了解到一些资料,还特意去学习了lua脚本,当然这是边工作边学的。学了大概2个多礼拜,终于有所成就。好了,下面说说lua是怎么调用C的。
了解过Lua的朋友都知道Lua采取的是利用栈进行交互,利用各种Lua_pushXXX将不同的值压入栈中,然后调用Lua脚本时自然会退栈取出参数运行,对于Lua的虚拟机来说,就好像是发生了一次正常的函数调用。(特别注意是这里的栈,是lua栈,不是C栈)
下面一段关于Lua的代码(test.lua):
require("add")
local a =5
local b = 4;
print(sum(a, b))
在这段代码里,开头首行require函数(关于require函数的使用,会在另一篇文件介绍)加载了一个add模块,并且调用了add模块里的sum函数。由此可知,在C语言编写的add模块里面,需要有相关的函数可以注册本模块提供的函数,并且让Lua可以“感知”到自身是一个模块。回忆一下Lua的路径搜索,可以看到除了后缀名为*.lua的文件之外,还有*.so文件,所以C扩展是编译为.so的。但实际情况是,在执行require语句的时候,系统会调用luaopen_add函数,这个函数名是通过luaopen_与add这个模块名连接而得到的。
以下是luaopen_add函数的定义:
int luaopen_add(lua_State *L){
lua_register( L, /* Lua 状态机 */
"sum", /*Lua中的函数名 */
is_sum /*当前文件中的函数名 */
);
return 0;
}
通过上面的例子可以看到,调用lua_register函数,在L这个lua虚拟机里面注册了一个函数sum,而对应的是is_sum C函数。
回过头来看一看,在lua脚本里,这条简单的require语句,执行了两个步骤:一是先把名字为add.so的文件加载起来,二是调用其中的luaopen_add函数。
下面来看一下具体的函数如何定义:
static int is_sum(lua_State *L){ /* C中的函数名 */
float a = lua_tonumber(L, -1); /* 从Lua虚拟机里取出一个变量,这个变量是number类型的 */
float b = lua_tonumber(L, 0);
lua_pushnumber(L, a+b); /* 将返回值压回Lua虚拟机的栈中 */
return 1; /* 这个返回值告诉lua虚拟机,我们往栈里放入了多少个返回值 */
}
C函数首先接受了一个Lua虚拟机变量L,然后从L里取出相应的参数(需要指定数据类型),最后将返回值再次压回虚拟机里面,通过返回int告诉Lua虚拟机,自己的返回值有多少个。
接下来是把这段C代码编译成.so文件。使用如下的编译参数
pp@pp-virtual-machine:~$ gcc -shared -fPIC -o add.so -I/usr/include/lua5.2 -llua5.2 addfun.c
特别要注意:addfun.c要加上include lua.h, lauxlib.h, lualib.h三个头文件,这是调用lua里面的函数。
另外解释一下两个参数的意思,-shared是告诉gcc,需要编译成.so文件(动态库),还有这个源文件里面是没有main函数的,特别是要注意。另一个-fPIC,是Position Independent Code的意思,主要用来避免同一份代码因为重定位位置不同而在内存中存在多个实例。具体含义可以参考这个网址:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1285426&page=1#pid9432189.
最后运行的结果:
pp@pp-virtual-machine:~$ lua test.lua
9