Chinaunix首页 | 论坛 | 博客
  • 博客访问: 194794
  • 博文数量: 90
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2017-08-23 16:48
文章分类

全部博文(90)

文章存档

2015年(1)

2011年(21)

2010年(59)

2009年(9)

我的朋友

分类: C/C++

2010-07-14 10:18:54

当突然发现其实持久化控件属性可以用很简单的方法实现时,实在不是一件很舒服的事,因为也就意味着前面的努力白搞了;幸好,发现这种简单的方法还是有缺陷的,只能用于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中,可以发现前面的两行代码被注释掉了,去掉注释,那么就可以还原为用原来的方式持久化了。
 
阅读(2575) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~