Cómo crear un proveedor de sincronización no administrado

En este tema se muestra cómo utilizar un lenguaje no administrado, como C++, para crear un proveedor de sincronización de Microsoft Sync Framework que sincronice los datos de un almacén de datos personalizado.

En este tema se suponen conocimientos básicos de los conceptos de C++ y COM.

Los ejemplos de este tema se centran en las siguientes interfaces de Sync Framework:

Introducción a los proveedores de sincronización

Un proveedor de sincronización es un componente de software que representa una réplica durante la sincronización. De esta forma, la réplica puede sincronizar sus datos con otras réplicas. Para que tenga lugar una sincronización, una aplicación primero crea un objeto de sesión de sincronización, lo conecta a dos objetos ISyncProvider e inicia la sesión. Uno de los proveedores representa la réplica de origen. La réplica de origen proporciona los metadatos para los elementos cambiados a través de su método IKnowledgeSyncProvider::GetChangeBatch y los datos de elemento a través de un objeto ISynchronousDataRetriever. El otro proveedor representa la réplica de destino. La réplica de destino recibe los metadatos de los elementos cambiados a través de su método IKnowledgeSyncProvider::ProcessChangeBatch y aplica los cambios en su almacén de elementos utilizando un objeto ISynchronousChangeApplier suministrado por Sync Framework junto con su propio objeto ISynchronousChangeApplierTarget.

Para obtener más información acerca del rol del proveedor de sincronización, vea Proveedores de sincronización.

Requisitos de la generación

  • Synchronization.h: declaraciones de los componentes de Sync Framework.

    #include <synchronization.h>
    
  • Synchronizationerrors.h: códigos de error personalizados.

    #include <synchronizationerrors.h>
    
  • Synchronization.lib: biblioteca de importación.

Ejemplo

El código de ejemplo de este tema muestra cómo implementar los métodos de interfaz básicos que se requieren para que una réplica participe en una comunidad de sincronización de Sync Framework, como origen y como destino. La réplica de este ejemplo es un archivo XML y los elementos que sincronizar son nodos XML que están contenidos en este archivo. En el código, la interfaz IXMLDOMNode representa los nodos XML. Este ejemplo también utiliza un almacén de metadatos personalizado que se implementa utilizando la API de Metadata Storage Service. Para obtener información acerca de Metadata Storage Service y de otros componentes de Sync Framework, vea el sitio web de Microsoft.

El almacén de metadatos y el almacén XML se declaran ambos como miembros de la clase de proveedor.

CMetadataMgr* m_pMetadataMgr;
CItemStore* m_pItemStore;

Implementar ISyncProvider e IKnowledgeSyncProvider

El punto de entrada principal al proveedor es la interfaz ISyncProvider. Esta interfaz está pensada para actuar como clase base para otras interfaces de proveedor más eficaces. Este ejemplo utiliza la interfaz IKnowledgeSyncProvider.

Declarar la interfaz IKnowledgeSyncProvider

Agregue IKnowledgeSyncProvider a la lista de herencias de clases.

class CXMLProvider : public IKnowledgeSyncProvider

Agregue los métodos ISyncProvider a la declaración de la clase.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

Agregue los métodos IKnowledgeSyncProvider a la declaración de la clase.

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);

Método GetIdParameters

Sync Framework llama a ISyncProvider::GetIdParameters tanto en los proveedores de destino como de origen cuando se crea el objeto ISyncSession. Este método devuelve el esquema de formatos de identificadores que el proveedor utiliza. Este esquema debe ser el mismo para ambos proveedores. La implementación de este ejemplo utiliza una constante global porque los formatos de identificadores son constantes para el proveedor.

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

El uso de una constante global facilita mucho la implementación de este método.

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

Método BeginSession

A continuación, Sync Framework llama al método IKnowledgeSyncProvider::BeginSession en los proveedores de origen y de destino. Este método informa a un proveedor de que se está uniendo a una sesión de sincronización y le pasa un objeto que contiene información del estado de la sesión. Esta implementación almacena el objeto del estado de la sesión.

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;
}

Método GetSyncBatchParameters

