アンマネージ同期プロバイダーを作成する方法

このトピックでは、C++ などのアンマネージ言語を使用して、カスタム データ ストアのデータを同期する Sync Framework 同期プロバイダーの作成方法を説明します。

このトピックは、C++ および COM の概念について基本的な知識がある方を対象としています。

このトピックの例では、次に示す Sync Framework のインターフェイスを中心に説明します。

同期プロバイダーについて

同期プロバイダーは、同期時のレプリカを表すソフトウェア コンポーネントです。レプリカは、同期プロバイダーを使用することで、そのデータを他のレプリカとの間で同期できるようになります。同期を実行するには、まず、アプリケーションで同期セッション オブジェクトを作成し、それを 2 つの ISyncProvider オブジェクトに接続してセッションを開始します。そのうちの 1 つは同期元のレプリカを表すプロバイダーです。変更された項目のメタデータは、同期元のレプリカから、その IKnowledgeSyncProvider::GetChangeBatch メソッドを介して提供されます。また、項目データは、ISynchronousDataRetriever オブジェクトを介して提供されます。もう 1 つは、同期先レプリカを表すプロバイダーです。同期先のレプリカは、変更された項目のメタデータを IKnowledgeSyncProvider::ProcessChangeBatch メソッドを介して受け取り、Sync Framework が備えている ISynchronousChangeApplier オブジェクトと、それ独自の ISynchronousChangeApplierTarget オブジェクトを使用することによって、項目ストアに変更を適用します。

同期プロバイダーのロールの詳細については、「標準のカスタム プロバイダーの実装」を参照してください。

ビルド要件

  • Synchronization.h : Sync Framework コンポーネントの宣言。

    #include <synchronization.h>
    
  • Synchronizationerrors.h : カスタム エラー コード。

    #include <synchronizationerrors.h>
    
  • Synchronization.lib : インポート ライブラリ。

このトピックのコード例では、Sync Framework の同期コミュニティに対し、レプリカを同期元や同期先として参加させるために必要な、基本的なインターフェイス メソッドの実装方法を紹介しています。この例に使用されているレプリカは XML ファイルです。同期する項目は、このファイルに含まれている XML ノードです。コード内では、XML ノードが IXMLDOMNode インターフェイスによって表されています。この例では、Metadata Storage Service API を使って実装されたカスタム メタデータ ストアも使用しています。Metadata Storage Service および他の Sync Framework コンポーネントについては、「Sync Framework Metadata Storage Service」を参照してください。

メタデータ ストアと XML ストアは、どちらもプロバイダー クラスのメンバーとして宣言されます。

CMetadataMgr* m_pMetadataMgr;
CItemStore* m_pItemStore;

ISyncProvider および IKnowledgeSyncProvider の実装

プロバイダーへのエントリ ポイントは、ISyncProvider インターフェイスです。このインターフェイスは、より強力な他のプロバイダー インターフェイスの基本クラスとして使用されます。この例では、IKnowledgeSyncProvider インターフェイスを使用します。

IKnowledgeSyncProvider の宣言

クラスの継承一覧に IKnowledgeSyncProvider を追加します。

class CXMLProvider : public IKnowledgeSyncProvider

クラスの宣言に ISyncProvider メソッドを追加します。

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

クラスの宣言に IKnowledgeSyncProvider メソッドを追加します。

STDMETHOD(BeginSession)(
    SYNC_PROVIDER_ROLE role,
    ISyncSessionState * pSessionState);

STDMETHOD(GetSyncBatchParameters)(
    ISyncKnowledge ** ppSyncKnowledge,
    DWORD * pdwRequestedBatchSize);

STDMETHOD(GetChangeBatch)(
    DWORD dwBatchSize,
    ISyncKnowledge * pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever);
   
STDMETHOD(GetFullEnumerationChangeBatch)(
    DWORD dwBatchSize,
    const BYTE * pbLowerEnumerationBound,
    ISyncKnowledge * pSyncKnowledgeForDataRetrieval,
    ISyncFullEnumerationChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever);

STDMETHOD(ProcessChangeBatch)(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics);

STDMETHOD(ProcessFullEnumerationChangeBatch)(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncFullEnumerationChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics);

STDMETHOD(EndSession)(
    ISyncSessionState * pSessionState);

GetIdParameters メソッド

ISyncSession オブジェクトの作成時、Sync Framework は、同期元と同期先の両方のプロバイダーの ISyncProvider::GetIdParameters を呼び出します。このメソッドからは、そのプロバイダーが使用する ID 形式スキーマが返されます。このスキーマは、両方のプロバイダーで一致している必要があります。この例の実装では、プロバイダーに対する ID 形式は一定なので、グローバル定数を使用しています。

