讀好書,好讀書

日期:2020.03.27
PartⅣ The C API

30 Managing Resources
上一節中實現的自定義類型,我們並沒有關注於資源管理的問題。上一節實現的數組是需要關心內存問題,而這些問題由Lua實現管理。但是很多時候事情不那麼簡單,有些對象不僅需要內存空間,還會需要如窗口句柄、文件描述等資源。儘管說這些也是內存開銷,但是這些資源是由系統的其他組件管理的。這種情況下,當一個對象被回收了,我們也需要合適的機制來實現回收這些額外的資源。
在17.6章節中,介紹了Lua提供的finalizer,由__gc 元方法所組成的一種機制。爲了能在C和API中也能正常的進行資源管理,本章將會從Lua中擴展兩個機制。第一個例子是一個函數遍歷一個目錄的實現;第二個例子是Expat的綁定,一個打開XML資源的解析器。

30.1 A Directory Iterator
之前已經實現過一個類似的dir函數,函數返回一個包含給定目錄下所有文件的table。在這裏我們要實現每次調用返回一個迭代器,代表一個條目(相當於之前table中的一個元素),這樣我們就可以用以下的方式實現遍歷一個目錄:
e.g.
for fname in dir.open(".") do
print(fname)
end
在C中要實現遍歷一個目錄,此時需要一個DIR的數據結構。而且數據結構DIR的實例需要由opendir創建,而且必須在調用closedir的時候立即釋放該實例。之前介紹的實現dir函數是將DIR的實例作爲一個局部變量存儲,然後在得到最後一個文件名字後釋放該實例。然後在這裏,不能將DIR的實例以局部變量來存儲,這裏考慮的是將會有多處的訪問會訪問該實例。不僅如此,也不能僅是在得到最後一個文件名字的時候做釋放工作,因爲假如程序中途退出了循環,那麼就會不會再有釋放了。所以,這裏將DIR的實例地址以一個userdataum的形式存儲,然後使用gc 元方法來管理這個userdataum,用來釋放這個資源。
需要藉助三個C函數來實現我們的需求:1,需用dir.open 函數,Lua用來創建迭代器,這個函數用來打開一個DIR 結構,然後創建一個將這個結構以upvalue的形式的closure;2,一個iterator 函數;3,
gc 元方法,用來釋放資源。當然也需要一個額外的函數用來做參數的初始化工作。
首先,dir.open 函數。這裏要注意的是,一定要在打開目錄之前創建好userdatum,因爲如果順序反了的話,會引發內存錯誤。在順序正確的基礎上,一創建好DIR 結構,就立馬與userdatum鏈接起來了,之後gc 元方法就會接管對其的釋放工作。
然後,dir_iter函數,函數從upvalue中得到DIR結構的地址,然後調用readdir讀取下一個條目。
接着,函數dir_gc 代表
gc 元方法。這個方法關掉目錄,但是這裏要注意一點:因爲是在打開目錄之前先創建好了userdatum,所以無論opendidr的結果是如何,都會對userdatum進行回收。
最後,luaopen_dir 函數,是打開這個庫的函數。
都是C函數形式實現的。

30.2 An XML Parser
這裏介紹的是簡單的實現Lua和Expat的綁定,叫做lxp。Expat是一個開源的用C寫的XML1.0解析器。該解析器實現爲SAX(the Simple API for XML)。SAX是一個基於API的事件分發器,即SAX解析器讀取一行XML文檔,然後通過回調來通知程序讀取到的信息。如我們使用Expat解析一個字符串"<tag cap="5">hi</tag>",將會生成三個事件:當讀取子串"<tag cap-"5>"生成一個 start-element 事件;讀取到"hi"的時候生成text event 事件;讀取到"</tag>"的時候生成endelement event 事件。每個事件在程序中對應一個相應的回調事件。
Expat有非常多的事件,在這裏主要是介紹上面提到的三個事件。
首先,介紹創建和清除Expat解析器的函數:
e.g.
XML_Parser XML_ParserCreate(const char encoding); / 創建 /
void XML_ParserFree(XML_Parser p); /
清除 /
第一個函數中的參數 encoding 是可選的,這裏使用NULL。
得到了解析器之後,就是需要註冊回調句柄了:
void XML_SetElementHandler(XML_Parser p,
XML_StartElementHandler start, /
start 事件 /
XML_EndElementHandler end); /
end 事件 /
void XML_SetCharacterDataHandler(XML_Parser p, XML_CharacterDataHandler hndl); /
text 事件 /
所有的回調句柄接受user data 作爲第一個參數。而且start-element 句柄也接受tag的名字和tag的屬性作爲其參數。
typedef void (
XML_StartElementHandler) (void uData,const char name,const char *atts);
而end-element句柄則只接受一個額外的參數,tag的名字:
typedef void(
XML_EndElementHandler)(void uData,const char name);
text 句柄則接受的是text作爲其參數加上這個text的長度:
typedef void(XML_CharacterDataHandler)(void uData,const char s,int len);
傳遞要解析的信息給解析器,用以下這個函數:
int XML_Parse(XML_Parser p,const char
s,int len,int isLast);
Expat通過連續調用XML_Parse,以塊來接受需要解析的文檔。最後一個參數isLast表示每次解析的塊是否是文檔中最後的一塊。我們這裏使用了每個塊的實際長度作爲參數,所以我們不需要給每個塊做0終止符標記。該函數在遇到解析錯誤的時候將會返回0。
最後的函數便是,設置user data,傳遞給每個句柄用的:
void XML_SetUserData(XML_Parser p ,void *uData);

轉載於:https://www.cnblogs.com/zhong-dev/p/4044552.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章