當突然發現其實持久化控制項屬性可以用很簡單的方法實現時,實在不是一件很舒服的事,因為也就意味著前面的努力白搞了;幸好,發現這種簡單的方法還是有缺陷的,只能用於IPersistStream介面的實現,而無法用於IPersistPropertyBag介面的實現,對於介面對象的持久化有點麻煩,等等。
不管怎樣,本文還是來講一講這種簡單的Serialize方法。
先從源碼分析開始:
實現IPersistStream介面的部分源碼如下:
STDMETHODIMP COleControl::XPersistStreamInit::Load(LPSTREAM pStm)
{
METHOD_PROLOGUE_EX(COleControl, PersistStreamInit)
return pThis->LoadState(pStm);
}
STDMETHODIMP COleControl::XPersistStreamInit::Save(LPSTREAM pStm,
BOOL fClearDirty)
{
METHOD_PROLOGUE_EX(COleControl, PersistStreamInit)
// Delegate to SaveState.
HRESULT hr = pThis->SaveState(pStm);
// Bookkeeping: Clear the dirty flag, if requested.
if (fClearDirty)
pThis->m_bModified = FALSE;
return hr;
}
可以看到,需要用到COleControl的兩個函數,LoadState和SaveState,來看這兩個函數
HRESULT COleControl::SaveState(IStream* pstm)
{
HRESULT hr = S_OK;
TRY
{
// Delegate to the Serialize method.
COleStreamFile file(pstm);
CArchive ar(&file, CArchive::store);
Serialize(ar);
}
CATCH_ALL(e)
{
hr = E_FAIL;
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
return hr;
}
HRESULT COleControl::LoadState(IStream* pstm)
{
HRESULT hr = S_OK;
TRY
{
// Delegate to the Serialize method.
COleStreamFile file(pstm);
CArchive ar(&file, CArchive::load);
Serialize(ar);
}
CATCH_ALL(e)
{
// The load failed. Delete any partially-initialized state.
OnResetState();
m_bInitialized = TRUE;
hr = E_FAIL;
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
// Clear the modified flag.
m_bModified = FALSE;
// Unless IOleObject::SetClientSite is called after this, we can
// count on ambient properties being available while loading.
m_bCountOnAmbients = TRUE;
// Properties have been initialized
m_bInitialized = TRUE;
// Uncache cached ambient properties
_afxAmbientCache->Cache(NULL);
return hr;
}
這兩個函數,最後歸為一個函數Serialize,頂多再加上一個OnResetState,再來看看這兩個函數是怎樣的
void COleControl::Serialize(CArchive& ar)
{
CArchivePropExchange px(ar);
DoPropExchange(&px);
if (ar.IsLoading())
{
BoundPropertyChanged(DISPID_UNKNOWN);
InvalidateControl();
}
}
void COleControl::OnResetState()
{
CResetPropExchange px;
DoPropExchange(&px);
InvalidateControl();
}
這兩個函數最後就到了我們熟悉的DoPropExchange函數上去了,現在我想也應該比較熟悉中間的過程了吧,在架構產生時,我們一般會看到 DoPropExchange和OnResetState,需要我們重載它們,以前經常會搞混,明明在DoPropExchange用PX_xxx函數時已經初始化了屬性了,為什麼還要用到OnResetState呢,它們是什麼關係呢,現在明白了,互補的關係,DoPropExchange中初始化一些屬性,在OnResetState中則可以初始化其它的一些在DoPropExchange中沒有初始化的資料。
搞清楚了這些關係後,接下來要做的事就也比較清楚了,我們知道IPersistStream介面最後要用到COleControl的Serialize,因此這所謂的更簡單,更最佳化,更靈活的操作就在於重載Serialize函數上,要注意的是,這時就不需要調用COleControl::Serialize(ar);了。當然,用嚮導產生時,本身也沒有加上的:)
不過在開始例子前,我們先還是來看看COleControl::DoPropExchange
void COleControl::DoPropExchange(CPropExchange* pPX)
{
ASSERT_POINTER(pPX, CPropExchange);
ExchangeExtent(pPX);
ExchangeStockProps(pPX);
}
可以發現,其實在COleControl中已經持久化了Extent和一些Stock屬性了。而架構產生時,還會有這句
void CToppCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
COleControl::DoPropExchange(pPX);
}
所以其實如果我們要自己用Serialize實現持久化的話,得先持久化這些東西,幸運的是,COleControl為我們提供了 SerializeExtent,SerializeStopProps,SerializeVersion和相應的初始化用的 ResetStockProps,ResetVersion。
回到我們在講屬性頁面時經常用到的例子topp
1.重載void Serialize(CArchive& ar)
void CToppCtrl::Serialize(CArchive& ar)
{
// COleControl::Serialize(ar);
// return;
DWORD dwVersion =
SerializeVersion(ar, MAKELONG(_wVerMinor, _wVerMajor));
SerializeExtent(ar);
SerializeStockProps(ar);
if (ar.IsStoring())
{ // storing code
ar << m_color;
}
else
{ // loading code
ar >> m_color;
}
SerializePicture(ar);
SerializeBox(ar);
SerializeItems(ar);
}
這裡的前面幾行代碼基本上是標準代碼了,不用怎麼修改的。
因為我們只有一個普通的屬性Color,所以簡單的ar<<m_color和ar>>m_color了,其它的三個函數分別是用來Serialize圖片屬性Picture,LPDISPATCH屬性Box和變數m_saItems的。
2.添加一個Serialize介面的函數SerializeUnknown(CArchive &ar, LPUNKNOWN *ppUnk, REFIID iid)。這裡基本上是從COleControl的ExchangePersistentProp代碼中抄來的,只是為了避免混淆,去掉了一個BYTE類型的表示預設介面的標誌,如果加上這個BYTE標誌,就可以和用標準方式持久化的控制項互用了,也就是說用標準方式儲存的控制項,將能用本文提供的方式讀取。
void CToppCtrl::SerializeUnknown(CArchive &ar, LPUNKNOWN *ppUnk, REFIID iid)
{
ASSERT_POINTER(ppUnk, LPUNKNOWN);
BOOL bResult = FALSE;
CArchiveStream stm(&ar);
if (ar.IsLoading())
{
if(*ppUnk){
(*ppUnk)->Release();
}
*ppUnk = NULL;
// read the CLSID
CLSID clsid;
ar >> clsid.Data1;
ar >> clsid.Data2;
ar >> clsid.Data3;
ar.Read(&clsid.Data4[0], sizeof clsid.Data4);
// check for GUID_NULL first and skip if found
if (IsEqualCLSID(clsid, GUID_NULL))
bResult = TRUE;
else
{
// otherwise will need a stream
LPSTREAM pstm = &stm;//_AfxGetArchiveStream(m_ar, stm);
if (IsEqualCLSID(clsid, CLSID_StdPicture) ||
IsEqualCLSID(clsid, _afx_CLSID_StdPicture2_V1))
{
// special case for pictures
bResult = SUCCEEDED(::OleLoadPicture(pstm, 0, FALSE, iid,
(void**)ppUnk));
}
else
{
// otherwise, seek back to the CLSID
LARGE_INTEGER li;
li.LowPart = (DWORD)(-(long)sizeof(CLSID));
li.HighPart = -1;
VERIFY(SUCCEEDED(pstm->Seek(li, STREAM_SEEK_CUR, NULL)));
// and load the object normally
CLSID clsid;
if (SUCCEEDED(::ReadClassStm(pstm, &clsid)) &&
(SUCCEEDED(::CoCreateInstance(clsid, NULL,
CLSCTX_SERVER | CLSCTX_REMOTE_SERVER,
iid, (void**)ppUnk)) ||
SUCCEEDED(::CoCreateInstance(clsid, NULL,
CLSCTX_SERVER & ~CLSCTX_REMOTE_SERVER,
iid, (void**)ppUnk))))
{
LPPERSISTSTREAM pps = NULL;
if (SUCCEEDED((*ppUnk)->QueryInterface(
IID_IPersistStream, (void**)&pps)) ||
SUCCEEDED((*ppUnk)->QueryInterface(
IID_IPersistStreamInit, (void**)&pps)))
{
ASSERT_POINTER(pps, IPersistStream);
bResult = SUCCEEDED(pps->Load(pstm));
pps->Release();
}
if (!bResult)
{
(*ppUnk)->Release();
*ppUnk = NULL;
}
}
}
}
}
else
{
ASSERT_NULL_OR_POINTER(*ppUnk, IUnknown);
// Check if *ppUnk and pUnkDefault are the same thing. If so, don't
// bother saving the object; just write a special flag instead.
if (*ppUnk != NULL)
{
LPPERSISTSTREAM pps = NULL;
if (SUCCEEDED((*ppUnk)->QueryInterface(
IID_IPersistStream, (void**)&pps)) ||
SUCCEEDED((*ppUnk)->QueryInterface(
IID_IPersistStreamInit, (void**)&pps)))
{
ASSERT_POINTER(pps, IPersistStream);
LPSTREAM pstm = &stm; //_AfxGetArchiveStream(ar, stm);
bResult = SUCCEEDED(::OleSaveToStream(pps, pstm));
pps->Release();
}
}
else
{
// If no object, write null class ID.
ar.Write(&GUID_NULL, sizeof(GUID));
}
}
// throw exception in case of unthrown errors
if (!bResult)
AfxThrowArchiveException(CArchiveException::generic);
}
3.實現上面提到的三個SerializeXXX函數
void CToppCtrl::SerializePicture(CArchive &ar)
{
ASSERT_POINTER(&m_pic, CPictureHolder);
LPUNKNOWN& pUnk = (LPUNKNOWN&)m_pic.m_pPict;
SerializeUnknown(ar, &pUnk, IID_IPicture);
}
void CToppCtrl::SerializeBox(CArchive &ar)
{
LPUNKNOWN& pUnk = (LPUNKNOWN&)m_pboxdisp;
SerializeUnknown(ar, &pUnk, IID_IDispatch);
}
void CToppCtrl::SerializeItems(CArchive &ar)
{
if(ar.IsLoading())
{
int n = 0;
ar >> n;
for(int i=0; i<n; i++){
CString str;
ar >> str;
m_saItems.Add(str);
}
}
else{
int n = m_saItems.GetSize();
ar << n;
for(int i=0; i<n; i++){
CString str = m_saItems[i];
ar << str;
}
}
}
值得注意的是SerializeItms,我們可以看到對於自訂資訊的持久化,用本文提供的方式將能大大簡化。
4.添加初始化
void CToppCtrl::OnResetState()
{
// COleControl::OnResetState(); // Resets defaults found in DoPropExchange
// return;
// TODO: Reset any other control state here.
ResetVersion(MAKELONG(_wVerMinor, _wVerMajor));
ResetStockProps();
CToppBox* pbox = new CToppBox;
m_pboxdisp = pbox->GetIDispatch(FALSE);
}
前面兩行代碼,ResetVersion和ResetStockProps也基本上標準配置,不用怎麼修改。根據需要,我們只加上了m_pboxdisp的初始化。
注意:DoPropExchange中的代碼不用刪掉,另外在DoPropExchange和OnResetState中,可以發現前面的兩行代碼被注釋掉了,去掉注釋,那麼就可以還原為用原來的方式持久化了。