[轉(zhuǎn)]lua與c的詳細堆棧說明

當在Lua和C之間交換數(shù)據(jù)時主要的問題是自動回收與手動回收內(nèi)存管理的不一致。因此,Lua 用一個抽象的棧在Lua與C之間交換值。
本文目錄:

1、c與lua的取值傳值
(1)取值(到c)
(2)傳值(到lua)

2、堆棧操作api
(1) 壓入元素
(2) 查詢元素(3) 其他堆棧操作3、堆棧操作實例
(1)一些堆棧操作和打印堆棧上所有變量的例子
(2)提供一個打印一張lua表的c的api函數(shù)4、項目例子

本文內(nèi)容:
1、c與lua的取值傳值
(1)取值
棧中的每一條記錄都可以保存任何Lua值。無論你何時想要從Lua請求一個值(比如一個全局變量的值),調(diào)用Lua,被請求的值將會被壓入棧。
Lua以一個嚴格的LIFO規(guī)則(后進先出;也就是說,始終存取棧頂)來操作棧。當你調(diào)用Lua時,它只會改變棧頂部分。你的C代碼卻有更多的自由;更明確的來講,你可以查詢棧上的任何元素,甚至是在任何一個位置插入和刪除元素。
(2)傳值
無論你何時想要傳遞一個值給Lua,首先將這個值壓入棧,然后調(diào)用Lua(這個值將被彈出)。我們?nèi)匀恍枰粋€不同的函數(shù)將每種C類型壓入棧和一個不同函數(shù)從棧上取值(只是取出不是彈出)。
因為棧是由Lua來管理的,垃圾回收器知道那個值正在被C使用。幾乎所有的API函數(shù)都用到了棧。例如,luaL_loadbuffer把它的結(jié)果留在了棧上(被編譯的chunk或一條錯誤信息);lua_pcall從棧上獲取要被調(diào)用的函數(shù)并把任何臨時的錯誤信息放在這里。
2、堆棧操作api
(1) 壓入元素API有一系列壓棧的函數(shù),它將每種可以用C來描述的Lua類型壓棧:空值(nil)用lua_pushnil,數(shù)值型(double)用lua_pushnumber,布爾型(在C中用整數(shù)表示)用lua_pushboolean,任意的字符串(char*類型,允許包含”字符)用lua_pushlstring,C語言風格(以”結(jié)束)的字符串(const char*)用lua_pushstring:void lua_pushnil (lua_State *L);void lua_pushboolean (lua_State *L, int bool);void lua_pushnumber (lua_State *L, double n);void lua_pushlstring (lua_State *L, const char *s, size_t length);void lua_pushstring (lua_State *L, const char *s);同樣也有將C函數(shù)和userdata值壓入棧的函數(shù),稍后會討論到它們。Lua中的字符串不是以零為結(jié)束符的;它們依賴于一個明確的長度,因此可以包含任意的二進制數(shù)據(jù)。將字符串壓入串的正式函數(shù)是lua_pushlstring,它要求一個明確的長度作為參數(shù)。對于以零結(jié)束的字符串,你可以用lua_pushstring(它用strlen來計算字符串長度)。Lua從來不保持一個指向外部字符串(或任何其它對象,除了C函數(shù)——它總是靜態(tài)指針)的指針。對于它保持的所有字符串,Lua要么做一份內(nèi)部的拷貝要么重新利用已經(jīng)存在的字符串。因此,一旦這些函數(shù)返回之后你可以自由的修改或是釋放你的緩沖區(qū)。無論你何時壓入一個元素到棧上,你有責任確保在棧上有空間來做這件事情。記住,你現(xiàn)在是C程序員;Lua不會寵著你。當Lua在起始以及在Lua調(diào)用C的時候,棧上至少有20個空閑的記錄(lua.h中的LUA_MINSTACK宏定義了這個常量)。對于多數(shù)普通的用法棧是足夠的,所以通常我們不必去考慮它。無論如何,有些任務(wù)或許需要更多的??臻g(如,調(diào)用一個不定參數(shù)數(shù)目的函數(shù))。在這種情況下,或許你需要調(diào)用下面這個函數(shù):int lua_checkstack (lua_State *L, int sz);它檢測棧上是否有足夠你需要的空間(稍后會有關(guān)于它更多的信息)。
(2) 查詢元素API用索引來訪問棧中的元素。在棧中的第一個元素(也就是第一個被壓入棧的)有索引1,下一個有索引2,以此類推。我們也可以用棧頂作為參照來存取元素,利用負索引。在這種情況下,-1指出棧頂元素(也就是最后被壓入的),-2指出它的前一個元素,以此類推。例如,調(diào)用lua_tostring(L, -1)以字符串的形式返回棧頂?shù)闹?。我們下面將看到,在某些場合使用正索引訪問棧比較方便,另外一些情況下,使用負索引訪問棧更方便。API提供了一套lua_is*函數(shù)來檢查一個元素是否是一個指定的類型,*可以是任何Lua類型。因此有l(wèi)ua_isnumber,lua_isstring,lua_istable以及類似的函數(shù)。所有這些函數(shù)都有同樣的原型:int lua_is… (lua_State *L, int index);lua_isnumber和lua_isstring函數(shù)不檢查這個值是否是指定的類型,而是看它是否能被轉(zhuǎn)換成指定的那種類型。例如,任何數(shù)字類型都滿足lua_isstring。還有一個lua_type函數(shù),它返回棧中元素的類型。(lua_is*中的有些函數(shù)實際上是用了這個函數(shù)定義的宏)在lua.h頭文件中,每種類型都被定義為一個常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD。這個函數(shù)主要被用在與一個switch語句聯(lián)合使用。當我們需要真正的檢查字符串和數(shù)字類型時它也是有用的為了從棧中獲得值,這里有l(wèi)ua_to*函數(shù):int lua_toboolean (lua_State *L, int index);double lua_tonumber (lua_State *L, int index);const char * lua_tostring (lua_State *L, int index);size_t lua_strlen (lua_State *L, int index);即使給定的元素的類型不正確,調(diào)用上面這些函數(shù)也沒有什么問題。在這種情況下,lua_toboolean、lua_tonumber和lua_strlen返回0,其他函數(shù)返回NULL。由于ANSI C沒有提供有效的可以用來判斷錯誤發(fā)生數(shù)字值,所以返回的0是沒有什么用處的。對于其他函數(shù)而言,我們一般不需要使用對應(yīng)的lua_is*函數(shù):我們只需要調(diào)用lua_is*,測試返回結(jié)果是否為NULL即可。Lua_tostring函數(shù)返回一個指向字符串的內(nèi)部拷貝的指針。你不能修改它(使你想起那里有一個const)。只要這個指針對應(yīng)的值還在棧內(nèi),Lua會保證這個指針一直有效。當一個C函數(shù)返回后,Lua會清理他的棧,所以,有一個原則:永遠不要將指向Lua字符串的指針保存到訪問他們的外部函數(shù)中(就算Lua_to* 和lua_is*沒有彈棧操作,也不該使用c指針指向lua字符串的地址)。
Lua_string返回的字符串結(jié)尾總會有一個字符結(jié)束標志0,但是字符串中間也可能包含0,lua_strlen返回字符串的實際長度。特殊情況下,假定棧頂?shù)闹凳且粋€字符串,下面的斷言(assert)總是有效的:const char *s = lua_tostring(L, -1);size_t l = lua_strlen(L, -1);assert(s[l] == ”);assert(strlen(s) <= l);
(3) 其他堆棧操作除開上面所提及的C與堆棧交換值的函數(shù)外,API也提供了下列函數(shù)來完成通常的堆棧維護工作:(3-1)int lua_gettop (lua_State *L);
函數(shù)lua_gettop返回堆棧中的元素個數(shù),它也是棧頂元素的索引。注意一個負數(shù)索引-x對應(yīng)于正數(shù)索引gettop-x+1。(3-2)void lua_settop (lua_State *L, int index);
lua_settop設(shè)置棧頂(也就是堆棧中的元素個數(shù))為一個指定的值。如果開始的棧頂高于新的棧頂,頂部的值被丟棄。否則,為了得到指定的大小這個函數(shù)壓入相應(yīng)個數(shù)的空值(nil)到棧上。特別的,lua_settop(L,0)清空堆棧。你也可以用負數(shù)索引作為調(diào)用lua_settop的參數(shù);那將會設(shè)置棧頂?shù)街付ǖ乃饕?。利用這種技巧,API提供了下面這個宏,它從堆棧中彈出n個元素:#define lua_pop(L,n) lua_settop(L, -(n)-1)(3-3)void lua_pushvalue (lua_State *L, int index);
函數(shù)lua_pushvalue壓入堆棧上指定索引的一個摶貝到棧頂;(3-4)void lua_remove (lua_State *L, int index);
lua_remove移除指定索引位置的元素,并將其上面所有的元素下移來填補這個位置的空白;(3-5)void lua_insert (lua_State *L, int index);
lua_insert移動棧頂元素到指定索引的位置,并將這個索引位置上面的元素全部上移至棧頂被移動留下的空隔;(3-6)void lua_replace (lua_State *L, int index);最后,lua_replace從棧頂彈出元素值并將其設(shè)置到指定索引位置,沒有任何移動操作。
(3-7)注意到下面的操作對堆棧沒有任何影響:lua_settop(L, -1);lua_insert(L, -1);
3、堆棧操作實例(1)一些堆棧操作和打印堆棧上所有變量的例子

