天天看點

【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參考手冊