/** * @file ObjArchive.h * @brief 二进制文件的序列化,支持版本兼容机制 * @date 2014-5-23 * @author: liyonggang */ #ifndef PAI_FRAME_COSGARCHIVE_H__ #define PAI_FRAME_COSGARCHIVE_H__ #pragma warning( push ,0) #include #include #include #include #ifdef _WINDOWS #include #endif #include #include #include #include #include #include #include #include #include #include //#include "Turtle.h" #pragma warning( pop ) //bool CXXClass::Serialize(CObjectArchive &ar) //{ // if(ar.IsStoring()) // { // /** // * begin Archive one object // * note : // * if you add some member variable, please add new block // * if you del some member variable, please use temp variable replace the member variable // * more detail ,please reference CObjectArchive in the "GmArchive.h" file // */ // BEGIN_WRITE_OBJECT( ar,1 ); // // BEGIN_WRITE_BLOCK( ar, 1); // //write your code // END_WRITE_BLOCK( ar, 1 ); // // END_WRITE_OBJECT( ar ); // } // else // { // BEGIN_READ_OBJECT( ar,1 ); // // BEGIN_READ_BLOCK( 1 ); // //write your code // END_READ_BLOCK( 1 ); // // END_READ_OBJECT( ar ); // } // return true; //} extern int g_nSerializeErrorMsgBoxMaxCount ; /*error occur when Serialize,the max MessageBox count */ //#ifndef NDEBUG // debug and release version ,when read block, ensure block size is right,help debug #define BEGIN_RECORD_BLOCKBEGINPOS(ar) \ int nBlockBeginPos_archive = static_cast ( (ar).GetDevice()->pos() ) ;\ CObjectArchive &ar_RecordBlockPos = (ar);\ // debug version ,when read block, ensure block size is right,help debug #define END_RECORD_BLOCKBEGINPOS() if( ( ar_RecordBlockPos.GetDevice()->pos() - nBlockBeginPos_archive ) != BlockDatasize_archive )\ {\ /*get error msg */\ QString strErrorMsg = "read block error :" ;\ strErrorMsg = strErrorMsg + "\nfile: " + __FILE__ + ":" + QString::number(__LINE__) ;\ /*strErrorMsg = strErrorMsg + "\ntypename: " + typeid( *pObject ).name() ;*/ \ if( g_nSerializeErrorMsgBoxMaxCount<1 )\ {\ /*QMessageBox::critical(NULL, "load data", strErrorMsg);*/ \ QMessageBox msgBox;\ msgBox.setIcon(QMessageBox::Critical);\ msgBox.setWindowTitle("load data");\ msgBox.setText(strErrorMsg);\ msgBox.setWindowFlags(msgBox.windowFlags()|Qt::WindowStaysOnTopHint);\ msgBox.exec();\ ++g_nSerializeErrorMsgBoxMaxCount;\ }\ ar_RecordBlockPos.SeekPos( static_cast( nBlockBeginPos_archive + BlockDatasize_archive ) ,CObjectArchive::SEEKFROM_BEGIN );\ }\ //#else //#define BEGIN_RECORD_BLOCKBEGINPOS(ar) //#define END_RECORD_BLOCKBEGINPOS() //#endif #ifndef NDEBUG // debug version, don't try exception #define BEGIN_TRY_SERIALIZE( ar,msg,pObject ) #define END_TRY_SERIALIZE( ar, bWrite , Msg,pObject ) #else /* begin try exception when serialize */ #ifdef _WINDOWS #define BEGIN_TRY_SERIALIZE( ar,msg,pObject ) \ {\ /*record object's first pos */\ qint64 nObjectBeginPos_archive = ( ar ).GetDevice()->pos() ;\ try\ {\ #define END_TRY_SERIALIZE( ar, bWrite , Msg,pObject ) \ }catch(...)\ { \ /*get error msg */\ QString strErrorMsg = Msg ;\ strErrorMsg = strErrorMsg + "\nfile: " + __FILE__ + ":" + QString::number(__LINE__) ;\ strErrorMsg = strErrorMsg + "\ntypename: " + typeid( *pObject ).name() ;\ /*10 msgbox is max*/ \ if( g_nSerializeErrorMsgBoxMaxCount<1 )\ { \ QMessageBox::critical(NULL, Msg, strErrorMsg);\ ++g_nSerializeErrorMsgBoxMaxCount;\ }\ /*record log */\ /*DEBUG_LOG( strErrorMsg );*/\ /*rollback operate */\ bool bWriteValue = bWrite;\ if( bWriteValue )\ {\ (ar).RollbackWriteObject( ( int )nObjectBeginPos_archive );\ }else\ {\ (ar).RollbackReadObject( ( int )nObjectBeginPos_archive );\ }\ }\ } #else #define BEGIN_TRY_SERIALIZE( ar,Msg,pObject ) #define END_TRY_SERIALIZE( ar, bWrite , Msg,pObject ) #endif #endif /** "Serializatioin macro" **/ //* begin to Read Object //* @param ar ; CObjectArchive object //* @param ver : the least version we expect Read //* @author : yonggang #define BEGIN_READ_OBJECT_EX(ar,ver,pObj )\ BEGIN_TRY_SERIALIZE( ar,"load",pObj );\ {\ qint64 nObjectBeginPos_archiveSeek = ( ar ).GetDevice()->pos() ;\ /* Read Block Number in One Object */\ unsigned short nBlockNumber_archive = 0;\ unsigned short verRead_archive = 0;\ (ar).ReadObject( nBlockNumber_archive ,verRead_archive );\ /*if Less than ver then skip this Object */\ if( verRead_archive < ver )\ { \ (ar).SeekPos( ( int )nObjectBeginPos_archiveSeek ,CObjectArchive::SEEKFROM_BEGIN );\ (ar).SkipObject();\ return nBlockNumber_archive >0 ;\ }\ /* loop all block */ \ for( int iBlock_archive = 0;iBlock_archive < nBlockNumber_archive ; ++iBlock_archive )\ { \ /* Read Block ID and Block Data's Size */ \ unsigned short BlockID_archive = 0 ;\ unsigned int BlockDatasize_archive = 0 ;\ (ar).ReadBlock( BlockID_archive,BlockDatasize_archive ) ;\ BEGIN_RECORD_BLOCKBEGINPOS(ar);\ /* Read One Block */ \ switch( BlockID_archive )\ { //* Read the end of object //* @param ar ; CObjectArchive object //* @author : yonggang #define END_READ_OBJECT_EX(ar,pObj )\ default :\ {\ /* if not recognise the Block , Then Skip */\ /* this will frequently happen when Lower Version software open the higher File */\ (ar).SeekPos( BlockDatasize_archive,CObjectArchive::SEEKFROM_CUR );\ }\ break;\ }\ }\ }\ END_TRY_SERIALIZE( ar, false,"LOAD_DATA",pObj ); #define BEGIN_READ_OBJECT(ar,ver ) BEGIN_READ_OBJECT_EX( ar,ver,this ) #define END_READ_OBJECT( ar ) END_READ_OBJECT_EX(ar,this ) //* begin to Read One Block //* @param BlockId ; Block ID //* @author : yonggang #define BEGIN_READ_BLOCK( BlockId )\ case BlockId:\ { \ //* End Read One Block //* @param id ; Block ID //* @author : yonggang #define END_READ_BLOCK( BlockId )\ END_RECORD_BLOCKBEGINPOS();\ }\ break;\ //* Begin to Write One Object //* @param ar ; CObjectArchive Object /** @param ver ; the Object's version, usually, we should not change the version,we should add new Block for add new member variable, i think the version shouled a big version for business,perhaps it is Unchange at least one year */ //* @param pObj: the object of write //* @author : yonggang #define BEGIN_WRITE_OBJECT_EX( ar,ver,pObj)\ BEGIN_TRY_SERIALIZE( ar,"write",pObj )\ {\ void *pObjWriteObjectVoidPointer = ( void * )(ar).CreateNewObjectID();\ (ar).BeginWriteObject( pObjWriteObjectVoidPointer ,ver );\ {\ //* end Write Object,this will correct the Object's Block Number //* @param ar : CObjectArchive Object //* @author : yonggang #define END_WRITE_OBJECT_EX( ar,pObj )\ }\ (ar).EndWriteObject( pObjWriteObjectVoidPointer );\ }\ END_TRY_SERIALIZE(ar, true ,"SAVE_DATA",pObj )\ #define BEGIN_WRITE_OBJECT( ar,ver) BEGIN_WRITE_OBJECT_EX( ar,ver,this ) #define END_WRITE_OBJECT(ar) END_WRITE_OBJECT_EX( ar,this ) //* end Write Object,this will correct the Object's Block Number //* @param ar : CObjectArchive Object //* @param BlockID : block id //* @author : yonggang #define BEGIN_WRITE_BLOCK( ar, BlockID ) \ (ar).BeginWriteBlock( BlockID , pObjWriteObjectVoidPointer );\ { //* end Write Object,this will correct the Object's Block Data size //* @param ar : CObjectArchive Object //* @parma BlockID : block id //* @author : yonggang #define END_WRITE_BLOCK( ar,BlockID ) \ }\ (ar).EndWriteBlock( BlockID , pObjWriteObjectVoidPointer ) ; /** "Serializatioin macro" end **/ class QFile; enum EGmArchiveMode { eStore = 1, eLoad = 2, ebNoFlushOnDelete = 4, ebNoByteSwap = 8 , }; #define CONSTRUCT_GMARCHIVE( strPath,eState )\ CObjectArchive ar( strPath, eState );\ if( !ar.IsOpened() ) return false;\ #define CONSTRUCT_GMARCHIVE_VOID( strPath,eState )\ CObjectArchive ar( strPath, eState );\ if( !ar.IsOpened() ) return ;\ /*define a emtyp value */ #define NULLVALUE /* if you want "return" when Store Data at special block,if must use this macro*/ #define RETURN_WRITE( ar, BlockID,retValue ) \ (ar).EndWriteBlock( BlockID , pObjWriteObjectVoidPointer ) ;\ (ar).EndWriteObject( pObjWriteObjectVoidPointer );\ return retValue;\ class CObjectArchive: public QDataStream { private: /** * Cache of information when Serialization */ struct CObjectArchivePos { public: qint64 m_uCurrentBlockSizePos ; //the Byte Pos of The Current Block size qint64 m_uCurrentBlockDataPos ; //the Byte Pos of The Current Block Data qint64 m_uBlockNumPos ; //the Byte Pos of One Object's Block Number unsigned short m_nBlockNumInObject; // Block Number of One Object #ifndef NDEBUG QSet< unsigned short > m_setBlockId ; //When Debug, Record one Object's all Block ID #endif }; /** * Get Current Positon of the File * @param pos [out]: currnet position of the file */ inline void GetCurPos( qint64 &pos ); /** * write Data at specially file's postion * @param pos : the position of write Data * @param pDwordSize : data .if this is not NULL , then write this data.other wize write pShortSize * @param pShortSize : data .if pDwordSize is NULL, then write this data */ inline void WriteSize ( const qint64 &pos , const unsigned int *pDwordSize,unsigned short *pShortSize ); /** * when Debug ,Assert the Block id * @param id : block Id * @param bHave : Assert the Block id whether have */ inline void Q_ASSERTBlockID( unsigned short id,bool bHave ); /** * all object's information that is writting */ std::map< const void * ,CObjectArchivePos > m_mapObjectPos; /** * the current Object's Information that is writting */ CObjectArchivePos * m_pCurObjectPos ; qint64 m_nObjectID; #ifndef NDEBUG int m_nBlockNumber; // when debug,Record the Block Number int m_nPackNumber ; // when Debug, Record the Object Number #endif /** * Init All Archive's Information ,is used the construct CObjectArchive Object */ void InitArchivePosData(); /** * Set The Current Object that is Writting * @param pObject : the Object */ void SetActiveObject( const void *pObject ) ; /** * separate block function */ public: /** * Begin Postion when Seek */ enum SEEKFROM { SEEKFROM_CUR, //the current postion SEEKFROM_BEGIN, //the beginning of the file }; /** * Skip One Object when Read */ void SkipObject(); /** * Read Block Number and Data size * @param blockId [out] : Block Id * @param datasize [out] : Block Data size */ void ReadBlock ( unsigned short &blockcid ,unsigned int &datasize ); /** * Read One Object's Block Number and Version * @param nBlockNumber :[out] Block Number * @param ver :[out] version */ void ReadObject( unsigned short& nBlockNumber,unsigned short &ver ) ; /** * Begin write one Block * @param id : block id * @parma pObject : the object of the block */ void BeginWriteBlock( unsigned short id,const void *pObj ) ; /** * end write the block,this will correct the block's data size * @param id : block id * @parma pObject : the object of this block */ void EndWriteBlock( unsigned short id , const void *pObj ) ; /** * begin write one Object * @param pObj : the object * @param ver : versioin */ void BeginWriteObject( const void *pObj,unsigned short ver ); /** * end write one object ,this will correct the block number of the object * @parma pObj: the object */ void EndWriteObject( const void *pObj ) ; /** * seek to specially positon * @param pos : the postion relative "from" * @parma from : the beginning of seek pos */ void SeekPos( int Pos ,SEEKFROM from ) ; /** * when Debug ,we can use this function to examine if we match "beginObject " and "endObject",Block ID etc. */ bool IsWriteDataOK( ) ; /** * Write a empty Object ,only write version and block Numer( blockNumer is 0 ) * it is used when we delete a object ,then we can Write and Read Empty object , * thus ,the lower version software can successly open the Higer file */ bool WriteEmptyObject( ) ; /** * Read a empty Object , Only Read version and Block Number ( after read ,Block Number shoudle be 0 ) * it is used when we delete a object ,then we can Write and Read Empty object , * thus ,the lower version software can successly open the Higer file */ bool ReadEmptyObject( ) ; /** * Create a new Object ID */ qint64 CreateNewObjectID( ) ; /** * rollback to write a emtpy object * when error occur during save object */ void RollbackWriteObject( int nObjctBeginPos); /** * rollback to skip this object * when error occur during load object */ void RollbackReadObject(int nObjectBeginPos ); public: /** * memery archive construct * @param byteArray : byte array * @param eMode : store or load mode */ CObjectArchive( QByteArray& byteArray,EGmArchiveMode eMode ); /** * construct a CObjectArchive object * @param sFile : file full path * @param eMode : store or load mode * @param nWriteOffSet : if eMode = store,then set the write offset * <0, append to the end of file. * =0 ,then truncate. * >0 ,then set the write offset.if file size is not enougth large,then extend it */ CObjectArchive(QString sFile, EGmArchiveMode eMode ,qint64 nWriteOffSet = 0); /** * attach a QFile object to CObjectArchive object , * @param pFile : QFile Object,when destructor,pFile Don't Close. * @param eMode : store or load mode */ void Attatch(QIODevice *pDevice,EGmArchiveMode eMode ); bool IsOpened(); ~CObjectArchive(); // QString m_strLastClassName; yonggang comment,don't need now bool IsLoading() ; bool IsStoring() ; /** * get byteArray.if this is not a memory archive, return NULL */ QByteArray* GetByteArray(); QIODevice* GetDevice(); void Read(void* lpBuf, unsigned int nMax); void Write(const void* lpBuf, unsigned int nMax); void Close(); void WriteString(char * lpsz); char * ReadString(char * lpsz, unsigned int nMax); int ReadString(QString& rString); unsigned long ReadCount(); void WriteCount(unsigned long dwCount); /** * @brief save filename in current archive and create a new archive(derive) on the filename * @param strFilePathName the file name which is connected with new archive * @return the new archive handle * @author haidong * @see PopArchive */ CObjectArchive& PushArchive(QString& strFilePathName); /** * @brief close specified archive and return to current archive * @param archive2 the derived archive handle,will be closed. * @return * @author haidong * @see PushArchive */ void PopArchive(CObjectArchive& archive2); CObjectArchive& operator<<(char ch); CObjectArchive& operator<<(unsigned char by); CObjectArchive& operator<<(bool b ); CObjectArchive& operator<<(short w); CObjectArchive& operator<<(unsigned short w); CObjectArchive& operator<<(int i); CObjectArchive& operator<<(unsigned int u); CObjectArchive& operator<<(long l); CObjectArchive& operator<<(unsigned long dw); CObjectArchive& operator<<(float f); CObjectArchive& operator<<(double d); CObjectArchive& operator<<(qint64 n); CObjectArchive& operator<<(const QString & s ); CObjectArchive& operator<<(QAction *pAction); CObjectArchive& operator<<( QUuid id ); // extraction operations CObjectArchive& operator>>(char& ch); CObjectArchive& operator>>(unsigned char& by); CObjectArchive& operator>>(bool & b ); CObjectArchive& operator>>(short& w); CObjectArchive& operator>>(unsigned short& w); CObjectArchive& operator>>(int& i); CObjectArchive& operator>>(unsigned int & u); CObjectArchive& operator>>(long& l); CObjectArchive& operator>>(unsigned long & dw); CObjectArchive& operator>>(float& f); CObjectArchive& operator>>(double& d); CObjectArchive& operator>>(qint64 &n); CObjectArchive& operator>>( QString & s ); CObjectArchive& operator>>( QAction *pAction ); CObjectArchive& operator>>( QUuid& id ); public: QString filename; protected: QIODevice* m_pDevice; EGmArchiveMode m_eMode; bool m_bOpened; bool m_bQFileOpenedByMe; int m_currLength; }; /** * @brief : clone object general,use T1,and T1's Write function,T2 and T2's Load function * @param src : the source object( pls use pointer ) * @param pWrite : static function pointer or function object ,the signature should be like function1( T1 t, CObjectArchive & ) * @param dest :the dest object,that will same with source object( pls use pointer ) * @param pLoad : static function pointer or function object ,the signature should be like function2( T2 t, CObjectArchive & ) */ template< class T1, class _Pr1Write,class T2,class _Pr2Load > void CloneObjectGeneral( T1 src ,_Pr1Write pWrite, T2 dest,_Pr2Load pLoad ) { QByteArray byteArray; /* write object data to byteArray use src "write" function */ { CObjectArchive ar( byteArray,eStore); pWrite( src, ar ); } /* read object data from byteArray use dest "Load" function */ { CObjectArchive ar( byteArray,eLoad ); pLoad( dest,ar ); } } /** * save object srcWrite to a BLOB variant by function pWrite */ template< class T ,class _Pr1Write> QVariant SaveObjectToVtBLOB( T *srcWrite,_Pr1Write pWrite ) { /* write object data to byteArray use src "write" function */ QByteArray byteArray; { CObjectArchive ar( byteArray,eStore); pWrite( srcWrite, ar ); } // set to vtBLOB QVariant vtBLOB(byteArray); // return the vtBLOB return vtBLOB; }; /** * save object srcWrite to a BLOB variant */ template< class T > QVariant SaveObjectToVtBLOB2(T *srcWrite) { /* write object data to byteArray use src "Serialize" function */ QByteArray byteArray; { CObjectArchive ar( byteArray,eStore); srcWrite->Serialize(ar ); } // set to vtBLOB QVariant vtBLOB(byteArray); // return the vtBLOB return vtBLOB; }; /** * load object ObjectLoad from a BLOB variant by function pLoad */ template< class T ,class _Pr2Load> void LoadObjectFromVtBLOB(T *ObjectLoad ,QVariant &vtBLOB, _Pr2Load pLoad ) { // get byteArray from vtBLOB QByteArray byteArray = vtBLOB.toByteArray(); /* read object data from byteArray use src "_Pr2Load" function */ if( byteArray.size() >0 ) { CObjectArchive ar( byteArray,eLoad ); pLoad( ObjectLoad, ar ); } } /** * load object ObjectLoad from a BLOB variant */ template< class T > void LoadObjectFromVtBLOB2( T *ObjectLoad ,QVariant &vtBLOB ) { // get byteArray from vtBLOB QByteArray byteArray = vtBLOB.toByteArray(); /* read object data from byteArray use src "Serialize" function */ if( byteArray.size() >0 ) { CObjectArchive ar( byteArray,eLoad ); ObjectLoad->Serialize( ar ); } }; /** * use Serialize function to clone object from one to other * object must have "Serialize(CObjectArchive &ar)"function * @param src : source object * @param dest : destination object.destination object will fully equal source object ( deep copy,all of children and pointer content will be copied ) */ template< typename T> void CloneObject( const T &src , T &dest ) { QByteArray byteArray; /* write object data to byteArray use src "Serialize" function */ { CObjectArchive ar(byteArray,eStore); T &tmp = const_cast< T & >( src ); tmp.Serialize( ar ); } /* read object data from byteArray use dest "Serialize" function */ { CObjectArchive ar( byteArray,eLoad); dest.Serialize( ar ); } } template CObjectArchive & operator <<( CObjectArchive &ar,std::vector& v ) { int nSize = (int )v.size(); ar << nSize; for( long i = 0 ; i < nSize ; ++i ) { ar << v[i]; } return ar; }; template CObjectArchive & operator >>( CObjectArchive &ar,std::vector& v ) { v.clear(); int nSize(0); ar >> nSize; for( long i = 0 ; i < nSize ; ++i ) { T a; ar >> a; v.push_back( a ); } return ar; }; template CObjectArchive & operator <<( CObjectArchive &ar,std::set& v ) { int nSize = (int )v.size(); ar << nSize; typename std::set::iterator it = v.begin(); for( ; it!=v.end() ; ++it ) { ar << *it; } return ar; }; template CObjectArchive & operator >>( CObjectArchive &ar,std::set& v ) { v.clear(); int nSize(0); ar >> nSize; for( long i = 0 ; i < nSize ; ++i ) { T a; ar >> a; v.insert( a ); } return ar; }; /** * @brief 序列化一个QVector到二进制流 */ template CObjectArchive& operator<< (CObjectArchive& ar,const QVector& vec) { ar << (int)vec.size(); typename QVector::const_iterator it = vec.constBegin(); for(;it != vec.constEnd(); ++it) { ar << *it; } return ar; } /** * @brief 从二进制流反序列化到一个QVector */ template CObjectArchive& operator >> (CObjectArchive& ar, QVector& vec) { assert(vec.size()==0); int vecSize = 0; ar >> vecSize; T value; for(int i = 0; i> value; vec.push_back(value); } return ar; } #endif