A continuación, Sync Framework llama al método IKnowledgeSyncProvider::GetSyncBatchParameters en el proveedor de destino. Este método recupera el número de cambios que el proveedor de origen debería incluir en un lote de cambios y obtiene el conocimiento actual del proveedor de destino. La implementación extrae el conocimiento del almacén de metadatos y establece el tamaño del lote en 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;
}

Método GetChangeBatch

La sesión de sincronización se inicia realmente cuando Sync Framework llama al método IKnowledgeSyncProvider::GetChangeBatch en el proveedor de origen. Este método recupera un lote de cambios para enviar al proveedor de destino y también devuelve la interfaz del recuperador de datos. El proveedor de destino utiliza esta interfaz para recuperar los cambios de los datos de elemento que se aplicaron a la réplica de destino. Sync Framework llama varias veces a GetChangeBatch hasta que se envía el último lote. El proveedor de origen indica que un lote es el último llamando al método ISyncChangeBatchBase::SetLastBatch. Esta implementación delega la tarea de enumeración de los cambios al método GetChangeBatch del almacén de metadatos. El objeto de almacén de elemento XML implementa una interfaz de recuperador de datos; por consiguiente, se devuelve su interfaz 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;
}

El método GetChangeBatch implementado por el almacén de metadatos enumera los elementos del almacén de metadatos y comprueba la versión de cada uno con respecto a los conocimientos del destino. Si la réplica de destino no sabe nada sobre un cambio, el cambio se agrega al lote de cambios que se devuelve.

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;
}

Método ProcessChangeBatch

Una vez que Sync Framework obtenga un lote de cambios del proveedor de origen llamando a su método GetChangeBatch, Sync Framework llamará al método IKnowledgeSyncProvider::ProcessChangeBatch en el proveedor de destino. Este método aplica los cambios a la réplica de destino. Este método se llama una vez por cada lote que se recupera utilizando GetChangeBatch desde el proveedor de origen. Esta implementación utiliza el método GetItemBatchVersions del almacén de metadatos para obtener la información de versión local para los elementos del proveedor de origen. A continuación, crea un objeto ISynchronousNotifyingChangeApplier implementado por Sync Framework y llama a su método 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;
}

El método GetItemBatchVersions del almacén de metadatos enumera los cambios enviados en el lote de cambios desde el proveedor de origen. Si un elemento está en los metadatos de destino, su información de versión se agrega a un nuevo lote creado expresamente para contener la información de versión. Si un elemento no existe en los metadatos de destino, se marca como elemento nuevo en el lote de la versión. A continuación, el método devuelve el lote de la versión.

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;
}

Método EndSession

Una vez que el proveedor de origen envía su último lote y el proveedor de destino ha aplicado los cambios a su almacén de datos, Sync Framework llama a IKnowledgeSyncProvider::EndSession tanto en el proveedor de origen como en el de destino. Este método informa a un proveedor de que está dejando una sesión de sincronización y que debería liberar cualquier recurso que esté asociado a la misma. Esta implementación libera el objeto de estado de sesión que almacenó en la llamada a 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;
}

Métodos que no se implementan

No se requieren los métodos siguientes porque este ejemplo nunca quita los elementos marcados como eliminados en el almacén de metadatos. Estos métodos pueden devolver E_NOTIMPL:

Implementar ISynchronousNotifyingChangeApplierTarget

Esta interfaz se proporciona a Sync Framework cuando el proveedor de destino llama al método ISynchronousNotifyingChangeApplier::ApplyChanges, normalmente en el método ProcessChangeBatch. ISynchronousNotifyingChangeApplierTarget contiene los métodos a los que se llama durante la aplicación de cambios. Sólo se llama a estos métodos en el proveedor de destino.

Declarar ISynchronousNotifyingChangeApplierTarget

Agregue ISynchronousNotifyingChangeApplierTarget a la lista de herencias de clases.

class CXMLProvider : public IKnowledgeSyncProvider
    , ISynchronousNotifyingChangeApplierTarget

Agregue los métodos ISynchronousNotifyingChangeApplierTarget a la declaración de la clase.

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);

Método GetIdParameters

