다음을 통해 공유


방법: 관리되지 않는 동기화 공급자 만들기

이 항목에서는 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 인터페이스로 나타냅니다. 또한 이 예제에서는 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를 호출하면 동기화 세션이 본격적으로 시작됩니다. 이 메서드는 대상 공급자에 전송할 일괄 변경 내용을 검색하고 데이터 검색자 인터페이스를 반환합니다. 대상 공급자는 이 인터페이스를 사용하여 대상 복제본에 적용된 변경 내용의 항목 데이터를 검색합니다. 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 메서드를 호출하여 원본 공급자에서 일괄 변경 내용을 가져온 후 대상 공급자에 대해 IKnowledgeSyncProvider::ProcessChangeBatch를 호출합니다. 이 메서드는 대상 복제본에 변경 내용을 적용합니다. 이 메서드는 GetChangeBatch를 사용하여 원본 공급자에서 검색한 일괄 변경 내용마다 한 번씩 호출됩니다. 이 구현에서는 메타데이터 저장소의 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 구현

이 인터페이스는 대상 공급자가 일반적으로 ProcessChangeBatch 메서드에서 ISynchronousNotifyingChangeApplier::ApplyChanges 메서드를 호출할 때 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 구현

원본 공급자는 GetChangeBatch 호출에 대한 응답으로 ISynchronousDataRetriever를 Sync Framework에 반환합니다. ISynchronousDataRetrieverProcessChangeBatch 호출 시 대상 공급자에 전송되며 이 호출에서 대개 변경 내용 적용자의 ApplyChanges 메서드에 전달됩니다. 그런 다음 변경 내용 적용자는 ISynchronousDataRetriever::LoadChangeData를 호출하여 항목 데이터를 나타내는 IUnknown 인터페이스를 가져옵니다. 변경 내용 적용자는 이 인터페이스를 대상 공급자의 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 핵심 구성 요소