在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
最近在进入Lua编程的状态,一度令我困惑的是,Lua提供的功能少的可怜,跟自备电池的python相比,可说是简陋了。连table的打印,都需要自己实现,也因此有了一打的第三方方案。后来我想明白了,以Lua和C如此紧密的关系,只需要建立Lua的binding,那么丰富而性能强大的C库资源完全可以为Lua所用,这样就不愁功能缺失了。
关于C调用Lua,前段时间已经写过一篇短文了:《多语言协作与二进制交互》,现在补上从Lua调用C的这一篇。先上一段Lua代码: #!/usr/bin/lua require("power") print(square(1.414213598)) print(cube(5)) 在这段代码里,我引用了一个power模块,并且调用了power模块里的square函数和cube函数。由此可知,在C语言编写的power模块里面,需要有相关的函数可以注册本模块提供的函数,并且让Lua可以“感知”到自身是一个模块。回忆一下Lua的路径搜索,可以看到除了后缀名为*.lua的文件之外,还有*.so文件,所以C扩展是编译为.so的。实际情况是,在执行require语句的时候,系统会调用luaopen_power函数,这个函数名是通过luaopen_与power这个模块名拼接得到的。
现在看看luaopen_power函数的定义: int luaopen_power(lua_State *L){ lua_register( L, /* Lua 状态机 */ "square", /*Lua中的函数名 */ isquare /*当前文件中的函数名 */ ); lua_register(L,"cube",icube); return 0; } 可以看到,通过调用lua_register函数,我们在L这个lua虚拟机里面注册了两个函数,一个是square,一个是cube,他们分别对应到isquare和icube这两个C函数。
回过头来看一看,lua脚本里,这条简单的require语句,执行了两个步骤:一是先把名字为power.so的文件加载起来,二是调用其中的luaopen_power函数。下面来看一下具体的函数如何定义: static int isquare(lua_State *L){ /* C中的函数名 */ float rtrn = lua_tonumber(L, -1); /* 从Lua虚拟机里取出一个变量,这个变量是number类型的 */ printf("Top of square(), nbr=%f\n",rtrn); lua_pushnumber(L,rtrn*rtrn); /* 将返回值压回Lua虚拟机的栈中 */ return 1; /* 这个返回值告诉lua虚拟机,我们往栈里放入了多少个返回值 */ } 注释的讲解比较详细了,可以看到为Lua定义的C函数格式都是比较统一的,首先接受一个Lua虚拟机变量L,然后从L里取出相应的参数(需要指定数据类型),最后将返回值再次压回虚拟机里面,通过返回int告诉Lua虚拟机,自己的返回值有多少个。
好,到这里就只差最后一步了,现在把这段C代码编译成.so文件。使用如下的编译参数: ubuntu@ubuntu:~$ gcc -Wall -shared -fPIC -o power.so -I/usr/include/lua5.1 -llua5.1 hellofunc.c (hellofunc.c记得要include lua.h, lauxlib.h, lualib.h三个头文件哦) 这里解释一下两个参数的意思,-shared是告诉gcc,需要编译成.so文件,并且这个源文件里面不会有main函数,不要大惊小怪。另一个-fPIC,是Position Independent Code的意思,具体的含义可以参考这篇,主要用来避免同一份代码因为重定位位置不同而在内存中存在多个实例。 调用的结果: ubuntu@ubuntu:~$ ./hellofunc.lua Top of square(), nbr=1.414214 2.0000002687177 Top of cube(), number=5.000000 125 当然,如果机器安装有多个版本的Lua,需要指定执行hellofunc.lua的lua解释器版本。因为本篇教程是针对Lua5.1的,所以需要指定Lua5.1来执行,例如lua5.1 hellofunc.lua,否则用lua5.2调用会引起core dump |
请发表评论