Microsoft Corporation
October 1998
Contents
Overview
The Sample StockPor Application
The Desktop StockPor Application
The Device StockPor Application
Desktop ActiveSync Provider
Device ActiveSync Provider
Recommended Steps to Develop the ActiveSync Providers
What's New in ActiveSync for Windows CE 2.1
Questions and Answers
Appendix
List of Released Microsoft ActiveSync Service Providers
Available Command-Line Parameters to syncmgr.exe
Registry Values Used by the ActiveSync manager
Flowcharts
CESYNC.H
Source Code for StockPor Sample Application
Click here to download the sample files associated with this article from the Downloads Center.
Overview
The ActiveSync™ technology is an architecture specifically designed for data synchronization between a device running the Microsoft® Windows® CE operating system and a desktop computer. Data synchronization is not the same as a data transfer. A data transfer sends a set of data between two computers, but does not check for differences between the transferred data and data on the receiving computer. Data synchronization, however, updates the data on both computers based on changes or deletions since the last synchronization. During a synchronization, only the changed or deleted objects are transferred. ActiveSync also handles situations where changes have been made to both the desktop computer and the Windows CE-based device since the last synchronization.
You can develop ActiveSync service providers to synchronize any form of data. Several ActiveSync service providers are shipped with Windows CE Services—for example, the Microsoft Outlook ActiveSync Service Provider synchronizes the Microsoft Outlook® messaging and collaboration client with Outlook data on your Windows CE-based device. After your ActiveSync service provider is installed and the required registry entries are created, your data will be synchronized automatically between the desktop computer and the Windows CE-based device.
The ActiveSync manager takes care of many synchronization tasks that can be applied to all kinds of data. Examples of these tasks are maintaining a table that maps the data on the device and the desktop, detecting changes, transferring data, and resolving conflicts arising when changes were made on both machines since the last synchronization. The ActiveSync manager is built into Windows CE Services. You only need to develop and register the ActiveSync service provider to synchronize your data. (The provider does the tasks that are specific to your data, such as converting an object into a series of bytes and back again, enumerating the objects in the data set, and providing a user interface.)
Design Considerations
An object is an item you want to synchronize. You may want to synchronize several different types of objects in your application. For example, the Microsoft Outlook ActiveSync service provider synchronizes appointments, contacts, email, and tasks. The object type is the name for a group of synchronization objects. For example, "appointment" for the set of appointments in Microsoft Outlook. Folder is similar to object type and is used mostly in naming interface methods. The object and object type depend entirely on your application—it could be any item or items you choose. The store contains all the objects for your application. A store can be a database or a file.
First, you must determine what data to synchronize. Next, you must define the object, the object type, and the store. Objects must have an identifier, that is, an object ID. An object ID is a 32-bit value and can be an integer, a text string, or a series of bytes, but it must satisfy the following criteria: The object ID must be unique for each object of the same object type, it cannot change once it is assigned, it cannot be reused if the original object is deleted, and object IDs must be ordered, that is, they must allow you to determine which of two objects comes first. An object must also signify changes since the last synchronization, typically by using a version number or time stamp. The store can be a flat file, a Windows CE database, or some other custom format, but it must accommodate the objects.
Next, you must specify how your application will work with the object. The application must be able to enumerate objects consistently. It also must be able to convert an object into a series of bytes—for transmission between the desktop computer and the Windows CE-based device—and be able to convert a series of bytes back into an object. You may also want to develop your own UI, for example, a dialog box for the user to select synchronization options.
You need to create a name for the object type, which is used in both registry and code you will develop. A display name for the object type can be set up in the registry and displayed in the ActiveSync Status window. It's important to keep in mind that object type and object are logical definitions that you create and therefore can be anything you desire.
The ActiveSync Service Provider
You develop two modules in an ActiveSync service provider—one on the desktop and one on the Windows CE-based device. These modules are usually dynamic link libraries (DLLs). The following diagram shows how the modules work together with your application. The desktop module is typically a 32-bit in-process server that implements two COM interfaces: IReplObjHandler and IReplStore. The device module also implements the IReplObjHandler interface and six functions: InitObjType, ObjectNotify, GetObjTypeInfo, ReportStatus, FindObjects, and SyncData. The implementation of ReportStatus is optional. The FindObjects and SyncData functions are used only in Windows CE version 2.1, the operating system used by Handheld PC Pro. Your application works with the functions and interfaces that are available on each machine. In turn, these functions work through the interfaces provided by the ActiveSync service manager. The two parts of the ActiveSync service provider transfer data using the IReplObjHandler interface.
.gif)
Figure 1. ActiveSync structural diagram
Desktop Interfaces
IReplStore is the most important interface one must develop in the desktop module of an ActiveSync service provider. There are total of 22 methods in this interface, which can be classified as follows:
- Store Manipulation: Initialize, GetStoreInfo, CompareStoreIDs
- Object Enumeration: FindFirstItem, FindNextItem, FindItemClose
- Object Information: CompareItem, IsItemChanged, IsItemReplicated, UpdateItem
- Handle Manipulation: ObjectToBytes, BytesToObject, FreeObject, CopyObject, IsValidObject
- User Interface: ActivateDialog, GetObjTypeUIData, GetConflictInfo, RemoveDuplicates
- Miscellaneous: ReportStatus, GetFolderInfo, IsFolderChanged
IReplObjHandler is the interface used to serialize (turning synchronization object into a series of bytes) and deserialize (turning this series of bytes back into an object) objects. It's also used to delete an object from the store. Its methods are: Setup, GetPacket, SetPacket, Reset, DeleteObject.
The ActiveSync manager provides an interface, IReplNotify, to the service provider. This interface has four methods: OnItemNotify, GetWindow, SetStatusText, and QueryDevice. See the section IReplNotify for details about this interface.
Device Functions
The ActiveSync provider on the device must implement and export three functions: InitObjType, ObjectNotify, and GetObjTypeInfo. In Windows CE version 2.1, it can also implement FindObjects and SyncData (see the section "What's New in ActiveSync for Windows CE 2.1" for details). It can also optionally implement and export ReportStatus. InitObjType is used to both initialize and terminate a device ActiveSync provider. ObjectNotify is used to handle object identification and change detection. GetObjTypeInfo is used to retrieve device information about the object type. ReportStatus allows a device ActiveSync provider to be informed about certain events.
Configuration
You must set up the proper configuration in order for the ActiveSync manager to recognize an ActiveSync service provider. First, you need to provide a programmatic identifier (ProgId) for the desktop component. This should be a unique name. For example, the ProgId for Microsoft Outlook ActiveSync service provider is "MS.WinCE.Outlook." Second, you need to generate a GUID (or CLSID) for the service provider. You must create the following keys in the Windows registry to register this service provider:
HKEY_CLASSES_ROOT\Clsid\<Class ID>\InProcServer32
HKEY_CLASSES_ROOT\Clsid\<Class ID>\ProgID
HKEY_CLASSES_ROOT\<ProgID>\CLSID
The Default value of the InprocServer32 key is the full path of the 32-bit DLL that implements the IReplStore interface. For example, for Microsoft Outlook, the Default value would be the full path to outstore.dll. The Default value of the ProgID key, in this case, would be MS.WinCE.Outlook. The CLSID is used by COM (CoCreateInstance) to create an instance of the store interfaces.
After the service provider is registered, you must register each object type it synchronizes in a subdirectory under HKEY_LOCAL_MACHINE. The following example registers the object types Appointment, Contact, and Task:
HKEY_LOCAL_MACHINE
Software
Microsoft
Windows CE Services
Services
Synchronization
Objects
Appointment
Contact
Task
Each object type name is a key. Under each key, you must define the five values: default, Display Name, Plural Name, Store, and Disabled. For the appointment object type in our Microsoft Outlook example, we have the following values:
[Default] "Outlook Appointment Object"
Display Name "Appointment"
Plural Name "Appointments"
Store "MS.WinCE.Outlook"
Disabled 0
The default is a descriptive name of the synchronization object. The Display Name and Plural Names are text, which will be displayed in various user interfaces. The Store value is the name of the ProgID of the ActiveSync service provider. Disabled tells if synchronization of this object type should be disabled by default.
Whenever a new device is connected to the desktop and a new device profile is to be created in Mobile Device Folder (this is the desktop program where user manages device profiles), the registry keys for the synchronization objects under HKEY_LOCAL_MACHINE are automatically copied to HKEY_CURRENT_USER. In our example, we would have Appointment, Contact, and Tasks registered as follows:
HKEY_CURRENT_USER
Software
Microsoft
Windows CE Services
Partners
<Device ID>
Services
Synchronization
Objects
Appointment
Contact
Tasks
The registration on the device is similar but simpler. All you need to do is register the ActiveSync device component under HKEY_LOCAL_MACHINE. The following shows the proper registration for our Microsoft Outlook example:
HKEY_LOCAL_MACHINE
Windows CE Services
Synchronization
Objects
Appointment
Contact
Tasks
Again, each object type name is a key, but now only two values are defined: Store and Display Name. For the Appointment object type in our example, we would define:
Store "pegobj.dll"
Display Name "Appointment"
Here, the Store refers to the DLL which exports the functions for this object type, while Display Name is the same as it was for the desktop registration under HKEY_LOCAL_MACHINE.
The Sample StockPor Application
To help developers to develop ActiveSync service providers, Windows CE Software Development Kit (SDK) provides sample application named StockPor. Here's a listing of files:
| Directory | File Name | Description |
| | | Source file shared by both desktop and device application. |
| | | Header file for structures and constants shared by the application and the ActiveSync provider. |
| | | Header file for resource constants. |
| | | Resource file shared by both desktop and device application. |
desktop
for the desktop application
| | Header file for structures and constants used by desktop application. |
| | | Desktop application icon file. |
| | | The rest of desktop application source code. |
desktop\sync
for ActiveSync service provider's desktop component
| | Source file for the GUIDs used. |
| | | Module definition file needed for the desktop DLL. |
| | | Resource file. |
| | | Registry setup file. |
| | | Header file for classes, structures and constants. |
| | | Source file that implements IreplObjHandler. |
| | | Source file that implements IreplStore. |
device
for the device application
| | Device application icon file. |
| | | The rest of device application source code. |
| | | Header file for structures and constants used by device application. |
| | | Source file for the device setup application. |
device\sync
for ActiveSync service provider's device component
| | Module definition file needed for the device DLL. |
| | | Header file for structures and constants. |
| | | Source file that implements the ActiveSync device component. |
The StockPor application can be used to keep track of one's stock holdings. The application runs on both the desktop PC and the device and uses the same user interface. An ActiveSync service provider is implemented such that any changes or deletions made on either side can be synchronized automatically. The object type is named "Stock". After the ActiveSync service provider is installed in Windows CE Services, "Stock" appears in the ActiveSync status window in the same way as "Appointment" or "Task" is displayed for Microsoft Outlook.
.gif)
Figure 2. ActiveSync Status window
The desktop application shares much of the user interface code with the device application. All platform-dependent code and data are separated into a class named CStock, which is defined differently for the desktop and the device, as illustrated in the following code from stockpor.cpp:
// define class CStocks
#ifdef UNDER_CE
#include "device\stocks.h"
#else
#include "desktop\stocks.h"
#endif
The CStore class defines many platform-dependent methods:
| Method Name | Description |
| BOOL Open( LPSTR lpszFile, BOOL fFailOnNew) | Open the stock portfolio database. |
| BOOL SetupDlg( HWND hDlg, UINT uParam ) | Set up a dialog for user to change the stock data. |
| BOOL Add( HWND hDlg ) | Add a new stock from the given dialog. |
| BOOL Change( HWND hDlg, UINT uParam ) | Change the stock data. |
| void Delete( UINT uParam ) | Delete the selected stock. |
| void OnDataChange( void ) | Called whenever stock data is changed. |
| BOOL BeforeAddChg( void ) | Called before a stock is about to be added or changed. |
For the desktop, these methods are implemented in the source file desktop\stocks.cpp. For the device, these methods are implemented in device\stocks.cpp.
The following chapters explain the sample application and the ActiveSync service provider in detail.
The Desktop StockPor Application
.gif)
Figure 3. Stock Portfolio application on desktop PC
The user interface is straightforward. The Open command opens a data file, Add Stock adds a new stock, Change Stock changes information about one stock, Delete Stock deletes the selected stock. The application stores many pieces of data for each stock: the symbol, company name, last quote price, purchase date, purchase price, gain/loss and last time the stock information is updated.
The portfolio data is stored in a shared memory mapped file, created by the following code in stocks.cpp:
m_hFile = CreateFile( m_szFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
m_hMapObj = CreateFileMapping( m_hFile, NULL, PAGE_READWRITE, 0, sizeof( PORTFILE ),
SZ_MAP_OBJ );
m_pStocks = (PPORTFILE)MapViewOfFile( m_hMapObj, FILE_MAP_WRITE, 0, 0, 0 );
Up to 500 stocks can be stored in the fixed-size data file. Each stock is stored in a structured name STOCK.
typedef struct tagStock
{
UINT uidStock; // the stock id
UINT uFlags; // see SF_* above
FILETIME ftViewTime; // time stamp of the item showed in the list view
FILETIME ftUpdated; // updated time of the stock
FILETIME ftLastModified; // last modification time of this stock
char szSym[ 10 ];
char szCompany[ 80 ];
char szLastPrice[ 20 ];
char szPurDate[ 20 ];
char szPurPrice[ 20 ];
char szGain[ 20 ];
} STOCK, *PSTOCK;
The memory-mapped file is structured as follows:
typedef struct tagPortFile
{
UINT uVer1; // must be equal to FILE_VERSION
UINT uidCurrStock; // current stock ID
// change/delete logs
UINT cChg, cDel;
UINT rgidChg[ MAX_STOCKS ], rgidDel[ MAX_STOCKS ];
UINT cStocks; // # of stocks in the portfolio
STOCK rgStocks[ MAX_STOCKS ];
UINT uVer2; // must be equal to FILE_VERSION
} PORTFILE, *PPORTFILE;
PORTFILE::uidCurrStock is a counter of the stock, which is incremented by one each time a new stock is created. It serves as the object ID of the new stock. PORTFILE::rgidChg is an array of object IDs for changed stocks. PORTFILE::rgidDel is an array of object IDs for deleted stocks. This is used solely for synchronization, so ActiveSync provider knows how many objects need to be synchronized. ActiveSync provider deletes the entries when it finishes using them in the shared memory.
Each stock in the application list view may be marked with the SF_IN_VIEW flag. When ActiveSync informs the application about changes, the application checks this flag to identify a new stock to add to the list view.
The application is coordinated with its ActiveSync provider in the following ways:
- They share the same data file. Access to the file is protected by a named mutex "StockPortMutex".
- For every change to the data file, the application puts changed object IDs in PORTFILE::rgidChg and deleted object IDs in PORTFILE::rgidDel. The application then signals the named event "StockPorChange". ActiveSync uses a thread to check for this named event, and when notified it calls IReplNotify::OnItemNotify on the object IDs. ActiveSync then removes the entries from the shared memory.
- ActiveSync sends the application a WM_DATA_CHANGED message whenever it completes a synchronization that changes the database.
- Upon receipt of this message, the application does a mini-synchronization between the data in the list view and the data on the file. It uses SF_IN_VIEW flag and STOCK::ftViewTime to indicate what stocks need to be synchronized.
The Device StockPor Application
The user interface for this device application is the same as that on the desktop application and shares much of the code with the desktop application in stockpor.cpp.
.gif)
Figure 4. StockPor application on the device
The portfolio data is stored in a database of type DBTYPE_STOCKPOR (defined as 21238). The database has the name "\StockPor.DB". Each stock is stored as a record in the database with the following properties:
// property tags used in the stock record
#define HHPR_FLAGS PROP_TAG( PEGVT_UI2, 0x8200 )
#define HHPR_SYMBOL PROP_TAG( PEGVT_LPWSTR, 0x8201 )
#define HHPR_COMPANY PROP_TAG( PEGVT_LPWSTR, 0x8202 )
#define HHPR_PRICE PROP_TAG( PEGVT_LPWSTR, 0x8203 )
#define HHPR_PUR_DATE PROP_TAG( PEGVT_LPWSTR, 0x8204 )
#define HHPR_PUR_PRICE PROP_TAG( PEGVT_LPWSTR, 0x8205 )
#define HHPR_GAIN_LOSS PROP_TAG( PEGVT_LPWSTR, 0x8206 )
#define HHPR_UP_TIME PROP_TAG( PEGVT_FILETIME, 0x8207 )
The HHPR_FLAGS is a special property that the application shares with the ActiveSync provider. It is a combination of the following flags:
SF_CHANGED1: record hasn't been sync'ed with first PC partner, must be valued at 1
SF_CHANGED2: record hasn't been sync'ed with second PC partner, must be valued at 2
SF_CHG_IN_SYNC: this object is changed again during sync
SF_UPDATE_VIEW: the record is changed by the synchronization and the view needs update.
The device application is coordinated with its ActiveSync provider in the following ways:
- They share the same database. Unlike the desktop application, there is no need to use a mutex to protect the database because database APIs in Windows CE are guaranteed to be atomic.
- Every time the user changes the record using the application, the application turns on the SF_CHANGED1 and SF_CHANGED2 flags, so the ActiveSync provider can pick up the changes.
- Every time the ActiveSync provider updates a record, it sets the SF_UPDATE_VIEW flag so the application knows to update the contents displayed in the UI.
- The ActiveSync provider is informed of any changes to the database records. The ActiveSync service manager calls the ObjectNotify function automatically whenever there is a change or deletion to the database records.
- The ActiveSync provider sends the application a WM_DATA_CHANGED message whenever it completes a synchronization that changes the database.
Desktop ActiveSync Provider
The Stockpor desktop ActiveSync provider is implemented as a DLL, which is named stsync.dll. This DLL exposes two COM interfaces: IReplStore and IReplObjHandler. An ActiveSync provider can support synchronization of multiple object types. For example, the Microsoft Outlook ActiveSync provider synchronizes appointments, contacts, and tasks. In the StockPor sample code, synchronization of one object type—"StockPor"—is supported.
HREPLITEM and HREPLFLD
HREPLITEM is an important data type for any ActiveSync provider. Each handle uniquely identifies one object. To the ActiveSync service manager, this handle is simply a 32-bit number created by the ActiveSync provider. Whenever the service manager needs to know something about the object, it calls methods in IReplStore or IReplObjHandler and passes the handle to the ActiveSync provider. To the ActiveSync provider, this handle is typically a pointer to an internal structure or a class instance. In the Stock Portfolio ActiveSync provider, it is a pointer to a CItem instance, which contains a 32-bit object ID and a time stamp for the last modification made to the object. A new HREPLITEM handle is always created by IReplStore::FindNextItem or by IReplStore::BytesToObject.
Since HREPLITEM contains an object ID, given two handles, the ActiveSync provider can tell if they represent the same object. HREPLITEM may also contain information like a time stamp of last modification or a change number of the object, so given two handles representing the same object, the ActiveSync provider can tell if one handle represents a more recent copy of the object (that is, the object is changed.)
There must be an order between two handles representing two different objects. In the sample code, implementation of IReplStore::CompareItem checks the object IDs contained in the given handles. It returns 1 if the first handle is bigger than the second handle, -1 if the first one is less than the second one and 0 if two are equal. Since IReplStore::CompareItem will be called frequently, it must be implemented in the most efficient way. Ordering handles allows the ActiveSync server manager to use a binary search on its table of handles.
An ActiveSync provider can selectively synchronize objects, a process known as filtering. This is implemented by IReplStore::IsItemReplicated. For example, an ActiveSync provider may want to synchronize only appointments from the last two weeks to four weeks in the future. This is implemented by IReplStore::IsItemReplicated. Given a HREPLITEM, the ActiveSync provider can either use the data stored in the handle directly or open the object and read related data so the filter can be applied. If the object is determined to be not replicated, the ActiveSync provider can return FALSE in this routine. If such an object exists on the device, the ActiveSync manager issues a command to the device to delete it. The corresponding object on the desktop is not touched and any future changes to the object are not synchronized unless the change makes the object become replicated again.
HREPLFLD is a handle that identifies a folder, which is another name for object type. In the Stock Portfolio ActiveSync provider, HREPLFLD is a pointer to an instance of the CFolder class. Since the StockPor ActiveSync provider supports synchronization of just one object type, only a single instance of the class needs to be created in IReplStore::GetFolderInfo.
The ActiveSync manager knows nothing about the handle. When the time comes to remove the handle from memory, the ActiveSync manager calls IReplStore::FreeObject to give the ActiveSync provider a chance to free whatever resource is used by the handle. If it needs to copy the data from one handle to another handle that represents the same object, the ActiveSync provider calls IReplStore::CopyObject. From time to time, the ActiveSync provider needs to make sure the handle still represents a valid object (not an object that has been deleted) so it calls IReplStore::IsValidObject.
Data stored in these HREPLITEM or HREPLFLD handles can be saved in a file named repl.dat, which is created and maintained by the ActiveSync manager for each device. An ActiveSync provider implements IReplStore::ObjectToBytes to convert a HREPLITEM or HREPLFLD to a series of bytes and IReplStore::BytesToObject to convert the same series of bytes back to a handle. Immediately after device is connected, repl.dat is read so all handles used in the previous synchronization section can be recreated. As long as a device is connected, the ActiveSync manager saves the handles in repl.dat whenever any object represented by the handle is changed.
the ActiveSync manager keeps different data file for different device. It makes sure the correct data file is loaded for the connected device. This process is transparent to the ActiveSync provider.
Store Information and Store Identifiers
It is important for an ActiveSync provider to tell if the store it synchronizes now is the same as the one used in the last synchronization. If it is different, mapping between desktops and device objects must be reestablished using a process known as "Combine/Discard." Combine means combining all the desktop and device objects together. Discard means discarding the device objects and replacing them with desktop objects. Combine may result in duplicated objects, which an ActiveSync provider should be capable of removing. See the section "Implementation of Combine/Discard and Removal of Duplicate Objects" for other cases where Combine/Discard process is required.
The ActiveSync manager calls IReplStore::GetStoreInfo to retrieve information about a store, including a store identifier. This store ID can be any size and the complete ID is stored in repl.dat. This ID is read right after a connection is made with the device. The ActiveSync manager passes it to IReplStore::CompareStoreIDs. ActiveSync provider must be able to tell if the current store ID matches the one loaded from repl.dat (which identifies the store used in previous synchronization).
The structure STOREINFO is passed into IReplStore::GetStoreInfo. The structure is defined as:
typedef struct tagStoreInfo
{
UINT cbStruct; // Size of this structure
UINT uFlags; // Miscellaneous flags, see SCF_xxx above
TCHAR szProgId[ 256 ]; // ProgID name of the store object
TCHAR szStoreDesc[ 200 ]; // Description of the store
UINT uTimerRes; // How often to enumerate? time in micro-seconds.
UINT cbMaxStoreId; // Max. size of the store ID.
UINT cbStoreId; // Actual size of the store ID.
LPBYTE lpbStoreId; // points to the store ID
} STOREINFO, *PSTOREINFO;
If the ActiveSync provider does not support real time notification of changes/deletions, it needs to set the STOREINFO::uTimerRes accordingly. If STOREINFO::uTimerRes is set to a nonzero time in microseconds, the ActiveSync manager automatically starts enumeration of the store once every interval. If STOREINFO::uTimerRes is set to –1, the ActiveSync manager only starts the enumeration either when the ActiveSync status window is activated by the user or just before synchronization starts.
To get back a variable-size store ID, the ActiveSync manager calls IReplStore::GetStoreInfo first with STOREINFO::cbMaxStoreId set to 0. The ActiveSync provider should set the required size for a store ID in STOREINFO::cbStoreId and return E_OUTOFMEMORY. The ActiveSync service manager then allocates memory and passes the pointer in STOREINFO::lpbStoreId, which is used by the ActiveSync provider to save the store ID.
Initialization and Termination
IReplStore::Initialize ensures the stock portfolio data file, for example, demo.por, exists and is opened. The name of the file that needs to be synchronized is stored in the registry. The registry location for this file name depends on whether the ActiveSync provider is initialized for the connected or selected device. Note that, in the Mobile Device Folder, there may be multiple device profiles. If no device is connected, the user can select any one of the devices and set ActiveSync options, which may cause the ActiveSync provider to be initiated. If a device is connected, the provider will always be initialized for the connected device. The bit flag ISF_SELECTED_DEVICE passed into IReplStore::Initialize is set if the provider is initiated for the selected device profile. To get the registry key for the selected device profile, use IReplNotify::QueryDevice( QDC_SEL_DEVICE_KEY, &hKey ). To get the registry key for the disconnected device profile, use IReplNotify::QueryDevice( QDC_CON_DEVICE_KEY, &hKey ).
The bit flag ISF_REMOTE_CONNECTED is set if the device is remotely connected, for example, through a modem or an Ethernet card. Whenever this flag is set, the ActiveSync provider should avoid using any blocking UI, such as MessageBox or a dialog box. The provider should instead take any default actions without prompting the user on the desktop because, when a device is connected remotely, a user may not be able to respond to the user interface.
In general, IReplStore::Initialize is the first method in IReplStore that is called by the ActiveSync manager. However, there are cases where other methods can be called first, especially when users need to change synchronization options for a disconnected device. The following is a list of methods that might be called before IReplStore::Initialize:
| Method | Purpose |
| IReplStore::GetStoreInfo | Get information about a store. This information is displayed in the ActiveSync Option dialog. |
| IReplStore::GetObjTypeUIData | Get information about an object type. The information is displayed in the ActiveSync Option dialog. |
| IReplStore::GetFolderInfo | Give the ActiveSync provider a chance to see the folder handle (HREPLFLD). |
| IReplStore::ActivateDialog | Let an ActiveSync provider to allow user to change options for a synchronization service. |
| IReplStore::BytesToObject | Convert a series of bytes to a HREPLITEM or HREPLFLD handle. |
| IReplStore::ObjectToBytes | Convert a HREPLITEM or HREPLFLD handle to a series of bytes. |
| IReplStore::ReportStatus | Let the ActiveSync provider know about certain events. |
You should make sure all of above methods work properly without IReplStore::Initialize being called first.
the ActiveSync manager calls IReplStore::GetObjTypeUIData to retrieve object type-specific data for display in the ActiveSync status window. An ActiveSync provider must set up the given OBJUIDATA structure correctly. This structure is defined as:
typedef struct tagObjUIData
{
UINT cbStruct; // size of this structure
HICON hIconLarge; // Handle of a large icon used in the list view
HICON hIconSmall; // Handle of a small icon used in the list view
char szName[ MAX_PATH ]; // Text displayed in the "Name" column
char szSyncText[ MAX_PATH ]; // Text displayed in the "Sync Copy In" column
char szTypeText[ 80 ]; // Text displayed in the "Type" column
char szPlTypeText[ 80 ]; // Plural form of text displayed in the "Type"
} OBJUIDATA, *POBJUIDATA;
See Flowchart 1 in the Appendix for an illustration of the initialization process.
The ActiveSync provider is unloaded automatically when the device is disconnected. The ActiveSync manager is typically the last one calling IReplStore::Release. The reference count of the provider should reach zero and the interface can then be deleted. However, during the running of an ActiveSync provider, it may make some calls to another party that increases the reference count of its own IReplStore instance. Therefore, the ActiveSync manager may not be the last one that releases the interface. The ActiveSync manager always calls IReplStore::ReportStatus with RSC_RELEASE before it attempts to release the interface by IReplStore::Release. An ActiveSync provider can make sure all other external interfaces are released when RSC_RELEASE is received. This will ensure the reference count reaches zero when the ActiveSync manager frees the store.
Enumeration of All Desktop Objects
An ActiveSync provider must be able to enumerate all objects for a given folder. IReplStore::FindFirstItem is always called first when the enumeration starts. The ActiveSync provider can initialize anything that is needed for the enumeration. In the sample code, it takes a snapshot of all existing objects in the memory map file and calls IReplStore::FindNextItem. Once HREPLITEM's of all objects are returned, the code sets *pfExist to FALSE. This terminates the enumeration and then IReplStore::FindItemClose is called.
If the ActiveSync provider filters synchronization objects, it can choose to return only objects that pass the filter during this enumeration. As a result, once an object falls outside the filter after a synchronization, it appears as a deleted object on the desktop. Since the desktop enumeration no longer returns the object, the ActiveSync manager thinks it is deleted from desktop and issues commands to the device to delete the corresponding object. This may not be desirable for devices that synchronize with multiple desktop PCs. If the filter settings in each desktop PC are different, the objects on one desktop PC may be deleted because the device object is deleted after it falls out of the filter of another desktop PC. To prevent this, the ActiveSync provider should always return every object in the store during the enumeration, and use IReplStore::IsItemReplicated to implement the filter.
If the enumeration takes more than a few seconds to complete, the ActiveSync provider can call IReplNotify::SetStatusText to display text to let user know about the enumeration progress.
If an ActiveSync provider has an efficient way of detecting if any object in a folder is changed or deleted, it should implement IReplStore::IsFolderChanged. If it sets *pfChanged to FALSE when no object is changed or deleted, the ActiveSync manager skips the enumeration of objects in the folder. But if ActiveSync provider is capable of detecting changes and deletions in real time, it should set the *pfChanged to FALSE every time, except the first time IReplStore::IsFolderChanged is called. When the IReplStore::IsFolderChanged is called the first time after connection, it is important to let the ActiveSync manager know that there is a possibility that one or more objects have been changed or deleted since last synchronization. Typically, an ActiveSync provider sets a flag in IReplStore::GetFolderInfo for each folder and in IReplStore::IsFolderChanged, if this flag is set, set *pfChanged to TRUE and clear the flag. If the flag is not set, always set *pfChanged to FALSE.
See Flowchart 2 in the Appendix for an illustration of the object enumeration process.
Detecting Changes or Deletions Between Synchronization
The ActiveSync manager automatically detects changes and deletions by comparing the list of handles returned by the current enumeration with the saved list of handles loaded from repl.dat. Internally, before the start of the enumeration, the ActiveSync manager marks a bit for each handle in its table of handles. Each time the ActiveSync providers return a new handle through IReplStore::FindFirstItem or IReplStore::FindNextItem, the ActiveSync manager attempts to find a handle that represents the same object by doing a binary search on its table. If no matching handle is found, a new object is created on the desktop store. If a matching handle is found, the ActiveSync manager clears the bit for the handle in its table, and calls IReplStore::IsItemChanged to see if the object is changed since last time it is synchronized. If so, the ActiveSync manager calls IReplStore::CopyObject to copy the data from the returned handle into the handle it saves. It then calls IReplStore::IsItemReplicated to see if it should be sent down to the device. At the end of enumeration, all handles in the ActiveSync manager's internal table that are still marked represent objects not returned by the enumeration and therefore must have been deleted from the desktop store.
IReplNotify
This is an interface implemented by the ActiveSync manager. Any ActiveSync provider can use the methods defined in this interface. The methods are:
- OnItemNotify notifies the ActiveSync manager on any change or deletion made to an object. Also notifies ActiveSync manager to shut down the ActiveSync provider. This enables the manager to update the synchronization status for the service provider automatically in real time. If the service provider doesn't have the capability to detect object changes/deletions in real time, it can simply ignore this method.
- SetStatusText sets the text to be displayed in the status bar in the ActiveSync status window, the mobile device window, and anyplace else where synchronization status can be seen.
- GetWindow returns a Window handle that is used as a parent window of any modal dialog or message box.
- QueryDevice returns information about the connected or selected device.
Report of Changes or Deletions in Real Time
If an ActiveSync provider is capable of detecting changes or deletions as soon as they take place in the desktop store, it can call IReplNotify::OnItemNotify to let the ActiveSync manager know immediately. The ActiveSync provider passes RNC_MODIFIED or RNC_CREATED if the object is simply created or modified and RNC_DELETED if the object is deleted. It also passes a handle to the object, which allows the ActiveSync manager to search its own table and find out which device object (if any) this handle corresponds to.
An ActiveSync provider can also call IReplNotify::OnItemNotify with RNC_SHUTDOWN if it detects the desktop application has closed and the ActiveSync provider needs to be unloaded. The ActiveSync manager responds by unloading the ActiveSync provider and updating the status display accordingly.
See Flowchart 4 in the Appendix for an illustration of the real time notification process.
Sending and Receiving Objects
Synchronization can be initiated by the user or done automatically as soon as data become out-of-date (as implemented by above-mentioned IReplNotify::OnItemNotify call).
IReplObjHandler is the COM interface used to convert an object to a series of bytes, that is, serialization, and to convert a series of bytes back to an object, or deserialization. The ActiveSync manager also uses this interface to delete an object from the store. The IReplObjHandler interface is implemented on both desktop and the device so that much of the code can be shared. One instance of this interface is created for each object type.
There is no limitation or specification on how an object can be serialized. The ActiveSync manager never knows the format of the bytes. An ActiveSync provider can serialize the object into any number of bytes and can group these bytes into any number of packets. The ActiveSync manager guarantees the packets will be sent to the device in the exact same number and sequence as they are given to the ActiveSync manager.
For StockPor, it is easy to serialize and deserialize data on each stock, since only one packet is needed. The packet has the following structure:
// data structure used to synchronize a stock
// used by IReplObjHandler on both the desktop and device
// all strings are always in UNICODE
typedef struct tagStockPacket
{
WCHAR wszSym[ 10 ];
WCHAR wszCompany[ 80 ];
WCHAR wszLastPrice[ 20 ];
WCHAR wszPurDate[ 20 ];
WCHAR wszPurPrice[ 20 ];
WCHAR wszGain[ 20 ];
FILETIME ftUpdated;
} STPACKET, *PSTPACKET;
The following methods are always called in sequence whenever an object is serialized into a series of bytes:
| Method | Purpose |
| IReplObjHandler::Setup | Tell ActiveSync provider which object is to be serialized. Give it a chance to allocate any resources needed for serialization. |
| IReplObjHandler::GetPacket | Let ActiveSync provider create one or more packet of bytes of any size. This is called multiple times until RWRN_LAST_PACKET is returned. |
| IReplObjHandler::Reset | Serialization is completed. Let ActiveSync provider free any resources used . |
The structure REPLSETUP is passed in IReplObjHandler::Setup. This structure is defined as:
typedef struct _tagReplSetup
{
UINT cbStruct;
BOOL fRead;
DWORD dwFlags; // see RSF_xxx above.
HRESULT hr;
OBJTYPENAME szObjType;
IReplNotify *pNotify;
DWORD oid;
DWORD oidNew;
#ifndef UNDER_CE
IReplStore *pStore;
HREPLFLD hFolder;
HREPLITEM hItem;
#endif
} REPLSETUP, *PREPLSETUP;
The ActiveSync provider needs only the following members in the structure:
- fRead is set to TRUE for reading an object from the desktop store and FALSE for writing an object into the desktop store.
- dwFlags is a collection of bit flags related to object serialization/deserialization.
- hFolder is a handle to the folder.
- hItem is the handle the object that needs to be serialized. ActiveSync should use the information contained in this handle to identify the object and convert it into packets of bytes.
All other members are internal to the ActiveSync manager and should not be changed.
The process of receiving an object from the device is very similar to sending an object. After packets of data arrive from the device, IReplObjHandler interface methods are called to let ActiveSync provider convert those packets back to an object. See the following table:
| Method | Purpose |
| IReplObjHandler::Setup | Tell ActiveSync provider which object is to be deserialized. Give it a chance to allocate any resources needed for deserialization. |
| IReplObjHandler::SetPacket | Send packets to the ActiveSync provider so it can recreate the object. Packets are sent in the exact same number, same size, and same sequence. This is called multiple times until last packet is received from the device. |
| IReplObjHandler::Reset | Deserialization is completed. Let ActiveSync provider free any resources used. |
The ActiveSync provider must take the data packets and create an object. A new HREPLITEM representing the object must be created and set in REPLSETUP::hItem.
In certain ActiveSync providers, it may be required to synchronize objects coming from the device as deletions. For example, when the user deletes an e-mail message on the device, the message is actually marked "changed" because it is simply moved to the Deleted Item folder. When the desktop ActiveSync provider receives such an object, it may want to delete the messages on the desktop. In this case, the following special error codes can be returned by IReplObjHandler::SetPacket:
- RERR_DISCARD: ActiveSync provider wants to delete the device object immediately after the change is synchronized. ActiveSync manager will send a command to the device object to delete the corresponding object.
- RERR_DISCARD_LOCAL: ActiveSync provider wants to delete the desktop object immediately after the change is synchronized. ActiveSync manager will call IReplObjHandler::DeleteObject to delete the existing desktop object.
See Flowchart 3 in the Appendix for an illustration of the object synchronization process.
Conflict Resolution
If an object is changed on both the device and the desktop before it is synchronized, there is a conflict. The ActiveSync manager first issues a command to the device to get the object up to the desktop. Methods in IReplObjHandler are called on the device ActiveSync provider to read data out of the device store. The data is brought up to the desktop and methods in IReplObjHandler are called on the desktop ActiveSync provider to create a temporary object. In both the device and the desktop call, RSF_CONFLICT_OBJECT is set in REPLSETUP::dwFlags. After the data is written, the ActiveSync manager calls IReplStore::GetConflictInfo, passing in a handle for the original desktop object and a handle for the temporary object. ActiveSync provider fills in the CONFINFO structure to customize the description text displayed in the standard conflict resolution dialog. This structure is defined as:
typedef struct tagConfInfo
{
UINT cbStruct;
HREPLFLD hFolder;
HREPLITEM hLocalItem;
HREPLITEM hRemoteItem;
OBJTYPENAME szLocalName;
TCHAR szLocalDesc[ 512 ];
OBJTYPENAME szRemoteName;
TCHAR szRemoteDesc[ 512 ];
} CONFINFO, *PCONFINFO;
In the standard conflict resolution dialog, the user either discards the original desktop object and resynchronizes the device object (device wins), or discards the object from the device and resynchronizes the desktop object (desktop wins). In any case, the IReplObjHandler::DeleteObject is called to delete the temporary object. If the device wins, the original desktop object is marked up-to-date while the device object is still dirty so, in the subsequent sync, the device object will be brought up. If the desktop wins, the device object is marked up-to-date.
In summary, here are the steps taken to resolve a conflict:
- After detecting a conflict, the ActiveSync manager sends down a command to the device ActiveSync provider to read the object. IReplObjHandler::GetPacket is called on the device.
- The packets for the object are brought up and sent to the desktop ActiveSync provider. A temporary object could be created on the desktop. IReplObjHandler::SetPacket is called on the desktop and a new HREPLITEM handle is created and returned to the ActiveSync manager.
- The ActiveSync manager sets the handles for both objects in CONFINFO structure in call to IReplStore::GetConflictInfo. The ActiveSync provider can use these two handles to extract information from the objects and save them in CONFINFO.
- Using the description text returned in the CONFINFO structure, the ActiveSync manager presents the standard conflict resolution dialog to the user.
- IReplObjHandler::DeleteObject is called to delete the temporary object.
- If user chooses to skip, nothing is done and conflict resolution on this item will begin again in the next synchronization.
- If the user chooses device-win, the desktop object is marked up-to-date so the device object can be brought to the desktop.
- If the user chooses desktop-win, the device object is marked up-to-date so the desktop object can be sent to the device later.
The ActiveSync manager sets the RSF_CONFLICT_OBJECT flag in all calls to IReplObjHandler methods.
See Flowchart 5 in the Appendix for an illustration of the conflict resolution process.
There are ways for the ActiveSync provider to avoid the conflict resolution dialog. In the implementation of IReplStore::GetConflictInfo, the following special error values can be returned:
- RERR_IGNORE: using the two given handles in CONFINFO, the ActiveSync provider sees that these two objects are indeed identical, so there is no need to prompt the user to select one or the other. The ActiveSync manager resolves the conflict automatically without touching either the desktop or the device object.
- RERR_DISCARD: the desktop object represented by the handle is already deleted. The ActiveSync manager will issue a command to the device to delete the device object too.
- RERR_DISCARD_LOCAL: the ActiveSync manager resolves the conflict by deleting the desktop object. There may be a special ActiveSync provider that treats some changes on the device objects as deletions to the desktop. So when such device changes cause a conflict, it is preferable to delete the desktop object.
If the ActiveSync provider cannot write a temporary object in the second bullet point above, it can save the packets in memory and return an HREPLITEM that contains the pointer to the memory. To the ActiveSync manager, this handle will be just like another handle of an object. To the ActiveSync provider, this handle is a special one that represents packets of data. The ActiveSync provider needs to make sure this type of handle is properly implemented in all methods in IReplStore that take an HREPLITEM handle (for example, CopyObject, FreeObject). When IReplStore::GetConflictInfo is called, CONFINFO::hRemoteItem will be this special handle. The ActiveSync provider can then extract descriptive text from the handle and save it in CONFINFO.
Implementation of Combine/Discard and Removal of Duplicate Objects
Whenever the ActiveSync manager loses the mapping between the desktop and device objects, it must ask the user to either combine two sets of data or to discard the device data and send all desktop objects down. The following is a list of possible causes that makes this Combine/Discard process necessary:
- User synchronizes a device with existing data for the first time.
- User deletes the device profile with an existing device and reconnects the device to recreate a new partnership.
- User chooses a different desktop store than the one used in last synchronization. This is detected through different store IDs.
- User completes a restore of the device data from a backup file.
- User does a Discard operation on one desktop computer and then synchronizes the device with second desktop computer.
- Repl.dat is corrupted and can not be read.
If user chooses Combine, all objects on both the device and the desktop will be marked as changed. If the user chooses Discard, all objects on the device will be marked deleted and all desktop objects will be marked changed. The normal synchronization takes place immediately after the selection to implement the process. The ActiveSync manager sets RSF_COMBINE in REPLSETUP::dwFlags when each object is to be synchronized. However, if the user cancels the synchronization or disconnects the device during the first synchronization, this flag will not be set again, even though the process can continue properly in a subsequent synchronization.
If the user chooses Combine, there is a good chance that there will be identical objects. For this case, it is highly recommend that an ActiveSync provider implement the IReplStore::RemoveDuplicates function. This method will be called at the end of the first successful synchronization after Combine is chosen. The ActiveSync provider should use this function to find each duplicated object in the desktop store and give the user a choice to remove them. Once duplicated objects are found and removed, the ActiveSync provider can return control to the ActiveSync manager, which in turn starts the enumeration of the store. The ActiveSync manager determines the desktop objects that are deleted and sends commands to the device to remove the corresponding device objects.
A better implementation would avoid duplicated objects in the first place. This is possible if an ActiveSync provider has an efficient way to check when a device object is identical to an existing desktop object. When data for a device object arrives at the desktop, IReplObjHandler::SetPacket is called. If the device object is identical to an existing desktop object, the ActiveSync provider can discard the packets and not write any object in IReplObjHandler::SetPacket. It also needs to tell the ActiveSync manager which desktop object the device object duplicated. It should set the RSF_DUPLICATED_OBJECT flag in the REPLSETUP structure that is passed into the IReplObjHandler::Reset method. Note, do not use the structure passed in IReplObjHandler::Setup. Also, it should set REPLSETUP::hItem to be the handle for the desktop object. All of these are necessary so the ActiveSync manager can establish a mapping between the device and the desktop object.
Setting Synchronization Options
If a device is connected to the desktop and ActiveSync Options is displayed, a user can select a service provider and click on the Options button. The ActiveSync manager calls IReplStore::ActivateDialog so the ActiveSync provider can provide its own user interface to set the options. If the ActiveSync provider does not support the option dialog, it should return E_NOTIMPL in IReplStore::ActivateDialog.
There is no limitation or specification for the user interface. An ActiveSync provider should call IReplNotify::GetWindow to get a window handle that can be used as the parent window of the dialog or the message box.
If the user changes an option that requires the ActiveSync provider to be unloaded and loaded again, IReplStore::ActivateDialog can return RERR_UNLOAD. If the user cancels the option dialog, IReplStore::ActivateDialog should return RERR_CANCEL.
An ActiveSync provider can save its options either in the registry or in the HREPLFLD handle. If options are saved in a HREPLFLD handle, the ActiveSync provider can set default option values in IReplStore::GetFolderInfo, where a new HREPLFLD needs to be created if and only if pointer pointed at by phFolder is NULL. If the pointer is not NULL, the options are already loaded from repl.dat.
If there is no connected device, the user can open the Mobile Device folder, select any device profile, and activate ActiveSync Option. The user can then select an ActiveSync service provider and initiate its UI to set the options. The same IReplStore::ActivateDialog is called. But there is a big difference with setting options for a connected device. If the device is not yet connected, IReplStore::Initialize may not be called as the first method into the store. The ActiveSync provider needs to make sure IReplStore::GetStoreInfo doesn't return an error even when IReplStore::Initialize has been called yet. As mentioned in the earlier section "Initialization and Termination," there are a number of methods that may be called before IReplStore::Initialize in this particular case. You should ensure that each one of these methods still works properly.
.gif)
Figure 5. ActiveSync Options
.gif)
Figure 6. StockPor Synchronization option
Device ActiveSync Provider
The device ActiveSync provider is implemented as a DLL. The Stockpor sample device ActiveSync provider is named stdevs.dll. This DLL exports four functions: InitObjType, ObjectNotify, GetObjTypeInfo, and ReportStatus.
File System Object vs. Synchronized Object
In Windows CE, the file system can have objects, such as files, directories, records, and databases. Each object in the file system is given a unique 32-bit ID. For simple cases, an ActiveSync provider may use this as the ID of the synchronized object. For example, if records in Windows CE databases must be synchronized, the provider can use the file system ID as the object ID and return it to the ActiveSync manager. In a more complicated case, that of a file containing multiple objects, the ActiveSync provider may need to return more than one object ID when the file is changed. In this case you could not use the Windows CE object ID, but would need to generate your own system of IDs.
Your system can be any type of 32-bit numbering, subject to the following restrictions: It must be unique, persistent (that is, it cannot change once it is assigned to an object and cannot be reused for another object), and it must have an order.
Initialization and Termination
InitObjType is called for both initialization and termination of the device ActiveSync provider. If the ActiveSync provider supports synchronization of multiple object types, InitObjType will be called for each object type, with the given lpszObjType not being NULL. When ActiveSync terminates, the given lpszObjType is NULL and the ActiveSync provider should free any resources it may have allocated.
ActiveSync supports the synchronization of two desktop computers with a Windows CE-based device by using a partner bit. The ActiveSync manager passes a partner bit into InitObjType when initializing an ActiveSync provider. This bit will be set to 1 if the connected desktop PC is the first partner and set to 2 if it is the second partner. If an ActiveSync provider uses dirty bits of a synchronized object to check if it is changed or not, it must consider this partner bit when setting or resetting the dirty bits.
Enumeration of Objects
Enumeration of objects on a device is quite different than that on the desktop. When the device is connected, the ActiveSync manager enumerates each file system object (except files in ROM or the \Windows directory) and calls the ObjectNotify function of each ActiveSync provider. The ActiveSync provider decides if it will synchronize the given file system object and, if so, it tells the ActiveSync manager how many synchronized objects are contained in the file system object. This is usually one, but if a file represents many objects it can be greater.
In Windows CE version 2.1, a new function can be added to the device service provider. This function is named FindObjects. Please see the section "What's New in ActiveSync for Windows CE 2.1" for details.
ObjectNotify and Detecting Changes and Deletions
ObjectNotify is called frequently and should be implemented in the most efficient way possible. It should quickly check the information in OBJNOTIFY to see if the call concerns an object type that the ActiveSync provider is interested in. Typically, an ActiveSync provider simply checks the flags and the file system ID given in the OBJNOTIFY structure for the changed file system object.
OBJNOTIFY is defined as follows:
typedef struct tagObjNotify
{
UINT cbStruct; // Input. Size of the structure in bytes.
OBJTYPENAME szObjType; // Input, the object type name
UINT uFlags; // Input, Flags
UINT uPartnerBit; // UNUSED.
CEOID oidObject; // Input. CEOID of the file system object changed/deleted
CEOIDINFO oidInfo; // Input. Information about the file system object
UINT cOidChg; // Output
UINT cOidDel; // Output
UINT *poid; // Output, ActiveSync provider owns the memory
} OBJNOTIFY, *POBJNOTIFY;
The OBJNOTIFY::uFlags have the following definitions:
| Name | Definition |
| ONF_FILE | OBJNOTIFY::oidObject is a file. |
| ONF_DIRECTORY | OBJNOTIFY::oidObject is a directory. |
| ONF_RECORD | OBJNOTIFY::oidObject is a record. |
| ONF_DATABASE | OBJNOTIFY::oidObject is a database. |
| ONF_CHANGED | The file system object is changed. |
| ONF_DELETED | The file system object is deleted. Only oidParent in OBJNOTIFY::oidInfo is defined. All other members in OBJNOTIFY::oidInfo are zero. |
| ONF_CLEAR_CHANGE | The ActiveSync provider should mark the object up-to-date. In this case, OBJNOTIFY::oidObject is the synchronized object ID, not the file system object ID. |
| ONF_CALL_BACK | Set by ActiveSync provider to ask the ActiveSync manager to call back in two seconds. |
| ONF_CALLING_BACK | Two seconds later, the ActiveSync manager sets this flag and calls ObjectNotify. |
If the file system object contains any synchronized object, the ActiveSync provider should set the OBJNOTIFY::poid to point to a list of object IDs. Typically, one file system object maps to one synchronized object so the ActiveSync provider can usually set OBJNOTIFY::poid to be &OBJNOTIFY::oidObject.
The ActiveSync manager calls ObjectNotify in the following cases:
- Immediately after connection. The ActiveSync manager enumerates all file system objects and calls ObjectNotify to get a list of synchronized object IDs from a file system object. Neither ONF_CHANGED nor ONF_DELETED is set. The ActiveSync provider should look at the file system object and determine how many synchronized objects are changed and how many remain up-to-date. It should set OBJNOTIFY::poid to point to a list of object IDs, where the first OBJNOTIFY::cOidChg of them are changed object IDs and the next OBJNOTIFY::cOidDel of them are up-to-date object IDs. Typically, one file system object maps to one synchronized object, so the ActiveSync provider simply sets OBJNOTIFY::poid to be &OBJNOTIFY::oidObject, sets OBJNOTIFY::cOidChg to 1 and sets OBJNOTIFY::cOidDel to 0 if the file system object is changed. Otherwise, it sets OBJNOTIFY::cOidChg to 0 and OBJNOTIFY::cOidDel to 1.
- When a file system object changes, as long as the device is connected, either ONF_CHANGED or ONF_DELETED must be set. The ActiveSync provider should set OBJNOTIFY::cOidChg, OBJNOTIFY::cOidDel and OBJNOTIFY::poid as mentioned previously, except that OBJNOTIFY::cOidDel is the number of deleted synchronized objects.
- After an acknowledgement is received from the desktop that the object has been synchronized successfully. The ActiveSync provider should mark the object up-to-date. ONF_CLEAR_CHANGE is always set in this case. The OBJNOTIFY::oidObject is the synchronized object ID, not the file system object ID. The ActiveSync provider should mark the object as up-to-date so it will not be synchronized until it is changed again.
- ObjectNotify is called two seconds after ONF_CALL_BACK is set in a previous call to ObjectNotify. ONF_CALLING_BACK is set in this case.
Sending and Receiving Objects
Sending and receiving objects is implemented in the same way as for the desktop ActiveSync provider. It uses the same IReplObjHandler interface. All packets received are guaranteed to be in the exact same order and of the exact same size.
Recommended Steps to Develop the ActiveSync Providers
The following is a brief summary of steps you may want to take to design and develop the ActiveSync providers.
- Define how many different types of object you need to synchronize. Name each object type reasonably. If the name coincides with another ActiveSync name, the original service provider will be replaced. It makes sense to develop a single ActiveSync provider to support synchronization of object types that are similar or exist in the same store. For example, one ActiveSync provider is developed to synchronize Appointment, Contact, Task, and Message object types.
- Define the object ID of objects in each object type.
- Determine how objects in each object type can be enumerated.
- Determine how to check whether an object has changed or not. Typically, a time stamp of the last modification is used. A change number (a number incremented by one every time an object is changed) can also be used.
- Define the structure used for HREPLITEM and HREPLFLD. Typically, HREPLITEM is cast into a structure that contains the object ID, the timestamp and any other object specific data. HREPLFLD is cast into a different structure that contains the filter for the object type.
- Define a unique ProgID for the store. Example: MS.WinCE.Outlook. Obtain a GUID for the store.
- Implement various methods of the desktop interfaces and device functions
- The implementation of IReplStore::BytesToObject, IReplStore::ObjectToBytes, IReplStore::CompareItem, and ObjectNotify must be efficient because they are called frequently.
- Read the section "Questions and Answers" and make sure all points are taken care of.
- Compile, configure, and test the ActiveSync service provider.
What's New in ActiveSync for Windows CE 2.1
Windows CE version 2.1 is the operating system used in devices such as the Handheld PC Pro. It has many improvements over Windows CE version 2.0, which is the operating system used by the Handheld PC. For example, you can now create independent database volumes in the internal file system or on a storage card (PC Card or Compact Flash Card).
Each database volume is given a unique CEGUID. Each record in each database is assigned with an object ID. This ID is unique only within the volume.
A new device function, FindObjects, is added to support synchronization of database volumes. Using this function, you can directly enumerate all objects that you want to synchronize and return a list of object IDs to the ActiveSync manager. If you synchronize more than one volume, you need to return multiple lists, one for each volume.
The following is the prototype for FindObjects:
typedef HRESULT (*PFINDOBJECTS)( PFINDOBJINFO );
The following is the definition of FINDOBJINFO structure used by the function:
#define FO_MORE_VOLUME ((UINT)0x00000001)
#define FO_DONE_ONE_VOL ((UINT)0x00000002)
typedef struct tagFindObjInfo
{
UINT uFlags; // See FO_* above
OBJTYPENAME szObjType; // what object type we need to enumerate
UINT *poid; // points to list of object ID's,
// first part is for unchanged objects,
// last part is for changed objects
UINT cUnChg; // # of unchanged object ID's in above list
UINT cChg; // # of changed object ID's in above list
LPBYTE lpbVolumeID; // ID of the volume where all above objects lives.
// NULL if the objects are in RAM
UINT cbVolumeID; // size of above ID in bytes
LPVOID lpvUser; // anything provider wants in this variable
} FINDOBJINFO, *PFINDOBJINFO;
FindObjects is called once after each connection. On first call, the ActiveSync manager sets uFlags to 0. The provider should enumerate all objects it synchronizes and return a list of object IDs, pointed to by poid. The cUnChg tells the ActiveSync manager how many object IDs in the first part of list are for unchanged objects. The cChg tells how many object IDs after that are for changed objects. The cbVolumeID and lpbVolumeID together tell which volume these objects are in.
If there are more objects or volumes to be returned, the service provider should set FO_MORE_VOLUME in the uFlags before returning the call.
After this call is returned, the ActiveSync manager saves the list of object IDs and calls FindObjects again, this time with FO_DONE_ONE_VOL set in uFlags. This allows the service provider to free up any resources used in previous call.
The service provider can return multiple times using the same volume ID. This may be necessary if the service provider can't allocate enough memory for the full list of object IDs.
For detailed information, please study the section "Source Code for StockPor Sample Application."
It's useful to note that volume ID does not have to be the database volume. You can organize your desktop objects so that certain objects belong to one volume and others belong to another volume. The advantage of such a grouping is that, if FindObjects on the device does not return the volume ID of any one volume, the volume will be considered inactive. All changes/deletions made to the desktop objects belonging to that volume will not be synchronized until it becomes active again in the next connection. This support is necessary when a volume of data resides in a PC card or Compact Flash card, in which case we do not want to synchronize the objects of a volume on a card that is not plugged into the device. However, as soon as the card is plugged in again, the volume becomes active, FindObjects returns the object IDs, and any changes made to the desktop objects will be synchronized.
Another new device function added is named SyncData. This function provides an easy and flexible way for the desktop service provider to send and receive data to and from the device.
The following is the prototype for SyncData:
typedef HRESULT (*PSYNCDATA )( PSDREQUEST psd );
The following is the definition of the FINDOBJINFO structure used by the function:
typedef struct SDREQUEST
{
OBJTYPENAME szObjType; // the object type where this data is coming from
BOOL fSet; // TRUE if sending data down and FALSE if getting data up
UINT uCode; // for getting data from the device, this code must be < 8
LPBYTE lpbData;
UINT cbData;
} SDREQUEST, *PSDREQUEST;
A new code is added to IReplNotify::QueryDevice: QDC_SYNC_DATA. The desktop service provider can create an SDREQUEST structure and pass the structure to IreplNotify::QueryDevice. The ActiveSync manager will send the request down and call up the device service provider so it can receive or return the requested data.
See the section "Source Code for StockPor Sample Application" for more details on how to make the call.
Note These two new functions are called only on Windows CE version 2.1 (used by the Handheld PC Pro). If the service provider is installed on earlier versions of Windows CE, none of these functions will be used.
Questions and Answers
Q: How does the ActiveSync manager identify a new desktop object that is created with the data from the device?
A: The implementation of IReplObjHandler::SetPacket must create a new HREPLITEM handle and set it in the hItem member of the REPLSETUP structure passed in the IReplObjHandler::Setup call. Typically, the ActiveSync provider saves the pointer to REPLSETUP during IReplObjHandler::Setup. Note that reading from and writing to the store can take place at the same time. In other words, two calls can be made to IReplObjHandler::Setup before a IReplObjHandler::Reset call. One call to IReplObjHandler::Setup may be for reading and the other call may be for writing, so the ActiveSync provider must keep two pointers to REPLSETUP in its implementation of IReplObjHandler::Setup.
Q: How does the ActiveSync manager identify a new device object that is created with the data from the desktop?
A: The implementation of IReplObjHandler::SetPacket in the device must assign the object ID of the new object to REPLSETUP::oidNew. This ID is sent to the desktop and the ActiveSync manager maintains its mapping with the desktop object.
Q: How do you automatically resolve conflicts and avoid the display of conflict resolution dialog?
A: IReplStore::GetConflictInfo can return special error codes. See the section "Conflict Resolution" for details.
Q: A change on the device may actually be a deletion on the desktop. How can this be implemented?
A: IReplObjHandler::SetPacket can return special error codes. See the section on Sending and Receiving Objects for details.
Q: How does the ActiveSync manager know about desktop store changes in real time?
A: In some cases, it is possible that the user switches the desktop store while the device is connected. For example, in the Stock Portfolio, the user can overwrite the data file with another one that contains a different set of data. In these cases, prompt the user to do a Combine/Discard, because the new store no longer maps to any object on the device. This can be implemented in IReplStore::IsFolderChanged by returning RERR_STORE_REPLACED.
Q: Is it possible to read from and write to the desktop store at the same time?
A: Yes. An ActiveSync provider must keep two pointers pointing to different REPLSETUP structures. However, only one object can be read and one object can be written at the same time. So there is problem with multiple reads or writes.
Q: Can you use transaction to write multiple objects?
A: Yes. If many device objects need to be synchronized to the desktop and if the desktop application supports transaction, it is sometimes desirable—for both performance and reliability—to write multiple objects in a single transaction. The ActiveSync provider can do so by implementing the IReplStore::ReportStatus function such that the transaction is started in RSC_BEGIN_BATCH_WRITE and ended in RSC_END_BATCH_WRITE.
Q: How do you prevent an object being marked as changed after it is written to the desktop store as a result of synchronization?
A: When a device object is completely written into the desktop store, the ActiveSync manager calls IReplStore::UpdateItem. The ActiveSync provider should open the object and update the given HREPLITEM handle with whatever it uses in IReplStore::IsItemChanged—typically a current time stamp or change number. This prevents the object being marked changed again on the desktop.
Q: What if a desktop object is changed again shortly after it is synchronized?
A: When an object is sent to the device, the ActiveSync manager waits for the acknowledgement from the device that this object has been synchronized successfully before it clears the mark that the desktop object is changed. So, if the device has problems writing the object, the synchronization for this object can be tried again in the next synchronization. However, there is a possibility that an object might be changed again before acknowledgement arrives on the desktop. In this case, the ActiveSync manager should keep the object dirty. This is implemented in the IReplStore::IsItemChanged method. The last parameter passed into this method will be NULL in the above case and ActiveSy