const ID_PARAMETERS c_idParams = 
{
    sizeof(ID_PARAMETERS), // dwSize
    { FALSE, sizeof(GUID) }, // replicaId
    { FALSE, sizeof(SYNC_GID) }, // itemId
    { FALSE, 1 }, // changeUnitId
};

グローバル定数を使用することによって、このメソッドの実装が非常に簡単になります。

STDMETHODIMP CXMLProvider::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

BeginSession メソッド

次に、Sync Framework は、同期元と同期先の両方のプロバイダーの IKnowledgeSyncProvider::BeginSession を呼び出します。このメソッドは、同期セッションに参加しようとしていることをプロバイダーに伝え、そのプロバイダーにセッション状態情報を含んだオブジェクトを渡します。この実装では、セッション状態オブジェクトが保存されます。

STDMETHODIMP CXMLProvider::BeginSession(
    SYNC_PROVIDER_ROLE role,
    ISyncSessionState * pSessionState)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSessionState)
    {
        hr = E_POINTER;
    }
    else
    {
        // This method should not be called twice.
        if (NULL != m_pSessionState || NULL == m_pMetadataMgr)
        {
            hr = SYNC_E_INVALID_OPERATION;
        }
        else
        {
            // Store the role and the session state object.
            m_role = role;

            pSessionState->AddRef();
            m_pSessionState = pSessionState;
            hr = S_OK;
        }
    }

    return hr;
}

GetSyncBatchParameters メソッド

次に、Sync Framework は、同期先プロバイダーの IKnowledgeSyncProvider::GetSyncBatchParameters を呼び出します。これは、同期元プロバイダー側で変更バッチに含める必要のある変更の数を取得し、同期先プロバイダーの現在のナレッジを取得するメソッドです。この実装では、メタデータ ストアからナレッジを抽出し、バッチ サイズを 10 に設定しています。

STDMETHODIMP CXMLProvider::GetSyncBatchParameters(
    ISyncKnowledge ** ppSyncKnowledge,
    DWORD * pdwRequestedBatchSize)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == ppSyncKnowledge || NULL == pdwRequestedBatchSize)
    {
        hr = E_POINTER;
    }
    else
    {
        _ASSERT(NULL != m_pMetadataMgr);
    
        *pdwRequestedBatchSize = 10;

        hr = m_pMetadataMgr->GetKnowledge(ppSyncKnowledge);
    }

    return hr;
}

GetChangeBatch メソッド

同期セッションが本格的に開始されるのは、Sync Framework によって同期元プロバイダーの IKnowledgeSyncProvider::GetChangeBatch が呼び出されたときです。このメソッドは、同期先プロバイダーに送信する変更のバッチを取得するほか、Data Retriever インターフェイスを返します。同期先プロバイダーでは、このインターフェイスを使って、同期先レプリカに適用された変更の項目データを取得します。Sync Framework は、最後のバッチが送信されるまで、GetChangeBatch を繰り返し呼び出します。同期元プロバイダーは、ISyncChangeBatchBase::SetLastBatch メソッドを呼び出すことにより、バッチが最後のバッチであることを示します。変更の列挙タスクは、メタデータ ストアの GetChangeBatch メソッドに委任されます。XML 項目ストア オブジェクトは Data Retriever インターフェイスを実装しているため、その IUnknown インターフェイスが返されます。

STDMETHODIMP CXMLProvider::GetChangeBatch(
    DWORD dwBatchSize,
    ISyncKnowledge * pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSyncKnowledge || NULL == ppSyncChangeBatch || NULL == ppUnkDataRetriever)
    {
        hr = E_POINTER;
    }
    else
    {
        _ASSERT(NULL != m_pMetadataMgr);
        hr = m_pMetadataMgr->GetChangeBatch(dwBatchSize, pSyncKnowledge, ppSyncChangeBatch);
        if (SUCCEEDED(hr))
        {
            hr = m_pItemStore->QueryInterface(IID_IUnknown, (void**)ppUnkDataRetriever);
        }
    }

    return hr;
}

メタデータ ストアに実装された GetChangeBatch メソッドは、メタデータ ストア内の項目を列挙し、同期先のナレッジと照らし合わせながら、それぞれの項目のバージョンを確認します。同期先のレプリカにとって未知の変更があった場合は、その変更が、返される変更バッチに追加されます。