Sync Framework llama al método ISynchronousNotifyingChangeApplierTarget::GetIdParameters para recuperar el esquema de formato de identificador del proveedor. En este ejemplo se utiliza la misma clase para implementar IKnowledgeSyncProvider e ISynchronousNotifyingChangeApplierTarget. Por consiguiente, esta implementación es igual que para ISyncProvider::GetIdParameters.

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

GetCurrentTickCount

Sync Framework llama a ISynchronousNotifyingChangeApplierTarget::GetCurrentTickCount para incrementar y recuperar el contador para la réplica. Esta implementación llama al método GetNextTickCount del almacén de metadatos.

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

El método GetNextTickCount del almacén de metadatos incrementa el contador de la réplica y lo devuelve.

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

Durante la aplicación de los cambios, Sync Framework llama a ISynchronousNotifyingChangeApplierTarget::SaveChange con cada cambio que se va a aplicar a la réplica de destino. Esta implementación administra apropiadamente los elementos nuevos, los elementos cambiados y los elementos eliminados; y actualiza tanto los datos de elemento del almacén como los metadatos del almacén de metadatos.

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

Después de procesar cada lote de cambios, Sync Framework llama a ISynchronousNotifyingChangeApplierTarget::SaveKnowledge para que el proveedor de destino pueda guardar el conocimiento que contiene los cambios nuevos. Esta implementación guarda el objeto de conocimiento en el almacén de metadatos y sobrescribe el conocimiento existente previamente.

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;
}

Métodos que no se implementan

Los métodos siguientes no se requieren en escenarios de sincronización básicos y pueden devolver sólo E_NOTIMPL:

Implementar ISynchronousDataRetriever

El proveedor de origen devuelve ISynchronousDataRetriever a Sync Framework en respuesta a la llamada al método GetChangeBatch. ISynchronousDataRetriever se envía al proveedor de destino en la llamada a ProcessChangeBatch , donde se suele pasar en el método ApplyChanges de un aplicador de cambios. A continuación, el aplicador de cambios llama al método ISynchronousDataRetriever::LoadChangeData para obtener una interfaz IUnknown que representa los datos de elemento. El aplicador de cambios pasa esta interfaz al método SaveChange del proveedor de destino. El proveedor de destino utiliza esta interfaz IUnknown para recuperar los datos de elemento para los elementos nuevos o cambiados, y aplica los datos de elemento a la réplica de destino.

Declarar ISynchronousDataRetriever

Agregue ISynchronousDataRetriever a la lista de herencias de clases.

class CItemStore : public ISynchronousDataRetriever

Agregue los métodos ISynchronousDataRetriever a la declaración de la clase.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

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

Método GetIdParameters

Sync Framework llama al método ISynchronousDataRetriever::GetIdParameters para recuperar el esquema de formato de identificador del proveedor. Esta implementación es básicamente la misma que para ISyncProvider::GetIdParameters.

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

Método LoadChangeData

Durante la aplicación de los cambios, Sync Framework llama al método ISynchronousDataRetriever::LoadChangeData para obtener una interfaz IUnknown que el proveedor de destino puede utilizar para recuperar los datos de elemento. Esta implementación busca el elemento en el almacén de elementos, lo clona y devuelve su interfaz 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;
}

Pasos siguientes

Ahora que ha creado un proveedor de sincronización, podría desear crear una aplicación que pueda hospedar la sesión de sincronización y conectarla al proveedor. Para obtener información sobre cómo hacer esto, vea Cómo crear una aplicación de sincronización no administrada.

Otro paso posterior que podría dar es mejorar el proveedor para administrar las unidades de cambio. Para obtener más información acerca de las unidades de cambio, vea Sincronizar las unidades de cambio.

También podría desear crear un almacén de metadatos personalizado. Para obtener más información sobre cómo controlar los metadatos de sincronización, vea Administración de los metadatos.

Vea también

Referencia

Interfaz ISyncProvider
Interfaz IKnowledgeSyncProvider
Interfaz ISynchronousNotifyingChangeApplierTarget
Interfaz ISynchronousDataRetriever
Estructura ID_PARAMETERS
Interfaz ISynchronousNotifyingChangeApplier

Conceptos

Proveedores de sincronización
Componentes principales de Sync Framework