当突然发现其实持久化控件属性可以用很简单的方法实现时,实在不是一件很舒服的事,因为也就意味着前面的努力白搞了;幸好,发现这种简单的方法还是有缺陷的,只能用于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了,其它的三个函数分别是用来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 CString str;
ar >> str;
m_saItems.Add(str);
}
}
else{
int n = m_saItems.GetSize();
ar << n;
for(int i=0; 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中,可以发现前面的两行代码被注释掉了,去掉注释,那么就可以还原为用原来的方式持久化了。