http://www.developer.com/

Back to article

Developing ActiveSync Service Providers: Desktop Part Implementation


October 10, 2005

What You Already Know

Here, you have reached a point where all your last coding will result in some viewable outcome. In the previous article, you covered the device part of ActiveSync Service Provider implementation. Now, you are ready to conquer its desktop components.

Desktop stuff requires much more effort and coding to get it working. As usual, you have to implement fully functional COM components to interface with MS ActiveSync. Partially, you saw it in previous article, but here you will add some new stuff. The following sections will guide you through all required stages.

Due to the fact that your ASP DLL will be loaded by ActiveSync, debugging may be a bit painful because you will need to restart ActiveSync every time you have new version of the DLL.

MS ActiveSync Data Storage

As noted earlier, MS ActiveSync has no idea about what your data is. It uses a generic approach to access and manipulate such data via HREPLFLDR and HREPLITEM pointers. ActiveSync places all such data to one single file named repl.dat for each device.

Usually, you will have many items and few folders, so you have to keep in mind not to break some rational limits of the information you define for each item or folder. Here is a typical implementation:

class CBaseFolder;
//
// ================== class CReplObject =========================
//
class CReplObject
{
public:
   enum IDD_OBJ_TYPE{ ASPSIMPLE_OBJ_FOLDER = 1,
                      ASPSIMPLE_OBJ_ITEM = 2 };
protected:
   IDD_OBJ_TYPE m_eType;    // type of this object folder/item
public:
   CReplObject(IDD_OBJ_TYPE eType) { m_eType = eType;}
   IDD_OBJ_TYPE GetType(){ return m_eType;}
   virtual ~CReplObject(){};
};
//
// ================== class CReplFolder ==========================
//
class CReplFolder: public CReplObject
{
protected:
   TCHAR m_szName[_MAX_PATH];
   CBaseFolder *m_pFolder;
public:
   CReplFolder(LPTSTR pszName, CBaseFolder *pFolder) :
      CReplObject(ASPSIMPLE_OBJ_FOLDER)
   {
      ::_tcscpy(m_szName, pszName);
      m_pFolder = pFolder;    // stores ptr to real folder
      return;
   }
   virtual ~CReplFolder(){}
   const LPTSTR GetName(){ return m_szName;}
   CBaseFolder* GetFolder(){ return m_pFolder;}
   CReplFolder& operator=(const CReplFolder& rhs)    // overloaded
                                                     // operator=
   {
      //
      // TODO: If you add/modify the members of this class,
      // you will also need to modify the code below.
      //
      ::_tcscpy(m_szName, rhs.m_szName);
      m_pFolder = rhs.m_pFolder;
      return *this;
   }
};
//
// ================== class CReplItem ===========================
//
class CReplItem: public CReplObject
{
protected:
   TCHAR       m_szId[_MAX_PATH];    // unique id to identify
                                     // this object
   FILETIME    m_ftModified;         // stores the last modified
                                     // time of this item
public:
   CReplItem(LPTSTR pszId) : CReplObject(ASPSIMPLE_OBJ_ITEM)
   {
      ::_tcscpy(m_szId, pszId);
      return;
   }
   virtual ~CReplItem(){}
   FILETIME& GetModified(){ return m_ftModified;}
   void SetModified(FILETIME& ft){ m_ftModified = ft;}
   CReplItem& operator=(const CReplItem&rhs)    // overloaded
                                                // operator=
   {
      //
      // TODO: If you add/modify the members of this class,
      // you will also need to modify the code below.
      //
      ::_tcscpy(m_szId, rhs.m_szId);
      m_ftModified = rhs.m_ftModified;
      return *this;
   }
   //
   // Compare function compares this object with another one
   //of the same type. Returns -1, 0 or 1 accordingly.
   //
   int Compare(const CReplItem* prhs)
   {
      return ::_tcscmp(m_szId, prhs->m_szId);
   }
   const LPTSTR GetId()
   {
      return m_szId;
   }
};

Folders are usually characterized by their names, whereas Items have a modification time stamp.

IReplStore Implementation

The IReplStore interface allows ActiveSync to obtain relevant info and manipulate all synchronized entities such as stores, folders, and items. Let me place your sample implementation's declaration here, categorized by functionality:

