HOW TO:建立 Unmanaged 同步處理提供者
本主題描述如何使用 Unmanaged 語言 (如 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 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 方法
Sync Framework 會在建立 ISyncSession 物件時,在來源和目的地提供者上呼叫 ISyncProvider::GetIdParameters。這個方法會傳回提供者所使用的識別碼格式結構描述。此結構描述在兩個提供者上必須相同。此範例中的實作使用全域常數,因為識別碼格式是提供者的常數。
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。這個方法會通知提供者,它正在聯結 (Join) 同步處理工作階段,並傳遞包含工作階段狀態資訊的物件給提供者。此實作會儲存工作階段狀態物件。
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
方法,從來源提供者取得項目的本機版本資訊。然後建立由 Sync Framework 實作的 ISynchronousNotifyingChangeApplier 物件,並呼叫其 ISynchronousNotifyingChangeApplier::ApplyChanges 方法。
STDMETHODIMP CXMLProvider::ProcessChangeBatch(
CONFLICT_RESOLUTION_POLICY resolutionPolicy,
ISyncChangeBatch * pSourceChangeBatch,
IUnknown * pUnkDataRetriever,
ISyncCallback * pCallback,
SYNC_SESSION_STATISTICS * pSyncSessionStatistics)
{
HRESULT hr = E_UNEXPECTED;
if (NULL == pSourceChangeBatch || NULL == pUnkDataRetriever || NULL == pSyncSessionStatistics)
{
hr = E_POINTER;
}
else
{
IEnumSyncChanges* pDestinationChangeEnum = NULL;
_ASSERT(NULL != m_pMetadataMgr);
// Obtain the local (destination) versions for the items in the source change batch.
hr = m_pMetadataMgr->GetItemBatchVersions(pSourceChangeBatch, &pDestinationChangeEnum);
if (SUCCEEDED(hr))
{
IProviderSyncServices* pProviderSvc = NULL;
hr = GetProviderSyncServices(&c_idParams, &pProviderSvc);
if (SUCCEEDED(hr))
{
// Create a standard change applier from Sync Framework.
ISynchronousNotifyingChangeApplier* pChangeApplier = NULL;
hr = pProviderSvc->CreateChangeApplier(IID_ISynchronousNotifyingChangeApplier,
(void**)&pChangeApplier);
if (SUCCEEDED(hr))
{
ISyncKnowledge* pDestinationKnowledge = NULL;
hr = m_pMetadataMgr->GetKnowledge(&pDestinationKnowledge);
if (SUCCEEDED(hr))
{
// Have the change applier process the change batch and apply changes.
// This method will call the change applier target methods to save
// changes and conflicts. It will also pass the data retriever
// interface to the change applier target so it can retrieve item data.
hr = pChangeApplier->ApplyChanges(resolutionPolicy, pSourceChangeBatch,
pUnkDataRetriever, pDestinationChangeEnum, pDestinationKnowledge,
NULL, this, m_pSessionState, pCallback);
pDestinationKnowledge->Release();
}
pChangeApplier->Release();
}
pProviderSvc->Release();
}
pDestinationChangeEnum->Release();
}
}
return hr;
}
中繼資料存放區的 GetItemBatchVersions
方法會列舉在變更批次中從來源提供者傳送的變更。如果項目是在目的地中繼資料內,其版本資訊會加入至特別為保存版本資訊而建立的新批次中。如果目的地中繼資料內沒有這個項目存在,就會在版本批次中加上旗標做為新項目。然後方法再傳回版本批次。
STDMETHODIMP CMetadataMgr::GetItemBatchVersions(
ISyncChangeBatch * pRemoteSyncChangeBatch,
IEnumSyncChanges ** ppLocalVersionsEnum)
{
HRESULT hr = E_UNEXPECTED;
if (NULL == pRemoteSyncChangeBatch || NULL == ppLocalVersionsEnum)
{
hr = E_POINTER;
}
else
{
IProviderSyncServices* pProvSvc;
hr = GetProviderSyncServices(&c_idParams, &pProvSvc);
if (SUCCEEDED(hr))
{
IDestinationChangeVersionsBuilder* pDestChangeBuilder = NULL;
hr = pProvSvc->CreateDestinationChangeVersionsBuilder(&pDestChangeBuilder);
if (SUCCEEDED(hr))
{
IEnumSyncChanges* pRemoteEnum = NULL;
hr = pRemoteSyncChangeBatch->GetChangeEnumerator(&pRemoteEnum);
if (SUCCEEDED(hr))
{
ULONG cFetched;
ISyncChange* pChange;
SYNC_GID gidItem;
DWORD cbID = sizeof(gidItem);
DWORD dwFlags;
SYNC_VERSION verCurrent;
SYNC_VERSION verCreation;
HRESULT hrEnum = S_OK;
while (S_OK == hrEnum && SUCCEEDED(hr))
{
pChange = NULL;
hrEnum = pRemoteEnum->Next(1, &pChange, &cFetched);
if (S_OK == hrEnum)
{
hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbID);
if (SUCCEEDED(hr))
{
// Try to find the item in the local (destination) metadata.
IItemMetadata* pItem = NULL;
hr = FindItemMetadataByGlobalId((BYTE*)&gidItem, &pItem);
if (S_OK == hr)
{
// S_OK means the item exists in our local store.
// Extract its version and tombstone information.
dwFlags = 0;
BOOL fTombstone = FALSE;
hr = pItem->GetIsDeleted(&fTombstone);
if (SUCCEEDED(hr))
{
if (fTombstone)
{
dwFlags = SYNC_CHANGE_FLAG_DELETED;
}
}
if (SUCCEEDED(hr))
{
hr = pItem->GetChangeVersion(&verCurrent);
if (SUCCEEDED(hr))
{
hr = pItem->GetCreationVersion(&verCreation);
}
}
pItem->Release();
}
else if (S_FALSE == hr)
{
// S_FALSE means this item does not exist in our local store.
// Set versions to 0 and flag it as a new item.
verCurrent.dwLastUpdatingReplicaKey = 0;
verCurrent.ullTickCount = 0;
verCreation.dwLastUpdatingReplicaKey = 0;
verCreation.ullTickCount = 0;
dwFlags = SYNC_CHANGE_FLAG_DOES_NOT_EXIST;
}
if (SUCCEEDED(hr))
{
// Add the item to the batch of destination versions.
GUID guidReplicaID = GUID_NULL;
ULONG cbID = sizeof(guidReplicaID);
hr = GetReplicaId((BYTE*)&guidReplicaID, &cbID);
if (SUCCEEDED(hr))
{
hr = pDestChangeBuilder->AddItemMetadata((BYTE*)&guidReplicaID,
(BYTE*)&gidItem, &verCurrent, &verCreation, dwFlags, NULL);
}
}
}
pChange->Release();
}
}
if (FAILED(hrEnum))
{
hr = hrEnum;
}
pRemoteEnum->Release();
}
if (SUCCEEDED(hr))
{
hr = pDestChangeBuilder->GetChangeEnumerator(ppLocalVersionsEnum);
}
pDestChangeBuilder->Release();
}
pProvSvc->Release();
}
}
return hr;
}
EndSession 方法
來源提供者傳送了最後一個批次,而且目的地提供者也套用了變更至其資料存放區之後,Sync Framework 會在來源及目的地提供者上呼叫 IKnowledgeSyncProvider::EndSession。這個方法會通知提供者,它正要離開同步處理工作階段,而且會釋出任何與該工作階段相關聯的資源。此實作會釋出存放於 BeginSession
呼叫中的工作階段狀態物件。
STDMETHODIMP CXMLProvider::EndSession(
ISyncSessionState * pSessionState)
{
HRESULT hr = E_UNEXPECTED;
if (NULL == m_pSessionState)
{
hr = SYNC_E_INVALID_OPERATION;
}
else
{
m_pSessionState->Release();
m_pSessionState = NULL;
hr = S_OK;
}
return hr;
}
未實作的方法
下列方法並不需要,因為此範例從不移除在中繼資料存放區中標示為刪除的項目。這些方法可能會傳回 E_NOTIMPL:
實作 ISynchronousNotifyingChangeApplierTarget
目的地提供者呼叫 ISynchronousNotifyingChangeApplier::ApplyChanges 方法時,此介面會提供給 Sync Framework,一般來說是在 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:
ISynchronousNotifyingChangeApplierTarget::GetDestinationVersion
ISynchronousNotifyingChangeApplierTarget::SaveChangeWithChangeUnits
實作 ISynchronousDataRetriever
ISynchronousDataRetriever 是由來源提供者傳回 Sync Framework,以回應 GetChangeBatch 呼叫。ISynchronousDataRetriever 是在 ProcessChangeBatch 呼叫中傳送至目的地提供者,一般是傳遞至變更套用者的 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,以擷取提供者的識別碼格式結構描述。基本上,此實作是跟 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;
}
後續步驟
現在,您已經建立了同步處理提供者,可能要建立可裝載同步處理工作階段的應用程式,並連接至提供者。如需有關如何執行這項操作的詳細資訊,請參閱 HOW TO:建立 Unmanaged 同步處理應用程式。
您可以採取的其他後續步驟是,加強提供者功能以處理變更單位。如需變更單位的詳細資訊,請參閱同步處理變更單位。
您可能也要建立自訂中繼資料存放區。如需如何處置同步處理中繼資料的詳細資訊,請參閱管理標準提供者的中繼資料。
請參閱
參考
ISyncProvider 介面
IKnowledgeSyncProvider 介面
ISynchronousNotifyingChangeApplierTarget 介面
ISynchronousDataRetriever 介面
ID_PARAMETERS 結構
ISynchronousNotifyingChangeApplier 介面