OSG第二代檔案序列化儲存分析

來源:互聯網
上載者:User

花了兩天時間仔細研究了一下array貢獻的第二代檔案序列化儲存,收益頗多。今後將得益於它的很強的可擴充性。通過自身的體驗,感覺應該還有很多初學者可能需要花費幾個小時才能看透,所以特將自己分析的流程發上來,以加快想搞明白其流程的初學者的學習速度。這兩天身體狀況不太好,兩眼通紅,眼睛發澀,所以在寫的過程中難免有些拼字錯誤什麼的,流程的分析也難免有紕漏或不妥,還請高手指正。


我們以將節點寫入檔案為例,節點讀取與此相同,看看究竟是如何將節點寫入檔案的,我們從檔案讀寫外掛程式的最頂層入口函數來看,即

virtual WriteResult writeNode( const osg::Node& node, const std::string& fileName, const Options* options ) const

因為osg第二代檔案格式讀寫外掛程式同時支援文本、二進位以及XML格式的檔案,所以,在寫檔案前要確定具體要寫入什麼檔案,讀寫不同的檔案格式需要用到的序列化器不同,檔案開啟檔案也不同,

進入writeNode後會調用Options* prepareWriting( WriteResult& result, const std::string& fileName, std::ios::penmode&
mode, const Options* options ) const
該函數就完成上面的工作,該函數根據副檔名判斷如果是osgt格式,就向options中添加文字格式設定的參數選項,如果是osgx格式,就添加XML格式選項,以讓後續的寫入操作根據該選項進行相應檔案格式的讀寫,否則以二進位格式讀寫;
做好以上準備工作後,就像以前一樣將檔案資料讀入檔案流開始向檔案流寫入資料,即進入virtual WriteResult writeNode( const osg::Node& node, std::stream& fout, const Options* options ) const
針對文本、XML、二進位格式的檔案分別對應三種不同的輸出指標,在OutputStream類中進行資料寫入時要調用相應的輸出指標,所以,首先要擷取相應類型的輸出指標;該工作通過函數OutputIterator* writeOutputIterator( std::stream& fout, const Options* options )來完成,該函數從options中尋找是否有文本或者XML的檔案格式選項(從剛才的分析我們已經知道,如果是osgt或osgx格式的話,在prepareWriting中已經添加進來),如果有對應的選項,就建立相應的輸出指標,如果兩種都沒有,就建立二進位格式的輸出指標,除文字格式設定之外,對於XML格式和二進位格式,該函數除了建立相應的輸出指標外,同時還像檔案流寫入了檔案頭資訊,對於XML格式,直接寫入XML檔案頭,對於二進位格式,寫入MD5碼;從上面的分析我們不難看出,對於文本和XML格式的檔案,我們簡單的添加supportsExtension來讓外掛程式支援我們自己的副檔名檔案類型是做不到的,外掛程式會把它當做二進位來對待,無法正確處理,所以,副檔名得擴充只適用於二進位格式;
得到輸出指標後構造一個OutputStream對象開始寫資料。
首先是調用void OutputStream::start( OutputIterator* outIterator, OutputStream::WriteType type ),將輸出指標傳給OutputStream並向檔案流寫入檔案的標誌資訊,如檔案內容類型標示(如情境、對象、映像)、版本號碼等資訊,接下來調用void
OutputStream::writeObject( const osg::Object* obj )從節點對象讀取節點資料。下面進入該函數,首先擷取一個對象唯一ID,然後向流中寫入對象類名(如osg::Group),然後寫入開始大括弧’{’,接下來寫入對象的資料,寫入對象資料後寫入結束大括弧’}’;上面的寫入操作都是通過剛才傳入的輸出指標來完成;
        下面我們將如何寫入對象資料來分解開進行分析,這是寫入操作的核心所在。該過程在void OutputStream::writeObjectFields( const osg::Object* obj )中完成,我們現在就來看看writeObjectFields都玩了哪些花樣;一定要看仔細,正是它玩的花樣,才使得我們可以在不修改該檔案操作外掛程式的情況下通過擴充的方式讓自己的節點對象也可以讀寫的,所以一定要看仔細了;
