如何在Lua與C/C++之間實現table數據的交換

    以前在《C/C++和Lua是如何進行通訊的?》一文中簡單的介紹了lua與宿主之間的通訊。簡單的說兩種不一樣的語言之間數據類型不同又如何進行數據交換呢?那就是lua_State虛擬棧,經過棧操做和lua庫函數,咱們很輕鬆就能完成二者之間的數據交換。c++

    開始以前,明確幾個問題,lua中的虛擬棧的索引編號問題(咱們假設棧大小爲n),編號1是棧底,n視棧頂,編號-1是棧頂,-n是棧底。lua中的庫函數須要訪問和操做棧上的數據都是經過索引編號定位的。可是咱們須要明確一點,有些API並無使用索引編號做爲參數,意味着默認對棧頂進行操做。如lua_pushnumber(L, 66)將數值66壓入棧頂,lua_tonumber(L, -1)取編號-1(棧頂)元素等等,若是這些基本知識和API的都已經熟悉了,那麼lua與宿主之間的數據交換就很容易理解了。
git

    姿式準備好了,那麼問題來了。需求:咱們如今要設計一個UI界面,咱們但願這個UI是能夠重用的。爲了知足這個需求,顯然咱們必須將UI界面與顯示數據分離。使用lua初始化數據後,將數據傳遞給UI界面而後顯示。這樣若是需求變動(遊戲開發中常常產生這樣的需求),咱們也只需改變lua腳本就能重用UI界面,聽上去真是程序猿的福音啊~~
github

    用於顯示UI的數據一定不少,須要使用lua中的table來封裝這些數據,如今給定以下lua table數據:
shell

local tTest = 
{
	gdp = 1234,
	info = "this is test about exchange table data!",
	task = {12, 23, 34, 45},
};

咱們的腳本將調用一個程序封裝好的c API(TestTable函數),而後將tTest做爲參數,壓入虛擬棧中,以下:api

local tRet = TestTable(tTest);

雖然tTest table已經傳給了程序,咱們還須要對TestTable這個c API進行定製,使它可以正確的理解這個table中的數據,實現代碼以下(LuaTestTable函數類型是lua_CFuntion類型,註冊到lua虛擬機中的函數名爲TestTable):函數

int LuaTestTable(lua_State* L)
{
	printf("stack size = %d\n", lua_gettop(L)); //打印棧中元素的個數

	lua_pushstring(L, "gdp");            //將gdp字符串壓入棧頂
	//根據棧頂的key獲取table中的value,將key(這裏的「gdp」)移除,再將value壓入棧頂
	lua_gettable(L, 1);                      
	printf("%s\n", lua_tostring(L, -1)); //取棧頂元素(注意這裏的整型值都是string類型)
	lua_pop(L, 1); //取完以後清理棧頂
	printf("stack size = %d\n", lua_gettop(L)); //打印棧中元素的個數

	lua_pushstring(L, "info");   //同上
	lua_gettable(L, 1);
	printf("%s\n", lua_tostring(L, -1));
	lua_pop(L, 1);
	printf("stack size = %d\n", lua_gettop(L));

	lua_pushstring(L, "task"); //這裏的value值是一個table哦,不要緊棧操做都是同樣的
	lua_gettable(L, 1);
	for (int i = 0; i < 4; ++i)
	{
		lua_pushnumber(L, i+1);
		lua_gettable(L, -2);
		printf("%s\n", lua_tostring(L, -1));
		lua_pop(L, 1);
	}
	lua_pop(L, 1);
	printf("stack size = %d\n", lua_gettop(L)); //到這裏tTest表依然在棧底,但不影響後面的操做。
	
	//------華麗的分割線------------//
	//到這裏table數據的解析就結束了,如下內容是c API給lua返回table數據

	lua_newtable(L);//要給lua腳本返回一個table類型,先要new一個,壓入棧頂
	lua_pushnumber(L, 1); //將key先壓入棧
	lua_pushstring(L, "table2lua"); //再將value壓入棧
	lua_settable(L, -3);//settable將操做-2,-1編號的鍵值對,設置到table中,並把key-value從棧中移除

	lua_pushstring(L, "key"); //同上
	lua_newtable(L); //這裏有個子table
	lua_pushstring(L, "capi");
	//這裏的value類型使用lua_CFunction類型,可用作c API調用,函數實現請參看附錄1
	lua_pushcfunction(L, LuaSayHello); 
	lua_settable(L, -3);
	lua_pushnumber(L, 2);
	lua_pushnumber(L, 10086);
	lua_settable(L, -3);
	lua_settable(L, -3); //這個從這裏「lua_pushstring(L, "key"); //同上」開始匹配的
	printf("stack size = %d\n", lua_gettop(L));
	return 1; //返回棧頂1個元素
}