STDMETHODIMP CMetadataMgr::GetChangeBatch(
    DWORD dwBatchSize,
    ISyncKnowledge *pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch)
{
    HRESULT hr = E_UNEXPECTED;

    ISyncChangeBatch* pChangeBatch = NULL;
    ISyncKnowledge* pMappedDestKnowledge = NULL;
    ISyncKnowledge* pSourceKnowledge = NULL;

    if (NULL == pSyncKnowledge || NULL == ppSyncChangeBatch)
    {
        hr = E_POINTER;
    }
    else
    {
        // Get our (source) knowledge object, map the remote (destination) knowledge for local use, 
        // and get our replica ID.
        GUID guidReplicaID;
        hr = GetKnowledge(&pSourceKnowledge);
        if (SUCCEEDED(hr))
        {
            hr = pSourceKnowledge->MapRemoteToLocal(pSyncKnowledge, &pMappedDestKnowledge);
            if (SUCCEEDED(hr))
            {
                ULONG cbID = sizeof(guidReplicaID);
                hr = GetReplicaId((BYTE*)&guidReplicaID, &cbID);
            }
        }

        if (SUCCEEDED(hr))
        {
            // Create a new change batch object.  We'll fill this object with changes to send.
            IProviderSyncServices* pProvSvc = NULL;
            // This helper function creates and initializes the IProviderSyncServices interface.
            hr = GetProviderSyncServices(&c_idParams, &pProvSvc);
            if (SUCCEEDED(hr))
            {
                hr = pProvSvc->CreateChangeBatch(pSyncKnowledge, NULL, &pChangeBatch);            

                pProvSvc->Release();
                pProvSvc = NULL;
            }
        }

        // Enumerate the items in our store and add new changes to the change batch.
        if (SUCCEEDED(hr))
        {
            // Begin an unordered group in our change batch. All change items will be added to this group.
            hr = pChangeBatch->BeginUnorderedGroup();
            if (SUCCEEDED(hr))
            {
                ULONG cFetched = 1;
                IItemMetadata* pItemMeta = NULL;
                SYNC_GID gidItem;
                ULONG cbgid = sizeof(gidItem);
                SYNC_VERSION verCur;
                SYNC_VERSION verCreate;
                hr = Reset();
                while (S_OK == hr)
                {
                    hr = Next(1, &pItemMeta, &cFetched);
                    if (S_OK == hr)
                    {
                        hr = pItemMeta->GetGlobalId((BYTE*)&gidItem, &cbgid);
                        if (SUCCEEDED(hr))
                        {
                            hr = pItemMeta->GetChangeVersion(&verCur);
                            if (SUCCEEDED(hr))
                            {
                                // Find out whether the destination already knows about this change.
                                hr = pMappedDestKnowledge->ContainsChange((BYTE*)&guidReplicaID,
                                    (BYTE*)&gidItem, &verCur);
                                if (S_FALSE == hr)
                                {
                                    // S_FALSE means the destination does not know about the 
                                    // change, so add it to the change batch.
                                    DWORD dwFlags = 0;
                                    BOOL fTomb = 0;
                                    hr = pItemMeta->GetIsDeleted(&fTomb);
                                    if (fTomb)
                                    {
                                        dwFlags = SYNC_CHANGE_FLAG_DELETED;                            
                                    }

                                    hr = pItemMeta->GetCreationVersion(&verCreate);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pChangeBatch->AddItemMetadataToGroup((BYTE*)&guidReplicaID, 
                                            (BYTE*)&gidItem, &verCur, &verCreate, dwFlags, 0, NULL);
                                    }
                                }
                            }
                        }

                        pItemMeta->Release();
                    }
                }
            }

            if (SUCCEEDED(hr))
            {
                // We always send the entire set of changes, so every batch is the last batch. 
                // If this flag is not set Sync Framework will call GetChangeBatch again.
                hr = pChangeBatch->SetLastBatch();
            }

            if (SUCCEEDED(hr))
            {
                // Close the change batch group that contains our changes.
                hr = pChangeBatch->EndUnorderedGroup(pSourceKnowledge, TRUE);
            }
        }

        if (NULL != pChangeBatch)
        {
            if (SUCCEEDED(hr))
            {
                // Return the change batch we've constructed.  This will be sent to the 
                // destination provider.
                *ppSyncChangeBatch = pChangeBatch;
            }
            else
            {
                pChangeBatch->Release();            
            }
        }

        if (NULL != pMappedDestKnowledge)
        {
            pMappedDestKnowledge->Release();
        }
        if (NULL != pSourceKnowledge)
        {
            pSourceKnowledge->Release();
        }
    }

    return hr;
}

