ActiveX控制項的MFC設計之旅-第19步

來源:互聯網
上載者:User
當突然發現其實持久化控制項屬性可以用很簡單的方法實現時,實在不是一件很舒服的事,因為也就意味著前面的努力白搞了;幸好,發現這種簡單的方法還是有缺陷的,只能用於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中,可以發現前面的兩行代碼被注釋掉了,去掉注釋,那麼就可以還原為用原來的方式持久化了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.