須要說明的是在lua中tTest["gdp"]和tTest.gdp的調用形式是同樣的,這是lua的語法糖。固然操做棧中的table方法除了lua_gettable和lua_settable還有其它方法,請參看lua_rawget和lua_rawset。理解棧中的元素變化是很是重要的。this

    LuaTestTable函數API的後面部分介紹了構造一個任意table做爲返回值,返回給lua腳本。首先使用lua_newtable庫函數新建一個table類型的數據,並壓入棧。而後將鍵值對key-value依次壓入棧,調用lua_settable(L, index)將key-value設置到table中,子table操做也是同樣的(這裏的index指的是要設置的table在棧中的索引編號)。lua

    好了,基本介紹完了,最後來編寫腳本,看看效果(如下是程序調用的腳本):
spa

--file: test.lua
local tTest = {
	gdp = 1234,
	info = "this is test about exchange table data!",
	task = {12, 23, 34, 45},
};
local tRet = TestTable(tTest);
printTable(tRet);    //實現請參看附錄2
tRet.key.capi(); //實現請參看附錄1

TestTable成功解析tTest的數據,而且返回一個table類型的tRet。.net

printTable(tRet);

printTable簡單的實現了一個table打印的腳本將tRet打印輸出。

tRet.key.capi();

腳本調用tRet中返回的c API類型的函數。具體實現請參見附錄。

運行結果:

stack size = 1  //如下是LuaTestTable的輸出
1234
stack size = 1
this is test about exchange table data!
stack size = 1
12
23
34
45
stack size = 1   
stack size = 2   //如下是printTable的輸出
{
  [1] = table2lua
  [key] = 
  {
    [2] = 10086
    [capi] = function: 0x409795
  }
}
Lua call c/c++:SayHello() //這裏是 [capi] = function: 0x409795被調用的輸出
Hello Everyone!

綜上述,咱們只須要修改tTest中的數據(結構不能改,改了須要修改LuaTestTable函數)就能改變UI界面的顯示,成功的解決了UI界面的複用問題。(文中沒有具體講到如何UI截面相關的細節,請參照例子自行腦補。)

附錄1:

//註冊到lua虛擬機中的c API函數
int LuaSayHello(lua_State* L)
{
	printf("Lua call c/c++:SayHello()\n");
	printf("Hello Everyone!\n");
	return 0;
}

附錄2:

//腳本函數實現打印一個table
function printTable(t, n)
	if "table" ~= type(t) then
		return 0;
	end
	n = n or 0;
	local str_space = "";
	for i = 1, n do
		str_space = str_space.."  ";
	end
	print(str_space.."{");
	for k, v in pairs(t) do
		local str_k_v = str_space.."  ["..tostring(k).."] = ";
		if "table" == type(v) then
			print(str_k_v);
			printTable(v, n + 1);
		else
			str_k_v = str_k_v..tostring(v);
			print(str_k_v);
		end
	end
	print(str_space.."}");
end

完整項目託管在github上: https://github.com/xlplbo/Lua2Game.git(支持vs2013和cmake編譯)

轉載請申明出處,若有任何疑問或建議指出,謝謝~

相關文章
相關標籤/搜索