天天看点

【Lua】Lua与C交互

上一篇文章简单介绍了在Lua中如何调用C,其中的原理还是需要稍微深究一下。

文章参考自:Lua和C交互的简易教程(HansChen的博客)

C/C++与Lua交互的基础源于虚拟栈。在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶

【Lua】Lua与C交互

一个简单的例子:

#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参考手册