//打印棧上的所有的變量  
static void stackDump (lua_State *L)  
{  
    int i;  
    int top = lua_gettop(L);  
    for (i = 1; i <= top; i++)  
    {  
        int t = lua_type(L, i);  
        switch (t)  
        {  
            case LUA_TSTRING:  
                printf("%s", lua_tostring(L, i));  
            break;  
            case LUA_TBOOLEAN:  
                printf(lua_toboolean(L, i)? "true":"false");  
            break;  
            case LUA_TNUMBER:  
                printf("%g", lua_tonumber(L, i));  
                break;  
            default:  
                printf("%s", lua_typename(L, t));  
            break;  
        }  
        printf(" ");  
    }  
    printf("\n");  
}  
  
  
int main (void)   
{  
    lua_State *L = lua_open();  
    //1 依次壓入幾個變量 true,10,nil,"hello"   
    printf("1:\n");  
    lua_pushboolean(L, 1);  
    lua_pushnumber(L, 10);  
    lua_pushnil(L);  
    lua_pushstring(L,"hello");  
    stackDump(L);//true 10 nil hello   
    //2  
    printf("2:\n");  
    lua_pushvalue(L, -4);  
    stackDump(L);//true 10 nil hello true   
    //3  用棧頂更換第三個  
    printf("3:\n");  
    lua_replace(L, 3);  
    stackDump(L);//true 10 true hello   
    //4 設(shè)置棧變量為6個  
    printf("4:\n");  
    lua_settop(L, 6);  
    stackDump(L);//true 10 true hello nil nil   
    //5 移除從棧頂往下數(shù)的第三個  
    printf("5:\n");  
    lua_remove(L, -3);  
    stackDump(L);//true 10 true nil nil   
    //6 設(shè)置-5位置的變量作為棧頂變量  
    printf("6:\n");  
    lua_settop(L, -5);  
    stackDump(L);//true  
    //7  
    lua_close(L);  
    return 0;  
}  