ProcessChangeBatch メソッド

Sync Framework が、同期元プロバイダーの GetChangeBatch メソッドを呼び出して、変更バッチを取得した後、Sync Framework は、同期先プロバイダーの IKnowledgeSyncProvider::ProcessChangeBatch を呼び出します。このメソッドによって、変更が同期先のレプリカへと適用されます。このメソッドは、同期元プロバイダーから GetChangeBatch を使って取得された各バッチにつき 1 回呼び出されます。この実装では、メタデータ ストアの GetItemBatchVersions メソッドを使用して、同期元プロバイダーから項目のローカル バージョン情報を取得します。さらに、Sync Framework によって実装された ISynchronousNotifyingChangeApplier オブジェクトを作成して、その ISynchronousNotifyingChangeApplier::ApplyChanges メソッドを呼び出します。

STDMETHODIMP CXMLProvider::ProcessChangeBatch(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSourceChangeBatch || NULL == pUnkDataRetriever || NULL == pSyncSessionStatistics)
    {
        hr = E_POINTER;
    }
    else
    {
        IEnumSyncChanges* pDestinationChangeEnum = NULL;

        _ASSERT(NULL != m_pMetadataMgr);

        // Obtain the local (destination) versions for the items in the source change batch.
        hr = m_pMetadataMgr->GetItemBatchVersions(pSourceChangeBatch, &pDestinationChangeEnum);
        if (SUCCEEDED(hr))
        {
            IProviderSyncServices* pProviderSvc = NULL;
            hr = GetProviderSyncServices(&c_idParams, &pProviderSvc);
            if (SUCCEEDED(hr))
            {
                // Create a standard change applier from Sync Framework.
                ISynchronousNotifyingChangeApplier* pChangeApplier = NULL;
                hr = pProviderSvc->CreateChangeApplier(IID_ISynchronousNotifyingChangeApplier,
                    (void**)&pChangeApplier);
                if (SUCCEEDED(hr))
                {
                    ISyncKnowledge* pDestinationKnowledge = NULL;
                    hr = m_pMetadataMgr->GetKnowledge(&pDestinationKnowledge);
                    if (SUCCEEDED(hr))
                    {
                        // Have the change applier process the change batch and apply changes.
                        // This method will call the change applier target methods to save
                        // changes and conflicts.  It will also pass the data retriever
                        // interface to the change applier target so it can retrieve item data.
                        hr = pChangeApplier->ApplyChanges(resolutionPolicy, pSourceChangeBatch, 
                            pUnkDataRetriever, pDestinationChangeEnum, pDestinationKnowledge, 
                            NULL, this, m_pSessionState, pCallback);
                        
                        pDestinationKnowledge->Release();
                    }

                    pChangeApplier->Release();
                }

                pProviderSvc->Release();
            }

            pDestinationChangeEnum->Release();
        }
    }

    return hr;
}

同期元プロバイダーから変更バッチとして送信された変更は、メタデータ ストアの GetItemBatchVersions メソッドによって列挙されます。項目が同期先メタデータに存在する場合は、バージョン情報を保持するために特別に作成された新しいバッチに、そのバージョン情報が追加されます。項目が同期先メタデータに存在しない場合は、バージョン バッチ内で、新しい項目としてフラグが設定されます。さらに、メソッドからは、このバージョン バッチが返されます。