首先要根據類名擷取該類的wrapper(所有要能夠將資料寫入檔案的類都要對應有一個該類的封裝類wrapper,所有的wrapper統一由wrapper管理器來管理,每添加一個wrapper類型,自己要將自己註冊到該管理器,否則無法對該類型的對象進行讀寫操作),如果要讓自己的對象能夠進行檔案讀寫,需要擴充自己的類的wrapper類,在wrapper類中定義要讀寫哪些資料欄位;
類是有繼承關係的,那麼在進行讀寫操作時,還是需要各個類自己操作自己本身的成員,一個子類只負責自己擴充出來的成員欄位的讀寫操作,父類的那些成員又父類的wrapper去處理,各司其職。而系統本身是沒有辦法知道一個子類上面都有哪些父類、祖父類等關聯類別資訊的,所以需要我們告訴它,該工作通過在構造wrapper時指定,將該子類關聯的所有上層類名稱傳進去,即associates參數,在每個wrapper類中有一個associates的數組。
繼續上面的分析過程,在writeObjectFields中擷取到要寫入檔案的節點對象(根據類名獲得,所以,自己派生的對象要能像osg對象那樣能夠用這種方式進行讀寫操作,必須從osg::Object派生,且必須實現ClassName介面)對應的wrapper後,遍曆該wrapper的關聯類別數組,然後再根據關聯類別擷取對應的wrapper進行資料寫入工作。在繼續該寫入過程詳細分析之前,需要補充說明一下,註冊wrapper時向該wrapper傳入的管理類資訊包含了該類本身,所以,這裡的寫入操作統一在遍曆關聯類別數組過程中進行,所以,在註冊wrapper時,一定要在最後將類自身也添加到關聯類別資訊裡,否則只能讀寫上層類的資料,本類本身的資訊會丟失。
接下來的分析就相對要集中了,就是針對具體的類類型進行資料的寫入操作了。如果需要向檔案中寫入每個類的欄位剛要資訊,就將每個類的屬性欄位名稱及類型以剛要的形式寫入,然後再寫該類的資料資訊,預設情況下不寫入;那麼我們接著看寫對象的資料欄位的內容。該過程由bool ObjectWrapper::write( OutputStream& os, const osg::Object& obj )完成,下面我們來進到裡面探個究竟。
在分析之前我們需要插入一段前奏,然後才能繼續下面的過程。通過上面的分析,我們知道怎麼讓外掛程式能夠讀寫每種類對象了(通過擴充wrapper並註冊),也知道了怎麼區別對待不同類型的檔案(Txt、XML、Binary,通過不同的指標),還有一個重要的問題沒有提到,那就是如何知道要讀寫一個類的哪些資料成員,以及如何調用資料擷取介面。在註冊wrapper時,除了給wrapper設定其對應的類名、類對象原型、關聯類別描述之外,還要設定通過何種序列化器對哪些欄位進行讀寫操作。我們通過代碼不難看到ADD_USER_SERIALIZER、ADD_OBJECT_SERIALIZER、ADD_DOUBLE_SERIALIZER等等身影,通過這些宏就設定好了要讀寫該wrapper對應的類的資料欄位。一個對象有多少屬性欄位,就會對應多少序列化器,每個序列化器負責該屬性欄位的具體的讀寫操作,wrapper對象會將所有加入的序列化器儲存在一個數組中。此外,對於不同的欄位資料類型,對應有相應類型的序列化器,如int、double、osg::Vec3d以及物件類型等等,操作相應類型的屬性欄位要選擇正確的序列化器。
通過上面的分析,我們應該知道了如何設定類的哪些屬性資料可進行檔案儲存體,接下來我們來看看是如何調用相關的屬性提供者來進行資料存取的。方法用的就是函數指標,在添加序列化器時,相應的序列化器會根據傳入的屬性欄位名稱自動建立對應的屬性訪問函數指標(getter、setter)或欄位讀寫函數指標(reader、writer)並將其傳遞給序列化器的建構函式(函數指標作為函數參數傳遞,這些函數都不會有重載函數,基本都是getXXX、setXXX,大可放心使用),然後序列化器會記錄下來這些函數指標,在序列化器進行資料欄位的讀寫時調用該函數指標來進行資料欄位的讀寫操作;
好了,前奏到此為止,不要忘了我們還沒有進入bool ObjectWrapper::write( OutputStream& os, const osg::Object& obj )呢。現在我們來看這個函數就很簡單了,它也就是遍曆該wrapper對象的所有序列化器,調用序列化器的write函數將每個屬性欄位寫進去。對於UserSerializer是傳遞屬性欄位讀寫函數指標,對於其他類型的序列化器,傳遞的是屬性欄位的訪問函數指標,兩者最終都是通過OutputStream的”<<”重載操作符將欄位值寫入到流中,進一步我們可以看到OutputStream的”<<”重載操作符函數最終通過調用最開始時傳入OutputStream的輸出指標的類型寫入函數來完成最終的寫入操作;針對不同類型的格式控制(txt、XML、Binary)都是在相應的輸出指標中完成。
現在我們知道,通過該外掛程式,我們要讓該外掛程式支援我們自己擴充的物件類型,只要建立相應的wrapper並註冊進來就OK,非常方便。另外一方面,可能在開發自己的應用系統時,需要定義自己的檔案格式,這時,如果是二進位的格式的話,直接通過supportsExtension添加自己的副檔名支援即可,在應用系統中通過addFileExtensionAlias來指定一下。此外還有一個重要的擴充可能對我們開發自己的系統更為重要,那就是檔案資料的加密儲存,簡單起見我們可以重寫外掛程式的writeOutputIterator和readOutputIterator,在檔案頭中加入自己的加密資料,如base64/md5加密資料,並做相應的檢測處理,通過派生新的compressor我們就可以實現對檔案內容的壓縮和解壓縮,或者加密和解密,或者加入自己的頭資訊等等,都非常方便;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.