Поделиться через


Как создать неуправляемую службу синхронизации

В этом разделе показано использование неуправляемого языка, например C++, для создания службы синхронизации платформыSync Framework, синхронизирующего данные из пользовательского хранилища данных.

В этом разделе предполагается, что читатель знаком с основными понятиями языка C++ и модели COM.

В примерах, приведенных в этом разделе, обсуждаются следующие интерфейсы Sync Framework:

Основные сведения о службах синхронизации

Служба синхронизации — это программный компонент, представляющий реплику во время синхронизации. Это позволяет реплике синхронизировать свои данные с другими репликами. Для выполнения синхронизации приложение вначале создает объект сеанса синхронизации, подключает его к двум объектам ISyncProvider и начинает сеанс. Один из поставщиков представляет реплику источника. Реплика источника поставляет метаданные измененных элементов с помощью своего метода IKnowledgeSyncProvider::GetChangeBatch и данные элементов с помощью объекта ISynchronousDataRetriever. Другой поставщик представляет реплику назначения. Реплика назначения получает метаданные измененных элементов через метод 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. В этом примере также используется пользовательское хранилище метаданных, которое реализуется с помощью API-интерфейса службы хранилища метаданных. Сведения о службе хранилища метаданных и других компонентах Sync Framework см. на Служба хранилища метаданных платформы Sync Framework.

Хранилище метаданных и 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

Sync Framework вызывает метод ISyncProvider::GetIdParameters как в поставщике источника, так и в поставщике назначения, если создается объект ISyncSession. Этот метод возвращает схему форматов идентификаторов, используемую поставщиком. Схема должна быть одинаковой для обоих поставщиков. Реализация в этом примере использует глобальную константу, поскольку форматы идентификаторов являются константами для поставщика.

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 в поставщике источника. Этот метод получает пакет изменений, который нужно отправить поставщику назначения, и возвращает также интерфейс получения данных. Поставщик назначения использует этот интерфейс для получения данных элемента для изменений, примененных в реплике назначения. Платформа Sync Framework несколько раз вызывает метод GetChangeBatch, пока все пакеты не будут отосланы. Чтобы указать, что пакет был последним, поставщик источника вызывает метод ISyncChangeBatchBase::SetLastBatch. Эта реализация делегирует задачу перечисления изменений методу GetChangeBatch хранилища метаданных. Объект хранения XML-элемента реализует интерфейс получения данных, поэтому возвращается его интерфейс 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 из поставщика источника. Эта реализация использует метод GetItemBatchVersions хранилища метаданных для получения локальной версии сведений от поставщика источника. Затем она создает объект ISynchronousNotifyingChangeApplier, реализованный Sync Framework, и вызывает метод 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:

Реализация метода Implementing ISynchronousNotifyingChangeApplierTarget

Этот интерфейс предоставляется Sync Framework, когда поставщик назначения вызывает метод ISynchronousNotifyingChangeApplier::ApplyChanges, обычно в методе ProcessChangeBatch. 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 для получения схемы форматов идентификаторов поставщика. В этом примере для реализации методов IKnowledgeSyncProvider и ISynchronousNotifyingChangeApplierTarget используется один и тот же класс. Поэтому реализация такая же, как для метода 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

Метод ISynchronousDataRetriever возвращается службам Sync Framework поставщиком источника в ответ на вызов метода GetChangeBatch. Метод ISynchronousDataRetriever отправляется поставщику назначения в вызове ProcessChangeBatch, где он обычно передается методу применения изменений ApplyChanges. После этого объект применения изменений вызывает метод ISynchronousDataRetriever::LoadChangeData, чтобы получить интерфейс IUnknown, представляющий данные элемента. Объект применения изменений передает этот интерфейс методу SaveChange поставщика назначения. Поставщик назначения использует этот интерфейс IUnknown для получения данных элементов для новых или изменившихся элементов и применяет эти данные элементов в реплике назначения.

Объявление метода Declaring ISynchronousDataRetriever

Добавьте ISynchronousDataRetriever к списку наследования классов.

class CItemStore : public ISynchronousDataRetriever

Добавьте методы ISynchronousDataRetriever в декларацию класса.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

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

Метод GetIdParameters

Sync Framework вызывает метод ISynchronousDataRetriever::GetIdParameters для получения схемы форматов идентификаторов поставщика. Поэтому реализация в основном такая же, как для метода 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