STDMETHODIMP CMetadataMgr::GetItemBatchVersions(
    ISyncChangeBatch * pRemoteSyncChangeBatch,
    IEnumSyncChanges ** ppLocalVersionsEnum)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pRemoteSyncChangeBatch || NULL == ppLocalVersionsEnum)
    {
        hr = E_POINTER;
    }
    else
    {
        IProviderSyncServices* pProvSvc;
        hr = GetProviderSyncServices(&c_idParams, &pProvSvc);
        if (SUCCEEDED(hr))
        {
            IDestinationChangeVersionsBuilder* pDestChangeBuilder = NULL;
            hr = pProvSvc->CreateDestinationChangeVersionsBuilder(&pDestChangeBuilder);
            if (SUCCEEDED(hr))
            {
                IEnumSyncChanges* pRemoteEnum = NULL;
                hr = pRemoteSyncChangeBatch->GetChangeEnumerator(&pRemoteEnum);
                if (SUCCEEDED(hr))
                {
                    ULONG cFetched;

                    ISyncChange* pChange;
                    SYNC_GID gidItem;
                    DWORD cbID = sizeof(gidItem);
                    DWORD dwFlags;
                    SYNC_VERSION verCurrent;
                    SYNC_VERSION verCreation;
                    HRESULT hrEnum = S_OK;
                    while (S_OK == hrEnum && SUCCEEDED(hr))
                    {
                        pChange = NULL;
                        hrEnum = pRemoteEnum->Next(1, &pChange, &cFetched);
                        if (S_OK == hrEnum)
                        {
                            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbID);
                            if (SUCCEEDED(hr))
                            {
                                // Try to find the item in the local (destination) metadata.
                                IItemMetadata* pItem = NULL;
                                hr = FindItemMetadataByGlobalId((BYTE*)&gidItem, &pItem);
                                if (S_OK == hr)
                                {
                                    // S_OK means the item exists in our local store.
                                    // Extract its version and tombstone information.
                                    dwFlags = 0;

                                    BOOL fTombstone = FALSE;
                                    hr = pItem->GetIsDeleted(&fTombstone);
                                    if (SUCCEEDED(hr))
                                    {
                                        if (fTombstone)
                                        {
                                            dwFlags = SYNC_CHANGE_FLAG_DELETED;
                                        }
                                    }

                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pItem->GetChangeVersion(&verCurrent);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = pItem->GetCreationVersion(&verCreation);                                            
                                        }
                                    }

                                    pItem->Release();
                                }
                                else if (S_FALSE == hr)
                                {
                                    // S_FALSE means this item does not exist in our local store.
                                    // Set versions to 0 and flag it as a new item.
                                    verCurrent.dwLastUpdatingReplicaKey = 0;
                                    verCurrent.ullTickCount = 0;
                                    verCreation.dwLastUpdatingReplicaKey = 0;
                                    verCreation.ullTickCount = 0;
                                    dwFlags = SYNC_CHANGE_FLAG_DOES_NOT_EXIST;
                                }

                                if (SUCCEEDED(hr))
                                {
                                    // Add the item to the batch of destination versions.
                                    GUID guidReplicaID = GUID_NULL;
                                    ULONG cbID = sizeof(guidReplicaID);
                                    hr = GetReplicaId((BYTE*)&guidReplicaID, &cbID);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pDestChangeBuilder->AddItemMetadata((BYTE*)&guidReplicaID,
                                            (BYTE*)&gidItem, &verCurrent, &verCreation, dwFlags, NULL);
                                    }
                                }
                            }

                            pChange->Release();
                        }
                    }

                    if (FAILED(hrEnum))
                    {
                        hr = hrEnum;                    
                    }

                    pRemoteEnum->Release();                
                }

                if (SUCCEEDED(hr))
                {
                    hr = pDestChangeBuilder->GetChangeEnumerator(ppLocalVersionsEnum);               
                }

                pDestChangeBuilder->Release();
            }

            pProvSvc->Release();        
        }
    }

    return hr;
}

EndSession メソッド

同期元プロバイダーから最新のバッチが送信され、変更が同期先プロバイダーのデータ ストアに適用されると、Sync Framework が、同期元と同期先の両方のプロバイダーの IKnowledgeSyncProvider::EndSession を呼び出します。このメソッドにより、同期セッションが終了し、セッションに関連付けられたリソースをすべて解放する必要があるという情報が、プロバイダーに伝えられます。この実装により、BeginSession の呼び出しで保存されたセッション状態オブジェクトが解放されます。

STDMETHODIMP CXMLProvider::EndSession(
    ISyncSessionState * pSessionState)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == m_pSessionState)
    {
        hr = SYNC_E_INVALID_OPERATION;
    }
    else
    {
        m_pSessionState->Release();
        m_pSessionState = NULL;
        hr = S_OK;
    }

    return hr;
}

実装されないメソッド

このサンプルでは、削除マークの付いた項目をメタデータ ストアから削除することはしません。したがって、次に示したメソッドは不要です。これらのメソッドからは、E_NOTIMPL が返される場合があります。

ISynchronousNotifyingChangeApplierTarget の実装

このインターフェイスは、同期先プロバイダーが ISynchronousNotifyingChangeApplier::ApplyChanges メソッドを (通常は ProcessChangeBatch メソッドで) 呼び出すときに、Sync Framework に提供されます。ISynchronousNotifyingChangeApplierTarget には、変更の適用時に呼び出されるメソッドが存在します。これらのメソッドは、同期先プロバイダーでのみ呼び出されます。

ISynchronousNotifyingChangeApplierTarget の宣言

クラスの継承一覧に ISynchronousNotifyingChangeApplierTarget を追加します。

class CXMLProvider : public IKnowledgeSyncProvider
    , ISynchronousNotifyingChangeApplierTarget