class CBaseFolder;
class CASPSimpleStore: public IReplStore
{
protected:
   long m_cRef;
   BOOL m_fInitialized;
   IReplNotify *m_pNotify;
   CBaseFolder *m_pFolders[ASPSIMPLE_NUM_FOLDERS];
private:
   static const LPTSTR m_pszCLSID;
   static const LPTSTR m_pszProgId;
   static const LPTSTR m_pszDesc;
public:
   static const LPTSTR GetCLSID(){ return m_pszCLSID;}
   static const LPTSTR GetProgId(){ return m_pszProgId;}
   static const LPTSTR GetDesc(){ return m_pszDesc;}
private:
   ~CASPSimpleStore();
public:
   CASPSimpleStore();
   CBaseFolder* GetFolderByName(LPTSTR szName);
   //========= IUnknown methods ==========//
   STDMETHODIMP         QueryInterface(const IID& iid,
                                       void **ppvObject);
   STDMETHODIMP_(ULONG) AddRef(void);
   STDMETHODIMP_(ULONG) Release(void);
   //========= IReplStore methods ==========//
   STDMETHODIMP        Initialize(IReplNotify *, UINT uFlags);
   STDMETHODIMP        GetStoreInfo(PSTOREINFO pStoreInfo);
   STDMETHODIMP        ReportStatus(HREPLFLD hFolder,
                                    HREPLITEM hItem,
                                    UINT uStatus,
                                    UINT uReserved);
   STDMETHODIMP_(int)  CompareStoreIDs(LPBYTE lpbID1, UINT cbID1,
                                       LPBYTE lpbID2, UINT cbID2);
   // ============ Object related routines ===========//
   STDMETHODIMP_(int)  CompareItem(HREPLITEM hItem1,
                                   HREPLITEM hItem2);
   STDMETHODIMP_(BOOL) IsItemChanged(HREPLFLD hFolder,
                                     HREPLITEM hItem,
                                     HREPLITEM hItemComp);
   STDMETHODIMP_(BOOL) IsItemReplicated(HREPLFLD hFolder,
                                        HREPLITEM hItem);
   STDMETHODIMP_(void) UpdateItem(HREPLFLD hFolder,
                                  HREPLITEM hItemDst,
                                  HREPLITEM hItemSrc);
   // ============ Folder related routines ===========//
   STDMETHODIMP GetFolderInfo(LPSTR lpszName, HREPLFLD *phFolder,
                              IUnknown **ppObjHandler);
   STDMETHODIMP IsFolderChanged(HREPLFLD hFolder, BOOL *pfChanged);
   // ============ Enumeration of folder objects =======//
   STDMETHODIMP FindFirstItem(HREPLFLD hFolder, HREPLITEM *phItem,
                              BOOL *pfExist);
   STDMETHODIMP FindNextItem(HREPLFLD hFolder, HREPLITEM *phItem,
                             BOOL *pfExist);
   STDMETHODIMP FindItemClose(HREPLFLD hFolder);
   // ============ Standard management routines ===========//
   STDMETHODIMP_(UINT)     ObjectToBytes(HREPLOBJ hObject,
                                         LPBYTE lpb);
   STDMETHODIMP_(HREPLOBJ) BytesToObject(LPBYTE lpb, UINT cb);
   STDMETHODIMP_(void)     FreeObject(HREPLOBJ hObject);
   STDMETHODIMP_(BOOL)     CopyObject(HREPLOBJ hObjSrc,
                                      HREPLOBJ hObjDst);
   STDMETHODIMP            IsValidObject(HREPLFLD hFolder,
                                         HREPLITEM hObject,
                                         UINT uFlags);
   // ============ UI related routines ================//
   STDMETHODIMP ActivateDialog(UINT uDlg, HWND hWnd,
                               HREPLFLD hFolder,
                               IEnumReplItem *penum);
   STDMETHODIMP GetObjTypeUIData(HREPLFLD hFolder,
                                 POBJUIDATA pData);
   STDMETHODIMP GetConflictInfo(PCONFINFO pConfInfo);
   STDMETHODIMP RemoveDuplicates(LPSTR lpszObjType, UINT uFlags);
};

