上一篇文章简单介绍了在Lua中如何调用C,其中的原理还是需要稍微深究一下。
文章参考自:Lua和C交互的简易教程(HansChen的博客)
C/C++与Lua交互的基础源于虚拟栈。在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶
一个简单的例子:
#include <lua.h>
#include <stdio.h>
#include "lauxlib.h"
int main()
{
lua_State *L = luaL_newstate();
lua_pushstring(L, "mick");
lua_pushnumber(L, 20);
printf("stack size: %d\n", lua_gettop(L));
const char *str = luaL_checklstring(L, 1, NULL);
int data = luaL_checknumber(L, 2);
printf("%s %d\n", str, data);
return 0;
}
不同于上次编译动态库,这次直接编译可执行文件,缺少一些编译选项就会报错。我遇到的:
1、少 liblua.a 时:
/tmp/ccM31DcG.o: In function `main':
lua_test.c:(.text+0x9): undefined reference to `luaL_newstate'
lua_test.c:(.text+0x1e): undefined reference to `lua_pushstring'
lua_test.c:(.text+0x3d): undefined reference to `lua_pushnumber'
lua_test.c:(.text+0x53): undefined reference to `luaL_checklstring'
lua_test.c:(.text+0x68): undefined reference to `luaL_checknumber'
collect2: error: ld returned 1 exit status
2、少 -lm 时:
object.c:(.text+0xeb): undefined reference to `fmod'
lobject.c:(.text+0x111): undefined reference to `pow'
lobject.c:(.text+0x125): undefined reference to `floor'
/usr/local/lib/liblua.a(ltable.o): In function `luaH_get':
ltable.c:(.text+0x623): undefined reference to `floor'
/usr/local/lib/liblua.a(ltable.o): In function `luaH_newkey':
ltable.c:(.text+0xcc9): undefined reference to `floor'
/usr/local/lib/liblua.a(lvm.o): In function `tointeger_aux':
lvm.c:(.text+0x7a): undefined reference to `floor'
/usr/local/lib/liblua.a(lvm.o): In function `luaV_execute':
lvm.c:(.text+0x1a2e): undefined reference to `floor'
lvm.c:(.text+0x1ead): undefined reference to `pow'
lvm.c:(.text+0x2000): undefined reference to `fmod'
collect2: error: ld returned 1 exit status
3、少 -ldl 时:
/usr/local/lib/liblua.a(loadlib.o): In function `lookforfunc':
loadlib.c:(.text+0x502): undefined reference to `dlsym'
loadlib.c:(.text+0x549): undefined reference to `dlerror'
loadlib.c:(.text+0x576): undefined reference to `dlopen'
loadlib.c:(.text+0x5ed): undefined reference to `dlerror'
/usr/local/lib/liblua.a(loadlib.o): In function `gctm':
loadlib.c:(.text+0x781): undefined reference to `dlclose'
collect2: error: ld returned 1 exit status
所以可行的编译为:
gcc source.c -o target /usr/local/lib/liblua.a -lm -ldl
返回如下:
stack size: 2
mick 20
Lua调用C动态库
对栈有了一定了解后,我们再次开始Lua调用C的例子,这回可以有返回值。
注意一点:所有的函数必须接收一个lua_State作为参数,同时返回一个整数值,该整数值表示函数的返回值。这个函数使用Lua栈作为参数,它可以从栈里面读取任意数量和任意类型的参数。当函数开始的时候, lua_gettop(L) 可以返回函数收到的参数个数。 第一个参数(如果有的话)在索引 1 的地方, 而最后一个参数在索引 lua_gettop(L) 处。 当需要向 Lua 返回值的时候, C 函数只需要把它们以正序压到堆栈上(第一个返回值最先压入), 然后返回这些返回值的个数。 在这些返回值之下的,堆栈上的东西都会被 Lua 丢掉。
例子如下:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
static int test(lua_State *L)
{
int num = luaL_checkinteger(L, 1);
const char* str = luaL_checklstring(L, 2, NULL);
printf("come from test: num = %d str = %s\n", num, str);
lua_pushnumber(L, 10);
lua_pushstring(L, "mick");
return 2;
}
int luaopen_myLib(lua_State *L)
{
luaL_Reg l[] =
{
{"test", test},
{NULL, NULL}
};
luaL_newlib(L, l);
return 1;
}
调用如下:
local reta, retb = myLib.test(666, "cxl")
print("ret val:", reta, retb)
打印如下:
rom test: num = 666 str = cxl
ret val: 10.0 mick
C调用Lua
事先准备lua文件:
str = "my lua lib"
person = {name = "cxl", age = 26}
function add(x, y)
return x + y
end
function addName(person, newName)
person.name = newName
return person
end
function display()
for k, v in pairs(person) do
print(k .. ": " .. v)
end
end
然后是 c 文件:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
lua_State* load_lua(char *luaPath) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
//加载脚本并运行
if (luaL_loadfile(L, luaPath) || lua_pcall(L, 0, 0, 0)) {
printf("load Lua failed: %s\n", lua_tostring(L, -1)); //发生错误时,有关错误的提示信息会被压入栈顶
return NULL;
}
return L;
}
int main() {
char *luaFile = "mylib.lua";
lua_State *L = load_lua(luaFile);
if (NULL == L) {
return -1;
}
printf("stack size: %d\n", lua_gettop(L));
lua_getglobal(L, "str");
printf("%s\n", luaL_checklstring(L, -1, NULL));
lua_getglobal(L, "person");
lua_getfield(L, -1, "name");
printf("name: %s\n", luaL_checklstring(L, -1, NULL));
lua_getfield(L, -2, "age"); // 由于将 "name" 对应的值压栈,table的索引变成了-2
printf("age: %d\n", luaL_checkinteger(L, -1));
//=================== 栈顶 ===================
// 索引 类型 值
// 4 int: 26
// 3 string: cxl
// 2 table: person
// 1 string: cxl
//=================== 栈底 ===================
lua_getglobal(L, "add");
lua_pushnumber(L, 3);
lua_pushnumber(L, 4);
printf("stack size: %d\n", lua_gettop(L));
if (lua_pcall(L, 2, 1, 0)) { // 函数调用完毕,函数与参数出栈
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
int result = luaL_checkinteger(L, -1);
printf("result: %d\n", result);
printf("stack size: %d\n", lua_gettop(L));
lua_pushnumber(L, 165);
lua_setfield(L, 2, "height"); // 栈顶元素被设置到table的"height",同时出栈
printf("stack size: %d\n", lua_gettop(L));
lua_getglobal(L, "display");
if (lua_pcall(L, 0, 0, 0)) {
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
lua_getglobal(L, "addName");
lua_newtable(L); // 建立一个新表
lua_pushstring(L, "mzh");
if (lua_pcall(L, 2, 1, 0)) {
printf("lua call failed: %s\n", luaL_checklstring(L, -1, NULL));
return -1;
}
lua_getfield(L, -1, "name");
printf("name: %s\n", luaL_checklstring(L, -1, NULL));
lua_close(L);
return 0;
}
输出显示为:
stack size: 0
my lua lib
name: cxl
age: 26
stack size: 7
result: 7
stack size: 5
stack size: 5
name: cxl
age: 26
height: 165.0
name: mzh
需要注意的是:栈操作是基于栈顶的,默认情况下它只会去操作栈顶的值。
举个例子,函数调用流程是先将函数入栈,参数入栈,然后用lua_pcall调用函数,此时栈顶为参数,栈底为函数,所以栈过程大致会是:参数出栈->保存参数->函数出栈->调用函数->返回值入栈
类似的还有lua_setfield,设置一个表的值,首先将值出栈,保存,再去给表赋值。
有了以上基础,我们尝试在lua调用c,并将table传入c,由c代码处理。
c代码:
#include <lua.h>
#include <lauxlib.h>
#include <stdio.h>
static int test(lua_State *L)
{
lua_pushnumber(L, 26);
lua_setfield(L, 1, "age");
return 1;
}
int luaopen_myLib(lua_State *L)
{
luaL_Reg l[] =
{
{"test", test},
{NULL, NULL}
};
luaL_newlib(L, l);
return 1;
}
lua调用:
package.cpath = "./?.so"
local myLib = require "myLib"
person = {name = "cxl"}
person = myLib.test(person)
for k, v in pairs(person) do
print(k .. ": " .. v)
end
结果如下:
age: 26.0
name: cxl
更多更详细的函数介绍见:Lua5.3参考手册