クラスの宣言に ISynchronousNotifyingChangeApplierTarget メソッドを追加します。

STDMETHOD(GetDataRetriever)(
    IUnknown ** ppDataRetriever);

STDMETHOD(GetCurrentTickCount)(
    ULONGLONG * pTickCount);

STDMETHOD(GetDestinationVersion)(
    ISyncChange * pSourceChange,
    ISyncChange ** ppDestinationVersion);

STDMETHOD(SaveChange)(
    SYNC_SAVE_ACTION  ssa,
    ISyncChange * pChange,
    ISaveChangeContext * pSaveContext);

STDMETHOD(SaveChangeWithChangeUnits)(
    ISyncChange * pChange,
    ISaveChangeWithChangeUnitsContext * pSaveContext);

STDMETHOD(SaveConflict)(
    ISyncChange * pChange,
    IUnknown * pUnkData,
    ISyncKnowledge * pConflictKnowledge);

STDMETHOD(SaveKnowledge)(
    ISyncKnowledge * pSyncKnowledge,
    IForgottenKnowledge * pForgottenKnowledge);

GetIdParameters メソッド

Sync Framework は、ISynchronousNotifyingChangeApplierTarget::GetIdParameters を呼び出して、プロバイダーの ID 形式スキーマを取得します。この例では、同じクラスを使用して、IKnowledgeSyncProviderISynchronousNotifyingChangeApplierTarget の両方を実装します。したがって、この実装は、ISyncProvider::GetIdParameters のものと同じです。

STDMETHODIMP CXMLProvider::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

GetCurrentTickCount

Sync Framework は、ISynchronousNotifyingChangeApplierTarget::GetCurrentTickCount を呼び出すことによって、レプリカのティック数をインクリメントしたり取得したりします。この実装では、メタデータ ストアの GetNextTickCount メソッドを呼び出します。

STDMETHODIMP CXMLProvider::GetCurrentTickCount(
    ULONGLONG * pTickCount)
{
    _ASSERT(NULL != m_pMetadataMgr);
    return m_pMetadataMgr->GetNextTickCount(pTickCount);
}

メタデータ ストアの GetNextTickCount メソッドは、レプリカのティック数をインクリメントして返します。

STDMETHODIMP CMetadataMgr::GetNextTickCount(
     ULONGLONG * pNextTickCount)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pNextTickCount)
    {
        hr = E_POINTER;
    }
    else
    {
        // Get the local tick count, increment it, store it, and return it.
        ULONGLONG ullTickCount = -1;
        hr = GetTickCount(&ullTickCount);
        if (SUCCEEDED(hr))
        {
            ++ullTickCount;
            hr = SetTickCount(ullTickCount);
            if (SUCCEEDED(hr))
            {
                *pNextTickCount = ullTickCount;            
            }
        }
    }

    return hr;
}

SaveChange

変更の適用時には、Sync Framework が、同期先レプリカに適用される各変更について、ISynchronousNotifyingChangeApplierTarget::SaveChange を呼び出します。この実装では、新しい項目、変更された項目、および削除された項目が適切に処理され、項目ストア内の項目データと、メタデータ ストア内の項目メタデータとが更新されます。