Let's discuss some of the above categories (I'll skip the IUnknown part). You will find more details in the accompanying sample project.

IReplStore methods

All functions from this group work at the Store level. You will be focused on one of them, GetStoreInfo, as a main part of this functional group. All the rest is simply to get and implement; see, for example, the Initialize code below:

STDMETHODIMP CASPSimpleStore::Initialize(IReplNotify *pNotify,
                                         UINT uFlags)
{
   if(pNotify == NULL)
      return E_INVALIDARG;
   //
   // TODO: ActiveSync manager will call this function to let you
   // initialize your store. Add any store level initialization
   // code here. Store the IReplNotify interface passed away in
   // our m_pNotify member. If you want to do real-time store
   // change notifications, you will have to use this pointer.
   //
   m_pNotify = pNotify;
   m_pNotify->AddRef();
   m_fInitialized = TRUE;
   return S_OK;
}

The more interesting part is the GetStoreInfo function. There, your provider will populate the STOREINFO struct with information about the store. It will be called twice: first to get the buffer size required to keep the store's unique ID and then to copy this ID to the buffer. Take a look at the next code snippet:

STDMETHODIMP CASPSimpleStore::GetStoreInfo(PSTOREINFO pInfo)
{
   if((pInfo == NULL)||(pInfo->szProgId == NULL)
                     ||(pInfo->szStoreDesc == NULL))
      return E_INVALIDARG;
   //
   // The store info flags are used to specify how this store
   // can be called by the activeSync manager and also the
   // method for notifying of changes to this store. The flag
   // SCF_SINGLE_THREAD specifies that this store can only
   // handle single-threaded calls(it is not thread-safe).
   // The flag SCF_SIMULATE_RTS specifies that we need to be
   // polled by the ActiveSync manager for any changes in the
   // store. If this flag is not set, ActiveSync assumes that
   // you will notify it of any changes using the IReplNotify
   // call back interface.The default polling frequency is
   // (5000 msecs), as set by the pInfo->uTimerRes member.
   // To be polled only when the ActiveSync window is activated
   // set uTimerRes to 0.
   //
   pInfo->uFlags = SCF_SINGLE_THREAD|SCF_SIMULATE_RTS;
   // default freq of 5 secs
   //
   // Set the ProgId & description of the store
   //
   ::lstrcpy(pInfo->szProgId,    m_pszProgId);
   ::lstrcpy(pInfo->szStoreDesc, m_pszDesc);
   //
   // bail if store is not yet initialized
   //
   if(!m_fInitialized)
      return S_OK;
   //
   // TODO: Construct something that will uniquely identify this
   // store. We just use a UINT number by default. If you need an
   // alternate way to identify your store, you will need to
   // modify the code section below.
   //
   pInfo->cbStoreId = sizeof(UINT);
   if(pInfo->cbStoreId > pInfo->cbMaxStoreId)
      return E_OUTOFMEMORY;
   if(pInfo->lpbStoreId == NULL)
      return E_POINTER;
   UINT *puId = reinterpret_cast<PUINT>(pInfo->lpbStoreId);
   *puId = 12345;
   return S_OK;
}

In the sample, you use some predefined number for the store ID. You may choose some other strategy. ReportStatus and CompareStoreIDs are in the sample's zip file.

Enumeration of folder objects

This group of functions perform item enumeration within a folder. Your data may be stored everywhere, so this is a place where you can iterate through different items, also providing information about modification timestamps:

