logplus/logPlus/ObjectArchive.h
2025-10-29 17:23:30 +08:00

790 lines
20 KiB
C++

/**
* @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 <stdio.h>
#include <map>
#include <vector>
#include <set>
#ifdef _WINDOWS
#include <typeinfo.h>
#endif
#include <QString>
#include <QDataStream>
#include <QAction>
#include <QSet>
#include <QMap>
#include <QMessageBox>
#include <QBuffer>
#include <QVariant>
#include <QUuid>
#include <QVector>
//#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<int> ( (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<int>( 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<class T>
CObjectArchive & operator <<( CObjectArchive &ar,std::vector<T>& v )
{
int nSize = (int )v.size();
ar << nSize;
for( long i = 0 ; i < nSize ; ++i )
{
ar << v[i];
}
return ar;
};
template<class T>
CObjectArchive & operator >>( CObjectArchive &ar,std::vector<T>& 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<class T>
CObjectArchive & operator <<( CObjectArchive &ar,std::set<T>& v )
{
int nSize = (int )v.size();
ar << nSize;
typename std::set<T>::iterator it = v.begin();
for( ; it!=v.end() ; ++it )
{
ar << *it;
}
return ar;
};
template<class T>
CObjectArchive & operator >>( CObjectArchive &ar,std::set<T>& 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 <typename T>
CObjectArchive& operator<< (CObjectArchive& ar,const QVector<T>& vec)
{
ar << (int)vec.size();
typename QVector<T>::const_iterator it = vec.constBegin();
for(;it != vec.constEnd(); ++it)
{
ar << *it;
}
return ar;
}
/**
* @brief 从二进制流反序列化到一个QVector
*/
template <typename T>
CObjectArchive& operator >> (CObjectArchive& ar, QVector<T>& vec)
{
assert(vec.size()==0);
int vecSize = 0;
ar >> vecSize;
T value;
for(int i = 0; i<vecSize; ++i)
{
ar >> value;
vec.push_back(value);
}
return ar;
}
#endif