輸出結(jié)果(輸出是從棧底到棧頂):
1:
true 10 nil hello
2:
true 10 nil hello true
3:
true 10 true hello
4:
true 10 true hello nil nil
5:
true 10 true nil nil
6:
true
(2)提供一個打印一張表的c的api函數(shù)

void tableDump (lua_State *L,int index)  
{  
        if (!lua_istable(L,index))  
        {  
                return;  
        }  
        int tLen = lua_objlen(L,index);  
  
        for(int i = 0;i < tLen;i++)  
        {  
                lua_rawgeti(L,index,i+1);  
                int t = lua_type(L, -1);  
                switch (t) {  
                        case LUA_TSTRING:  
                                printf("t[%d]=%s ",i+1, lua_tostring(L,-1));  
                                break;  
                        case LUA_TBOOLEAN:  
                                printf("t[%d]=%s ",i+1,lua_toboolean(L, -1)? "true":"false");  
                                break;  
                        case LUA_TNUMBER:  
                                printf("t[%d]=%g ",i+1, lua_tonumber(L, -1));  
                                break;  
                        default:  
                        printf("t[%d]=%s ",i+1, lua_typename(L, t));  
                        break;  
                }  
                lua_pop(L,1);  
        }  
        printf("\n");  
}  

4、項目例子
這里是將一個c++的配置壓入到lua一張叫QuestData(任務(wù)表)的全局的表里。這個例子可以看出c構(gòu)建lua表數(shù)據(jù)的基本應(yīng)用