HRESULT CASPSimpleFolder::FindFirstItem(HREPLITEM *phItem,
                                        BOOL *pfExist)
{
   *phItem  = NULL;
   *pfExist = FALSE;
   //
   // We need to get info about our file and set the new
   // CReplItem's members.
   // If our file has been deleted, we simply return success.
   //
   WIN32_FILE_ATTRIBUTE_DATA atrFile;
   ::ZeroMemory(&atrFile, sizeof(atrFile));
   if(!::GetFileAttributesEx(GetSyncFileName(),
      GetFileExInfoStandard, &atrFile))
      return S_OK;    // file does not exist
   //
   // We create a new CReplItem object for our one & only file
   // here and initialize its members.
   //
   CReplItem *pItem = new CReplItem(GetSyncFileName());
   if(pItem == NULL)
   return E_OUTOFMEMORY;
   pItem->SetModified(atrFile.ftLastWriteTime);
   *phItem = reinterpret_cast<HREPLITEM>(pItem);
   *pfExist = TRUE;
   return S_OK;
}
HRESULT CASPSimpleFolder::FindNextItem(HREPLITEM *phItem,
                                       BOOL *pfExist)
{
   //
   // TODO: Find the Next item in your folder, create a new
   // CReplItem object set its members & return it thru hItem ptr.
   // You can free any additional memory allocated in this call
   // when FindItemClose() is called. If there are no more items
   // in the folder, set *pfExist to FALSE.
   //
   *pfExist = FALSE;
   return S_OK;
}
HRESULT CASPSimpleFolder::FindItemClose()
{
   //
   // TODO: Sync manager calls this function after enumerating
   // all the items in your folder(thru calls to FindFirstItem()
   // & FindNextItem()). Add clean up code here - you will
   // have to free any memory that was allocated.
   //
   return S_OK;
}

This implementation works with a single flat file. You may want to have multiple files and manipulate them here.

I won't discuss all the rest of the methods; additional details are in the sample project. My goal is to highlight important points you need to pay attantion to, that's all. You have seen the 'Standard management routines' section in the previous article, so please refer to it if you need to. Being said in a couple of words, you have to focus on item iteration and serialization routines. The following picture shows the results of the ActivateDialog call:



Click here for a larger image.

IReplObjectHandler Implementation

Similar to its device counterpart, the code for the desktop implements the same interface, but probably with some differences from the PDA. It doesn't make sense to discuss it here.

Registration

Finally, you have reached a point when you will register all your created stuff. The whole process will be devided into the device part and the desktop one. The PDA registration is easy:

extern "C" BOOL _declspec(dllexport) RegisterActiveSync()
{
   // register this DLL as implementing an ActiveSync service
   HKEY hKey;
   DWORD dwDisp;
   TCHAR szYourProvider[] = _T("YourProvider.dll");
   if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,
      _T("Windows CE Services\Synchronization\Objects\
          YOUR_PROVIDER"),
      0, NULL, 0, 0, NULL, &hKey, &dwDisp) != 0)
      return FALSE;
   if(RegSetValueEx(hKey, _T("Store"),
                    NULL,
                    REG_SZ,
                    (LPBYTE)szYourProvider,
                    _tcslen(szYourProvider) * sizeof(TCHAR)) != 0)
      return FALSE;
   return TRUE;
}

You may call such a function from your application or add appropriate entries into the installation project.

The desktop part is a bit longer. You have to create a key with the same name as for the device because they're linked by name. The DllRegisterServer function makes all required jobs; here I just place a short 'ToDo' list of Registry keys used by ActiveSync to identify the service provider:

// + Create provider key          HKLMSOFTWAREMicrosoft
                                     Windows CE Services
                                  ServicesSynchronization
                                     ObjectsASPSimple
// + Set default value            HKLMSOFTWAREMicrosoft
                                     Windows CE Services
                                  ServicesSynchronizationObjects
                                     ASPSimpleASPSimple
// + Create "display name" key    HKCRASPSimple.ASPSimple
                                     Display Name
// + Set default value            HKCRASPSimple.ASPSimple
                                     DisplayName - SimpleFileSync
// + Set Display Name             HKCRASPSimple.ASPSimple
                                     DisplayName - SimpleFileSync
// + Set Plural Name              HKCRASPSimple.ASPSimple
                                     PluralName - SimpleFileSync Items
// + Set Store Name               HKCRASPSimple.ASPSimple
                                     Store - DeveloperCom.SimpleFileSync

Final results are below:

Conclusion

Well, you finally have dug to some outcome. You have done a huge job to get it working, but now you may use it for your pleasure. And, now is a good time to reveal a tiny mystery: Pocket PC 2003 SDK contains a wizard for desktop Visual Studio, which helps you in ASP skeleton creation. Anyway, even if it does most of the routine job for you, you have to understand what is it to successfuly focus on the meaningful parts of your project. Besides, SDK samples contain a Stock Portfolio project that illustrates the continuous data synchronization process very well.

Download

Download the accompanying code's zip file here.

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date