STDMETHODIMP CXMLProvider::SaveChange(
    SYNC_SAVE_ACTION ssa,
    ISyncChange * pChange,
    ISaveChangeContext * pSaveContext)
{
    HRESULT hr = E_UNEXPECTED;

    _ASSERT(NULL != m_pItemStore);

    if (NULL == pChange || NULL == pSaveContext)
    {
        hr = E_POINTER;
    }
    else
    {
        // First save or delete the item data itself.
        switch (ssa)
        {
        case SSA_DELETE_AND_REMOVE_TOMBSTONE:
        {
            // This sample does not track forgotten knowledge and so cannot properly
            // handle this action.
            hr = E_UNEXPECTED;
            break;
        }

        case SSA_CREATE:
        case SSA_UPDATE_VERSION_AND_DATA:
        case SSA_UPDATE_VERSION_AND_MERGE_DATA:
        {
            // Save the item in the data store.

            // This IUnknown interface is the interface returned by the data retriever's
            // LoadChangeData method.
            IUnknown* pUnk = NULL;
            hr = pSaveContext->GetChangeData(&pUnk);
            if (S_OK == hr)
            {
                // The item is an XML node.
                IXMLDOMNode* pNode = NULL;
                hr = pUnk->QueryInterface(__uuidof(pNode), (void**)&pNode);
                if (SUCCEEDED(hr))
                {
                    // Have the data store save the item.
                    hr = m_pItemStore->SaveItem(pChange, pNode);

                    pNode->Release();
                }

                pUnk->Release();
            }

            break;
        }

        case SSA_DELETE_AND_STORE_TOMBSTONE:
        {
            // Delete the item from the data store.
            hr = m_pItemStore->DeleteItem(pChange);
        }
            break;

        case SSA_UPDATE_VERSION_ONLY:
        {
            // Update the version only, so nothing to do in the data store.
            hr = S_OK;
        }
            break;

        default:
            hr = E_INVALIDARG;
        }

        // Now update the metadata for the item in the metadata store.
        if (SUCCEEDED(hr))
        {
            SYNC_GID gidItem;
            DWORD cbItemID = sizeof(gidItem);
            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbItemID);
            if (SUCCEEDED(hr))
            {
                // Save the item metadata to the metadata store.
                // First extract the information from the change.
                GUID guidReplicaID;
                ULONG cbReplicaID = sizeof(guidReplicaID);
                hr = m_pMetadataMgr->GetReplicaId((BYTE*)&guidReplicaID, &cbReplicaID);
                if (SUCCEEDED(hr))
                {
                    SYNC_VERSION verCurrent;
                    hr = pChange->GetChangeVersion((BYTE*)&guidReplicaID, &verCurrent);
                    if (SUCCEEDED(hr))
                    {
                        SYNC_VERSION verCreation;
                        hr = pChange->GetCreationVersion((BYTE*)&guidReplicaID, &verCreation);
                        if (SUCCEEDED(hr))
                        {
                            DWORD dwFlags;
                            hr = pChange->GetFlags(&dwFlags);
                            if (SUCCEEDED(hr))
                            {
                                // Try to find the item in the metadata store.
                                IItemMetadata* pItem = NULL;
                                hr = m_pMetadataMgr->FindItemMetadataByGlobalId((BYTE*)&gidItem, 
                                    &pItem);
                                if (S_FALSE == hr)
                                {
                                    // S_FALSE means the item does not exist in the metadata store.
                                    // Therefore it must be a new item.  Create it and set its
                                    // creation version.
                                    hr = m_pMetadataMgr->CreateNewItemMetadata(&pItem);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pItem->SetGlobalId((BYTE*)&gidItem);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = pItem->SetCreationVersion(&verCreation);
                                        }
                                    }
                                }

                                // Set the item's change version and tombstone status.
                                if (SUCCEEDED(hr))
                                {
                                    if (dwFlags & SYNC_CHANGE_FLAG_DELETED)
                                    {
                                        hr = pItem->MarkAsDeleted(&verCurrent);
                                    }
                                    else
                                    {
                                        hr = pItem->SetChangeVersion(&verCurrent);
                                    }
                                }

                                // Commit the item change and update the knowledge.
                                if (SUCCEEDED(hr))
                                {
                                    hr = m_pMetadataMgr->SaveItemMetadata(pItem);
                                    if (SUCCEEDED(hr))
                                    {
                                        ISyncKnowledge* pUpdatedKnowledge = NULL;
                                        IForgottenKnowledge* pUpdatedForgottenKnowledge = NULL;
                                        hr = pSaveContext->GetKnowledgeForScope(&pUpdatedKnowledge, &pUpdatedForgottenKnowledge);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = m_pMetadataMgr->SetKnowledge(pUpdatedKnowledge);

                                            pUpdatedKnowledge->Release();

                                            if (NULL != pUpdatedForgottenKnowledge)
                                            {
                                                // This sample does not use forgotten knowledge, so it is an error to receive
                                                // forgotten knowledge from the save context.
                                                hr = E_UNEXPECTED;

                                                pUpdatedForgottenKnowledge->Release();
                                            }
                                        }
                                    }
                                }

                                if (NULL != pItem)
                                {
                                    pItem->Release();                                    
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return hr;
}

SaveKnowledge

各変更バッチを処理した後、Sync Framework は、同期先プロバイダーが、新しい変更を含んだナレッジを保存できるよう、ISynchronousNotifyingChangeApplierTarget::SaveKnowledge を呼び出します。この実装では、ナレッジ オブジェクトがメタデータ ストアに保存され、また、既存のナレッジは上書きされます。

STDMETHODIMP CXMLProvider::SaveKnowledge(
    ISyncKnowledge * pSyncKnowledge,
    IForgottenKnowledge * pForgottenKnowledge)
{
    HRESULT hr = E_UNEXPECTED;

    _ASSERT(NULL != m_pMetadataMgr);

    if (NULL == pSyncKnowledge)
    {
        hr = E_POINTER;    
    }
    else if (NULL != pForgottenKnowledge)
    {
        // This sample does not support forgotten knowledge, so it is an error to receive it in this method.bb
        hr = E_INVALIDARG;
    }
    else
    {
        hr = m_pMetadataMgr->SetKnowledge(pSyncKnowledge);
    }
    
    return hr;
}

実装されないメソッド

基本的な同期シナリオでは、次のメソッドは不要です。単に E_NOTIMPL が返される場合があります。

ISynchronousDataRetriever の実装

Sync Framework には、GetChangeBatch の呼び出しの応答として、同期元プロバイダーから ISynchronousDataRetriever が返されます。ISynchronousDataRetriever は、ProcessChangeBatch の呼び出しで、同期先プロバイダーに送信されます (通常は、Change Applier の ApplyChanges メソッドに渡されます)。さらに、Change Applier は、ISynchronousDataRetriever::LoadChangeData を呼び出して、項目データを表す IUnknown インターフェイスを取得します。Change Applier は、このインターフェイスを同期先プロバイダーの SaveChange メソッドに渡します。同期先プロバイダーは、この IUnknown インターフェイスを使用して、新しい項目や変更された項目の項目データを取得し、それを同期先レプリカに適用します。

ISynchronousDataRetriever の宣言

クラスの継承一覧に ISynchronousDataRetriever を追加します。

class CItemStore : public ISynchronousDataRetriever

クラスの宣言に ISynchronousDataRetriever メソッドを追加します。

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

STDMETHOD(LoadChangeData)(
    ILoadChangeContext * pLoadChangeContext,
    IUnknown ** ppUnkData);

GetIdParameters メソッド

Sync Framework は、ISynchronousDataRetriever::GetIdParameters を呼び出して、プロバイダーの ID 形式スキーマを取得します。この実装は、基本的に ISyncProvider::GetIdParameters のものと同じです。

STDMETHODIMP CItemStore::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

LoadChangeData メソッド

変更の適用時、Sync Framework は、ISynchronousDataRetriever::LoadChangeData を呼び出して、IUnknown インターフェイスを取得します。同期先プロバイダーは、このインターフェイスを使用することによって項目データを取得できます。この実装では、項目ストアから項目を検索して複製し、その IUnknown インターフェイスを返します。

STDMETHODIMP CItemStore::LoadChangeData(
    ILoadChangeContext * pLoadChangeContext,
    IUnknown ** ppUnkData)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pLoadChangeContext || NULL == ppUnkData)
    {
        hr = E_POINTER;    
    }
    else
    {
        // Find the item in the data store, clone it, and return its IUnknown interface.
        ISyncChange* pChange = NULL;
        hr = pLoadChangeContext->GetSyncChange(&pChange);
        if (SUCCEEDED(hr))
        {
            SYNC_GID gidItem;
            DWORD cbID = sizeof(gidItem);
            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbID);
            if (SUCCEEDED(hr))
            {
                IXMLDOMNode* pNodeItem = NULL;
                hr = FindItem(&gidItem, &pNodeItem);
                if (SUCCEEDED(hr))
                {
                    IXMLDOMNode* pNodeClone = NULL;
                    hr = pNodeItem->cloneNode(TRUE, &pNodeClone);
                    if (SUCCEEDED(hr))
                    {
                        hr = pNodeClone->QueryInterface(IID_IUnknown, (void**)ppUnkData);

                        pNodeClone->Release();
                    }

                    pNodeItem->Release();                
                }
            }

            pChange->Release();
        }
    }

    return hr;
}

次の手順

同期プロバイダーの作成はこれで終了です。同期セッションをホストするアプリケーションを作成して、プロバイダーに接続することもできます。具体的な方法については、「アンマネージ同期アプリケーションを作成する方法」を参照してください。

また、次の手順として、変更単位を処理するようにプロバイダーを強化することも可能です。変更単位の詳細については、「変更単位の同期」を参照してください。

さらに、カスタム メタデータ ストアを作成することもできます。同期メタデータを処理する方法の詳細については、「標準プロバイダーのメタデータの管理」を参照してください。

参照

リファレンス

ISyncProvider インターフェイス
IKnowledgeSyncProvider インターフェイス
ISynchronousNotifyingChangeApplierTarget インターフェイス
ISynchronousDataRetriever インターフェイス
ID_PARAMETERS 構造体
ISynchronousNotifyingChangeApplier インターフェイス

概念

標準のカスタム プロバイダーの実装
Sync Framework のコア コンポーネント