void QuestAccessor::makeScriptQuestData()  
{  
  QuestConfig** ppQuestList = m_Quest.own_ptr();  
  INT_PTR nCount = m_Quest.length();  
  lua_State* L = g_Script->getLuaState();  
  lua_newtable(L);//QuestData表  
  for (INT_PTR i = 1; i < nCount; ++i)  
  {  
    pushToQuestData(L, ppQuestList[i]);  
  }  
  lua_setglobal(L, "QuestData");//設(shè)置該表作為全局的表,名為QuestData  
}  
 /* 
QuestData[questId] = {  
        name = "任務(wù)名", 
        acceptTalk = { "111", "222", "333" },  
        acceptReply = {"11", "22", "33"}, 
        completeTalk = { "aaa", "bbb", "ccc" }, 
        completeReply = {"aa", "bb", "cc"}, 
} 
*/  
void QuestAccessor::pushToQuestData(lua_State* L, QuestConfig *quest)//最外面建了一張表quests  
{  
    lua_newtable(L);//新建questId作為QuestData表的下標的表  
    lua_pushvalue(L,-1);//拷貝表引用到棧頂  
    lua_rawseti(L, -3, quest->nQid);//設(shè)置questId作為QuestData表的下標的表  
    //name = "quest-name"  
    lua_pushstring(L, quest->sName);  
    lua_setfield(L, -2, "name");//questId 表的名字  
    const QuestAccessor::TalkStruct* pTalkStruct =  &(m_questTalkList.get(quest->nQid));  
    if (NULL != pTalkStruct)  
    {  
    if (pTalkStruct->accept.nCount > 0)  
    {  
            lua_newtable(L);//acceptTalk 表  
            lua_pushvalue(L,-1);  
            lua_setfield(L, -3, "acceptTalk");  
      for (int i = 0; i < pTalkStruct->accept.nCount; ++i)  
      {  
        lua_pushstring(L, pTalkStruct->accept.talkList[i].c_str());  
        lua_rawseti(L, -2, i + 1);  
      }  
      lua_pop(L, 1);//彈出acceptTalk表  
      lua_newtable(L);//新建acceptReply表  
      lua_pushvalue(L, -1);  
      lua_setfield(L, -3, "acceptReply");//設(shè)置acceptReply表到questId表  
      for (int i = 0; i < pTalkStruct->accept.nCount; ++i)  
      {  
        lua_pushstring(L, pTalkStruct->accept.replyList[i].c_str());  
        lua_rawseti(L, -2, i+1);  
      }  
      lua_pop(L, 1);//彈出acceptReply標  
    }  
        if (pTalkStruct->complete.nCount > 0)  
    {  
      lua_newtable(L);//新建completeTalk表  
      lua_pushvalue(L, -1);  
      lua_setfield(L, -3, "completeTalk");  
      for (int i = 0; i < pTalkStruct->complete.nCount; ++i)  
      {  
        lua_pushstring(L, pTalkStruct->complete.talkList[i].c_str());  
        lua_rawseti(L, -2, i + 1);  
      }  
      lua_pop(L, 1);  
  
  
      lua_newtable(L);//新建completeReply表  
      lua_pushvalue(L, -1);  
      lua_setfield(L, -3, "completeReply");  
      for (int i = 0; i < pTalkStruct->complete.nCount; ++i)  
      {  
        lua_pushstring(L, pTalkStruct->complete.replyList[i].c_str());  
        lua_rawseti(L, -2, i+1);  
      }  
      lua_pop(L, 1);  
    }  
  }  
  lua_pop(L, 1);//彈出questId表  
}  

轉(zhuǎn)自:http://blog.csdn.net/chenjiayi_yun/article/details/23772627

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第一篇 語言 第0章 序言 Lua僅讓你用少量的代碼解決關(guān)鍵問題。 Lua所提供的機制是C不擅長的:高級語言,動態(tài)...
    testfor閱讀 2,936評論 1 7
  • C API 云風Blog:Lua C API 的正確用法 C讀取和調(diào)用Lua文件的庫:lua.h, lauxlib...
    SysuYe閱讀 5,793評論 2 10
  • lua C api PS:這里是默認我已經(jīng)學完了lua腳本的基本知識(包括table,元表,函數(shù),基本庫, 文件i...
    綠箭ML閱讀 3,682評論 0 1
  • 月兒,那時炎熱剛剛退去 雨滴翻山越水而來,在我的窗前不絕清唱 窗戶自然是開著的。還有伸長了等待傾聽的耳朵 月兒,在...
    楊昊田閱讀 637評論 4 12

友情鏈接更多精彩內(nèi)容