Programmer's Guide for ActiveSync

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.

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
 
stockpor.cpp
Source file shared by both desktop and device application.
 
common.h
Header file for structures and constants shared by the application and the ActiveSync provider.
 
resource.h
Header file for resource constants.
 
stockpor.rc
Resource file shared by both desktop and device application.
desktop
for the desktop application
stocks.h
Header file for structures and constants used by desktop application.
 
stockpor.ico
Desktop application icon file.
 
stocks.cpp
The rest of desktop application source code.
desktop\sync
for ActiveSync service provider's desktop component
guids.cpp
Source file for the GUIDs used.
 
stsync.def
Module definition file needed for the desktop DLL.
 
stsync.rc
Resource file.
 
stsync.reg
Registry setup file.
 
mainmod.h
Header file for classes, structures and constants.
 
sthand.cpp
Source file that implements IreplObjHandler.
 
mainmod.cpp
Source file that implements IreplStore.
device
for the device application
stockpor.ico
Device application icon file.
 
stocks.cpp
The rest of device application source code.
 
stocks.h
Header file for structures and constants used by device application.
 
devsetup.cpp
Source file for the device setup application.
device\sync
for ActiveSync service provider's device component
stdevs.def
Module definition file needed for the device DLL.
 
stdevs.h
Header file for structures and constants.
 
stdevs.cpp
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.

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

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.

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:

  1. 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.
  2. 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.
  3. 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.
  4. Using the description text returned in the CONFINFO structure, the ActiveSync manager presents the standard conflict resolution dialog to the user.
  5. IReplObjHandler::DeleteObject is called to delete the temporary object.
  6. If user chooses to skip, nothing is done and conflict resolution on this item will begin again in the next synchronization.
  7. If the user chooses device-win, the desktop object is marked up-to-date so the device object can be brought to the desktop.
  8. 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.

Figure 5. ActiveSync Options

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.

The following is a brief summary of steps you may want to take to design and develop the ActiveSync providers.

  1. 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.
  2. Define the object ID of objects in each object type.
  3. Determine how objects in each object type can be enumerated.
  4. 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.
  5. 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.
  6. Define a unique ProgID for the store. Example: MS.WinCE.Outlook. Obtain a GUID for the store.
  7. Implement various methods of the desktop interfaces and device functions
  8. The implementation of IReplStore::BytesToObject, IReplStore::ObjectToBytes, IReplStore::CompareItem, and ObjectNotify must be efficient because they are called frequently.
  9. Read the section "Questions and Answers" and make sure all points are taken care of.
  10. 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 ActiveSync provider should open the current object and compare the time stamp. If the current object has been changed again, it should return TRUE.

Q: If the synchronization filter defined by an ActiveSync provider is related to the current date, how can the filter be reapplied when date is changed?

A: A typical example of such filter is one that synchronizes only appointments that fall in the next three days. If the device is connected and the date is changed because midnight is just past or the user changed the current date and time, all appointments must be reevaluated against the filter. Whenever such a date change occurs, the ActiveSync manager calls IReplStore::ReportStatus with RSC_DATE_CHANGED for each and every object. An ActiveSync provider typically resets a bit flag in the given HREPLITEM so that when IReplStore::IsItemReplicated is called later on the item, the rule will be reevaluated.

Q: How do you shut down and restart the ActiveSync manager?

A: Running "syncmgr.exe /quit" closes the ActiveSync manager gracefully. Running "syncmgr.exe /show" restarts the ActiveSync manager and makes the ActiveSync status window visible. Running "syncmgr.exe" simply starts the ActiveSync manager without displaying the ActiveSync status window.

Q: Why is the Options button, in the ActiveSync Options dialog, disabled for a disconnected device?

A: A common error is to let IReplStore::GetStoreInfo return any error code when IReplStore::Initialize is not yet called. The correct implementation is to return NOERROR right after every member in STOREINFO, unless the store ID is set. An ActiveSync provider typically sets a flag in IReplStore::Initialize and checks this flag in IReplStore::GetStoreInfo.

Q: If the ActiveSync provider supports real-time notification, why are objects not shown as changed or deleted in the ActiveSync status window right after connection?

A: You may have implemented IReplStore::IsFolderChanged to always set *pfChanged to FALSE. You need to set it to *pfChanged when IReplStore::IsFolderChanged is called the first time. See the section "Enumeration of Objects" for more detail.

Q: If the ActiveSync provider does not support real-time notification, how can changes and deletions of objects be detected?

A: Changes and deletions are detected by enumerating all objects in the store and checking each one to see if it has changed. Old objects that are not enumerated are assumed deleted. This checking process will be started before synchronization starts or it can be started once every specified time interval. This interval is set in microseconds in STOREINFO::uTimerRes. This process uses the same resources as the application and thus can't be started if the application is busy. An ActiveSync provider can set STOREINFO::uTimerRes to –1. Then, whenever the ActiveSync status window gets activated, for instance, when user clicks on the status window, it is reasonable to assume the application that uses the data is not busy and the ActiveSync manager can start the enumeration.

Q: An object is deleted on the device and user deletes the corresponding desktop object. Why does the object keep showing out-of-date?

A: There could be an unusual situation that, while a device is connected, the ActiveSync manager can't detect the deletion. ActiveSync provider should implement IReplStore::IsValidObject to check the object represented by the given handle and, if the object no longer exists, return RERR_OBJECT_DELETED. It can also return RERR_CORRUPT to indicate a bad handle that doesn't represent any object at all.

Q: IReplStore::RemoveDuplicates is called and the ActiveSync provider removes some objects from the desktop store. How can it ask the ActiveSync manager to restart synchronization to pick up those deletions?

A: Returning RERR_RESTART in IReplStore::RemoveDuplicates causes the ActiveSync manager to start the synchronization process again. Returning any other error code causes the ActiveSync manager to call IReplStore::RemoveDuplicates again after the completion of next synchronization.

Q: How do you prevent the ActiveSync manager from displaying the standard "Initialization of %s synchronization service was not successful. Error: %X." error message box?

A: If an ActiveSync provider returns an error in IReplStore::Initialize, the above error message box is displayed with the error code. If the ActiveSync provider prompts the error by itself and doesn't want the ActiveSync manager to display a message box, it should return RERR_NO_ERR_PROPMT in IReplStore::Initialize.

Q: How do you signal that the last (or only) packet has been read when reading an object?

A: Return RWRN_LAST_PACKET in IReplObjHandler::GetPacket.

Q: How does an ActiveSync provider know when synchronization starts or ends?

A: IReplStore::ReportStatus with RSC_BEGIN_SYNC is called when synchronization starts. It's called with RSC_END_SYNC when synchronization ends.

Q: How do you get the error code when an object fails to be written or deleted on the device?

A: IReplStore::ReportStatus with RSC_WRITE_OBJ_FAILED is called after an object fails to be written to the device. It's called with RSC_DELETE_OBJ_FAILED when the object can't be deleted. In both cases, uParam is the HRESULT error code.

Q: How does an ActiveSync provider know whether it is dealing with a connected or a selected device?

A: When IReplStore::Initialize is called, the uFlags is set to ISF_SELECTED_DEVICE if and only if the device is not connected (that is, the user selects a disconnected device profile in Mobile Device folder). If this flag is not set, the device must be connected. An ActiveSync provider can also call IReplNotify::QueryDevice( QDC_CON_DEVICE_KEY, &hKey ). If this call is successful, there is a connected device. Otherwise it is disconnected.

Q: How do you get the registry key where options for an ActiveSync provider can be saved?

A: Call IReplNotify::QueryDevice( QDC_CON_DEVICE_KEY, &hKey ) for a connected device or IReplNotify::QueryDevice( QDC_SEL_DEVICE_KEY, &hKey ) for a selected and disconnected device.

Q: How do you get the device name?

A: Call IReplNotify::QueryDevice( QDC_CON_DEVICE, &devInfo ) for a connected device or IReplNotify::QueryDevice( QDC_SEL_DEVICE, &devInfo ) for a selected and disconnected device. DEVINFO::szName is the name of the device.

Q: What does the desktop ActiveSync provider need to do to support synchronization with multiple devices?

A: Not much. The ActiveSync manager manages different mappings (that is, repl.dat) with different devices. It makes sure the correct data file is loaded for the correct device. All ActiveSync provider needs to do is to make sure any device-specific data or configuration that it uses—which the ActiveSync manager has absolutely no idea about—is saved in the device specific registry key or file folder. The device specific file folder is DEVINFO::szPath, returned by IReplNotify::QueryDevice( QDC_CON_DEVICE, &devInfo ). The device specific registry key is hKey, returned by IReplNotify::QueryDevice( QDC_CON_DEVICE_KEY, &hKey ).

Q: What does the device ActiveSync provider need to do to support synchronization with two desktop computers?

A: The device ActiveSync provider should save the uPartnerBit passed into InitObjType and use it to set or reset dirty bits of a synchronized object.

Q: The reference count of IReplStore never reaches zero and the provider cannot be freed in IReplStore::Release. Why?

A: The ActiveSync provider may have another party holding its reference and thus the ActiveSync manager is not the last one that frees the store (it needs to be.) See the section "Initialization and Termination" for more details.

Q: In IReplObjHandler::SetPacket, how does the desktop ActiveSync provider know if it is a new object or not?

A: RSF_NEW_OBJECT will be set in REPLSETUP::dwFlags given in the IReplObjHandler::Setup call.

Q: In IReplObjHandler::SetPacket, how does the device ActiveSync provider know if an object is new or not?

A: It can call CeGetOidInfo with REPLSETUP::oid, which always fails on new device objects.

Q: During IReplObjHandler::SetPacket on desktop, a different object may be changed or created. How can you tell the ActiveSync manager to synchronize again so this object will be picked up?

A: As an example, when a new contact with a birthday is written into the desktop store, a recurring appointment may be automatically created. If the ActiveSync provider supports real-time notification, this change should be picked up automatically. Otherwise, the ActiveSync provider needs to call IReplNotify::OnItemNotify with the store's ProgID and the object type of the changed/created object. The HREPLITEM can be passed as NULL.

Q: When the device ActiveSync provider is to be terminated, how can it tell the ActiveSync manager to try again later?

A: If the ActiveSync provider can not terminate itself immediately, when InitObjType( NULL, NULL, 0 ) is called, it can return FALSE. The ActiveSync manager will attempt to terminate the provider again in two seconds. It will keep trying for up to 15 minutes. This is necessary for certain ActiveSync providers, which must signal and wait for a working thread to terminate.

Q: The ActiveSync provider supports synchronization of multiple object types. How can the IReplStore::Initialize knows which type is enabled so it does not waste time on disabled object types?

A: Before calling IReplStore::Initialize, ActiveSync calls IReplStore::ReportStatus with RSC_OBJ_TYPE_ENABLED once for each enabled object type and RSC_OBJ_TYPE_DISABLED once for each disabled object type. The HREPLFLD passed into IReplStore::ReportStatus is actually a pointer to the object type name.

Q: In the setup program that installs ActiveSync modules, how can you shut down the ActiveSync manager so it no longer loads the modules?

A: When your setup program is about to install/upgrade ActiveSync modules, it may be necessary to shut down the ActiveSync manager to let it free the existing ActiveSync modules from memory so you can overwrite the modules with your updates. You can shut down the ActiveSync manager by running "syncmgr.exe /quit". You can restart the ActiveSync manager by running either "syncmgr.exe /show", which displays the ActiveSync status window, or simply "syncmgr.exe", which keeps the status window hidden. See the section "Available Command-Line Parameters to syncmgr.exe" in the Appendix for a list of all available command line parameters.

Q: Does the CE File ActiveSync provider support mounted volumes, such as flashcards?

A: Not at present time.

Appendix

List of Released Microsoft ActiveSync Service Providers

The release of Microsoft Windows CE Services version 2.1 provides a number of ActiveSync service providers:

Provider Name Desktop Provider Desktop Provider

Base Address

Device Provider Supported Object Types
Outlook Synchronization outstore.dll 0x22000000 pegobj.dll Appointment

Contacts

Inbox

Task

Schedule+ Synchronization scdstore.dll 0x20000000 pegobj.dll Appointment

Contacts

Task

Windows CE File Synchronization cefstore.dll 0x23000000 cefobj.dll File
Mobile Channels Synchronization aafstore.dll 0x24000000 aafobj.dll Channel

Available Command-Line Parameters to syncmgr.exe

syncmgr.exe is the executable for the ActiveSync manager. It accepts a number of parameters. You can pass more than one parameters to the program. You can also run syncmgr.exe again with parameters even if it is running already. The following is the list of parameters:

Parameter Purpose
/quit Shut down the ActiveSync manager while keeping connection active. This is useful for debugging because all ActiveSync modules will be unloaded. They are reloaded when you run syncmgr.exe again, so you won't need to physically disconnect and reconnect the device.
/show Start the ActiveSync manager if necessary and make the ActiveSync status window visible.
/synconcon Start the ActiveSync manager if necessary and checks for sync-on-connect setting. If it is turned on, start synchronization immediately when device is connected. Without this parameter, the ActiveSync manager does not even check for the sync-on-connect setting.
/syncmgr Start the ActiveSync manager if necessary and display the ActiveSync Options dialog.
/syncnow Start the ActiveSync manager if necessary and synchronizes data immediately.

Registry Values Used by ActiveSync Manager

ActiveSync manager uses a number of registry values during its operation. All these values are under the key:

 HKEY_CURRENT_USER\Software\Microsoft\Windows CE Services\
    Partners\<Device ID>\Services\Synchronization
Value Name Default Value Definition
Conflict Resolution 1 0: Do not prompt for conflict resolution.

1: Prompt for conflict resolution.

Conflict Default Prompt 2 Used if value of "Conflict Resolution" is 1.

0: Desktop object wins.

1: Device object wins.

2: Skip.

Conflict Default No Prompt 0 Same as above, but used only if value of "Conflict Resolution" is 0.
Continuously Update 0 0: Synchronization must be started manually.

1: Synchronization starts automatically when data is changed.

Update on docking 0 0: No synchronization when device is connected.

1: Synchronize immediately after device is connected.

Briefcase Creation N/A The time at which synchronization partnership with the device is first set up, expressed in minutes since 0/0/0 12:00 AM.
Sync Time on Dock 1 0: Do not set device time after connection.

1: Set device time same as desktop after connection.

Remove Dup 0 1: Will call IReplStore::RemoveDuplicates after successful synchronization.

0: No need to call IReplStore::RemoveDuplicates.

Last Checked Date N/A The last date IReplStore::ReportStatus is called with RSC_DATE_CHANGED, expressed in minutes since 0/0/0 12:00 AM.
GetLog Timeout 60 Number of seconds the ActiveSync manager will wait for the list of objects from the device before giving up.
GetObject Timeout 40 Number of seconds the ActiveSync manager will wait for an acknowledgement from the device on an object.
Data File repl.dat Full path name of the synchronization data file.
ResetPartner 0 1: Need to reset the partnership and ask for Combine/Discard

0: No such need.

Enable Sync 1 0: Synchronization is not enabled for all services.

1: Synchronization is enabled.

Flowcharts

Flowchart 1. Initialization of desktop ActiveSync provider

Flowchart 2. Enumeration of desktop objects

Flowchart 3. Synchronization of objects

Flowchart 4. Detect changes to desktop object in real time

Flowchart 5. Conflict resolution

CESYNC.H

The following is the listing for CESYNC.H, which contains declaration of various interfaces and device functions:

/*++

Copyright (c) 1996-1997, Microsoft Corporation

Module Name:


    cesync.h

Abstract:

    Include file for synchronization modules for Windows CE

--*/
#ifndef _INC_CESYNC_H
#define _INC_CESYNC_H

// max size of the object type name
#define MAX_OBJTYPE_NAME    100

// max. size of a packet in IReplObjHandler::GetPacket & IReplObjHandler::SetPacket (about 254K)
#define MAX_PACKET_SIZE     260000

#define MAX_ACTIVE_VOL      16                  // up to 16 active volumes (including the default system volume) can be synchronized during each connection

typedef struct _tagReplSetup *PREPLSETUP;

typedef TCHAR   OBJTYPENAME[ MAX_OBJTYPE_NAME ];
typedef char    OBJTYPENAMEA[ MAX_OBJTYPE_NAME ];
typedef WCHAR   OBJTYPENAMEW[ MAX_OBJTYPE_NAME ];

#define FACILITY_CESYNC     0x14
#define MAKE_RERR(code)     ((HRESULT)(MAKE_SCODE( SEVERITY_ERROR, FACILITY_CESYNC, code )))
#define MAKE_RWRN(code)     ((HRESULT)(MAKE_SCODE( SEVERITY_SUCCESS, FACILITY_CESYNC, code )))

#ifndef UNDER_CE
typedef struct _REPLOBJ FAR *HREPLOBJ;
typedef struct _REPLITEM FAR *HREPLITEM;
typedef struct _REPLFLD FAR *HREPLFLD;

#endif

// Error/Return code used
#define RERR_SHUT_DOWN      MAKE_RERR( 0x0001 ) // serious error, asking implementation to shut down immediately
#define RERR_STORE_REPLACED MAKE_RERR( 0x0002 ) // the store was replaced.
#define RERR_CANCEL         MAKE_RERR( 0x0003 ) // user cancel the operation
#define RERR_RESTART        MAKE_RERR( 0x0004 ) // restart the operation, applicable in RSC_END_SYNC & RSC_END_CHECK
#define RERR_IGNORE         MAKE_RERR( 0x0005 ) // used by IReplStore::GetConflictInfo.
#define RERR_UNLOAD         MAKE_RERR( 0x0006 ) // used by IReplStore::ActivateDialog or IReplStore::IsFolderChanged to request unloading of replication modules 
#define RERR_OBJECT_DELETED MAKE_RERR( 0x0007 ) // used by IReplStore::IsValidObject, indicates the object identified by the hObject is deleted
#define RERR_CORRUPT        MAKE_RERR( 0x0008 ) // used by IReplStore::IsValidObject, indicates the object identified by the hObject is corrupted
#define RERR_NO_DEVICE      MAKE_RERR( 0x0009 ) // returned by IReplNotify::QueryDevice. indicates no selected or connected device exists
#define RERR_NO_ERR_PROMPT  MAKE_RERR( 0x0010 ) // returned by IReplStore::Initialize. indicates error initializing. No UI is needed to show this error.
#define RERR_DISCARD        MAKE_RERR( 0x0011 ) // returned by IReplObjHandler::SetPacket. indicates this object should be discarded from the device immediately.
#define RERR_DISCARD_LOCAL  MAKE_RERR( 0x0012 ) // returned by IReplObjHandler::SetPaket. indicates this object should be discarded from the desktop only.
#define RERR_VOL_INACTIVE   MAKE_RERR( 0x0013 ) // returned by IReplObjHandler::GetPacket && IReplObjHandler::SetPacket, the volume has become inactive.
#define RERR_BIG_OBJ_TYPE   MAKE_RERR( 0x0014 ) // returned by IReplNotify::QueryDevice on QDC_SYNC_DATA
#define RERR_BIG_CODE       MAKE_RERR( 0x0015 ) // returned by IReplNotify::QueryDevice on QDC_SYNC_DATA
#define RERR_UNMATCHED      MAKE_RERR( 0x0016 ) // returned by IReplNotify::QueryDevice on QDC_SYNC_DATA
#define RERR_DEVICE_WIN     MAKE_RERR( 0x0017 ) // returned by IReplStore::GetConflictInfo, resolve the conflict so device object wins
#define RERR_DESKTOP_WIN    MAKE_RERR( 0x0018 ) // returned by IReplStore::GetConflictInfo, resolve the conflict so desktop object wins
#define RERR_SKIP_ALL_OBJ   MAKE_RERR( 0x0019 ) // returned by IReplStore::ReportStatus on RSC_WRITE_OBJ_FAILED, skip sync of all remaining objects

// use by IReplObjHandler
#define RERR_SKIP_ALL       MAKE_RERR( 0x0100 )  // skip all incoming packets because of write errors
#define RERR_BAD_OBJECT     MAKE_RERR( 0x0101 )  // this is a bad object because of read error, server should not try to replicate it again
#define RERR_TRY_AGAIN      MAKE_RERR( 0x0102 )  // this is a bad object because of read error, server should can try to replicate it again later
#define RERR_USER_SKIP      MAKE_RERR( 0x0103 )  // object skipped by the user

// these are warning codes
#define RWRN_LAST_PACKET    MAKE_RWRN( 0x0001 )

// flags used in RSC_BEGIN_SYNC
#define BSF_AUTO_SYNC           ((UINT)0x00000001)  // sync get started as a result of changes while auto. sync on change is turned on
#define BSF_REMOTE_SYNC         ((UINT)0x00000002)  // consistent with RSC_REMOTE_SYNC, set if we are sync'ing remotely
#define BSF_RESERVED            ((UINT)0x80000000)  // Reserved by ActiveSync server.

// Code for ReportStatus
#define RSC_BEGIN_SYNC          ((UINT)1)   // Synchronization is about to start, uReserved is combination of bit flags, see BSF_* above
#define RSC_END_SYNC            ((UINT)2)   // Synchronization is about to end
#define RSC_BEGIN_CHECK         ((UINT)3)   // FindFirstItem is about to be called, followed by FindNextItem
#define RSC_END_CHECK           ((UINT)4)   // FindItemClose has been called
#define RSC_DATE_CHANGED        ((UINT)5)   // System Date has changed, this is called for each known desktop object
#define RSC_RELEASE             ((UINT)6)   // Replication is about to release the store
#define RSC_REMOTE_SYNC         ((UINT)7)   // Indicates if remote sync is enabled. uParam will TRUE if all sync 
                                            // will be remote until this status is reported again with uParam set to FALSE
#define RSC_INTERRUPT           ((UINT)8)   // interrupt current operation
#define RSC_BEGIN_SYNC_OBJ      ((UINT)9)   // Synchronization is about to start on an object type. uReserved points to 
#define RSC_END_SYNC_OBJ        ((UINT)10)  // Synchronization is about to end on an object type.
#define RSC_OBJ_TYPE_ENABLED    ((UINT)11)  // Synchronization of the given object is enabled, hFolder is indeed a pointer to a string (object type name)
#define RSC_OBJ_TYPE_DISABLED   ((UINT)12)  // Synchronization of the given object is disabled, hFolder is indeed a pointer to a string (object type name)
#define RSC_BEGIN_BATCH_WRITE   ((UINT)13)  // A series of SetPacket will be called on a number of objects, this is the right time for some service providers to start a transaction
#define RSC_END_BATCH_WRITE     ((UINT)14)  // above write ends, this is the right time for some service providers to commit the transaction
#define RSC_CONNECTION_CHG      ((UINT)15)  // connection status has changed. uParam is TRUE if connection established. FALSE otherwise.
#define RSC_WRITE_OBJ_FAILED    ((UINT)16)  // failed writing an object on the device. uParam is the HRESULT code.
#define RSC_DELETE_OBJ_FAILED   ((UINT)17)  // failed deleting an object on the device. uParam is the HRESULT code.
#define RSC_WRITE_OBJ_SUCCESS   ((UINT)18)  // writing of an object succeeded on the device. uParam is a pointer to SDREQUEST (with (lpbData, cbData) representing the volume ID)
#define RSC_DELETE_OBJ_SUCCESS  ((UINT)19)  // deletion of an object succeeded on the device. uParam is a pointer to SDREQUEST (with (lpbData, cbData) representing the volume ID)
#define RSC_READ_OBJ_FAILED     ((UINT)20)  // failed to read an object from the device. uParam is the HRESULT code
#define RSC_TIME_CHANGED        ((UINT)21)  // System time has changed, this is called only once.

//
//========================= IReplNotify ==============================
//

typedef struct tagDevInfo
{
    DWORD   pid;                // device ID
    char    szName[ MAX_PATH ]; // device name
    char    szType[ 80 ];       // device type
    char    szPath[ MAX_PATH ]; // device path
} DEVINFO, *PDEVINFO;

// a structure used to get/set custom sync. data from/to the device
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 less than 8
    LPBYTE      lpbData;
    UINT        cbData;
} SDREQUEST, *PSDREQUEST;

// code for QueryDevice
#define QDC_SEL_DEVICE      1   // Selected device info, *ppvData points to DEVINFO
#define QDC_CON_DEVICE      2   // Connected device info, *ppvData points to DEVINFO
#define QDC_SEL_DEVICE_KEY  3   // get a registry key that can be used to store selected device specific settings. 
                                // *ppvData points to HKEY, caller must close reg key when its usage is over
#define QDC_CON_DEVICE_KEY  4   // get a registry key that can be used to store connnected device specific settings. 
                                // *ppvData points to HKEY, caller must close reg key when its usage is over
#define QDC_SYNC_DATA   5       // get or set custom sync data from the device, *ppvData points to SDREQUEST

#define INF_OVERRIDE        ((UINT)0x0001000)   // used for OnItemNotify, overrid3 the default action of "delete wins over change"

#undef  INTERFACE
#define INTERFACE   IReplNotify
DECLARE_INTERFACE_( IReplNotify, IUnknown )
{
#ifndef UNDER_CE
    STDMETHOD(       SetStatusText)     ( THIS_ LPSTR lpszText ) PURE;
    STDMETHOD_(HWND, GetWindow)         ( THIS_ UINT uFlags ) PURE;
    STDMETHOD(       OnItemNotify )     ( THIS_ UINT uCode, LPSTR lpszProgId, LPSTR lpszName, HREPLITEM hItem, ULONG ulFlags ) PURE;
    STDMETHOD(       QueryDevice )      ( THIS_ UINT uCode, LPVOID *ppvData ) PURE;
#endif

    // Internal use only
    STDMETHOD(       OnItemCompleted )  ( THIS_ PREPLSETUP pSetup ) PURE;
};

#define RNC_CREATED     1
#define RNC_MODIFIED    2
#define RNC_DELETED     3
#define RNC_SHUTDOWN    4


#ifndef UNDER_CE

#define SCF_SINGLE_THREAD   ((UINT)0x00000001)  // set if the implementation only supports single thread operation.
#define SCF_SIMULATE_RTS    ((UINT)0x00000002)  // set if the implementation wants to simulate detection of real-time change/deletes

typedef struct tagStoreInfo
{
    UINT    cbStruct;               // Size of this structure
    UINT    uFlags;                 // Miscelleanous flags on the store, see SCF_xxx above
    TCHAR   szProgId[ 256 ];        // Output, ProgID name of the store object
    TCHAR   szStoreDesc[ 200 ];     // Output, description of the store, will be displayed to the user
    UINT    uTimerRes;              // Input/Output, resolution of timer in micro-seconds. 5000 by default.

    UINT    cbMaxStoreId;           // Input, max. size of the store ID that can be stored in buffer pointed by lpbStoreId.
    UINT    cbStoreId;              // Output, actual size of the store ID stored in buffer pointed by lpbStoreId
    LPBYTE  lpbStoreId;             // Output pointer to a buffer of anything that uniquely
                                    // identifies the current store instance (Eg. a schedule file)
} STOREINFO, *PSTOREINFO;

typedef struct tagObjUIData
{
    UINT        cbStruct;               // size of this structure
    HICON       hIconLarge;             // Handle of a large icon used in the list view display in Synchronization Status
    HICON       hIconSmall;             // Handle of a small icon used in the list view display in Synchronization Status
    char        szName[ MAX_PATH ];     // Text displayed in the "Name" column in Synchronization Status
    char        szSyncText[ MAX_PATH ]; // Text displayed in the  "Sync Copy In" column in Synchronization Status
    char        szTypeText[ 80 ];       // Text displayed in the  "Type" column in Synchronization Status
    char        szPlTypeText[ 80 ];     // Plural form of text displayed in the  "Type" column in Synchronization Status
} OBJUIDATA, *POBJUIDATA;

enum ReplDialogs
{
    OPTIONS_DIALOG,
};

//
//========================= IEnumReplItem ==============================
//
DEFINE_GUID( IID_IEnumReplItem,              /* a417bc0e-7be1-11ce-ad82-00aa006ec559 */
    0xa417bc0e,
    0x7be1,
    0x11ce,
    0xad, 0x82, 0x00, 0xaa, 0x00, 0x6e, 0xc5, 0x59
);

#undef  INTERFACE
#define INTERFACE   IEnumReplItem
DECLARE_INTERFACE_( IEnumReplItem, IUnknown )
{
    STDMETHOD(Next)                         ( THIS_ ULONG celt, HREPLITEM *phItem, ULONG *pceltFetched ) PURE;
    STDMETHOD(Skip)                         ( THIS_ ULONG celt ) PURE;
    STDMETHOD(Reset)                        ( THIS ) PURE;
    STDMETHOD(Clone)                        ( THIS_ IEnumReplItem **ppenum ) PURE;
    STDMETHOD_( HREPLFLD, GetFolderHandle)  ( THIS ) PURE;
};

typedef struct tagConfInfo
{
    UINT        cbStruct;
    HREPLFLD    hFolder;
    HREPLITEM   hLocalItem;
    HREPLITEM   hRemoteItem;

    OBJTYPENAME szLocalName;
    TCHAR       szLocalDesc[ 512 ];

    OBJTYPENAME szRemoteName;
    TCHAR       szRemoteDesc[ 512 ];
} CONFINFO, *PCONFINFO;

// flags for uParam of IReplStore::ReportStatus 
#define PSA_RESET_INTERRUPT ((UINT)0x00000001)  // this flag is set if we're clearing the interrupt state (ie. we go back to normal operation)
#define PSA_SYS_SHUTDOWN    ((UINT)0x00000002)  // Windows is shutting down

// Actions for Setup 
#define RSTP_SETUP          ((WORD)0x0001)  // New setup
#define RSTP_CREATE         ((WORD)0x0002)  // New profile
#define RSTP_RENAME         ((WORD)0x0003)  // Rename profile
#define RSTP_DELETE         ((WORD)0x0004)  // Delete profile

//========================= IReplSetup ==============================
//

DEFINE_GUID( IID_IReplSetup, /* 60178ec0-c670-11d0-837a-0000f80220b9 */
    0x60178ec0,
    0xc670,
    0x11d0,
    0x83, 0x7a, 0x00, 0x00, 0xf8, 0x02, 0x20, 0xb9
);

#undef  INTERFACE
#define INTERFACE   IReplSetup
DECLARE_INTERFACE_( IReplSetup, IUnknown )
{
    // *** IReplSetup methods ***
    STDMETHOD(          Setup )             ( THIS_ HWND hwndParent, DWORD dwDeviceId, WORD wAction ) PURE;
};

//
//========================= IReplStore ==============================
//
DEFINE_GUID (IID_IReplStore,            // a417bc0f-7be1-11ce-ad82-00aa006ec559
    0xa417bc0f,
    0x7be1,
    0x11ce,
    0xad, 0x82, 0x00, 0xaa, 0x00, 0x6e, 0xc5, 0x59
);

// Flags for Initialize
#define ISF_SELECTED_DEVICE     ((UINT)0x00000001)  // set if the store is initialized for selected device
                                                    // otherwise it's initialized for connected device
#define ISF_REMOTE_CONNECTED    ((UINT)0x00000002)  // set if the store is initialized during remote connection, all UI should be suppressed.

#undef  INTERFACE
#define INTERFACE   IReplStore
DECLARE_INTERFACE_( IReplStore, IUnknown )
{
    // *** IReplStore methods ***
    STDMETHOD(          Initialize )        ( THIS_ IReplNotify *pNotify, UINT uFlags ) PURE;
    STDMETHOD(          GetStoreInfo )      ( THIS_ PSTOREINFO pStoreInfo ) PURE;
    STDMETHOD(          ReportStatus )      ( THIS_ HREPLFLD hFld, HREPLITEM hItem, UINT uStatus, UINT uParam ) PURE;
    STDMETHOD_( int,    CompareStoreIDs)    ( THIS_ LPBYTE, UINT, LPBYTE, UINT ) PURE;

    // Item related routines
    STDMETHOD_( int,    CompareItem )       ( THIS_ HREPLITEM hItem1, HREPLITEM hItem2 ) PURE;
    STDMETHOD_( BOOL,   IsItemChanged)      ( THIS_ HREPLFLD hFld, HREPLITEM hItem, HREPLITEM hItemComp ) PURE;
    STDMETHOD_( BOOL,   IsItemReplicated )  ( THIS_ HREPLFLD hFld, HREPLITEM hItem ) PURE;
    STDMETHOD_( void,   UpdateItem )        ( THIS_ HREPLFLD hFld, HREPLITEM hItemDst, HREPLITEM hItemSrc ) PURE;

    // Folder related routines
    STDMETHOD(          GetFolderInfo )     ( THIS_ LPSTR lpszObjType, HREPLFLD *phFld, IUnknown ** ) PURE;
    STDMETHOD(          IsFolderChanged )   ( THIS_ HREPLFLD hFld, BOOL *pfChanged ) PURE;

    // Enumeration of folders
    STDMETHOD(          FindFirstItem )     ( THIS_ HREPLFLD hFld,  HREPLITEM *phItem, BOOL *pfExist ) PURE;   // get first object the folder
    STDMETHOD(          FindNextItem )      ( THIS_ HREPLFLD hFld,  HREPLITEM *phItem, BOOL *pfExist ) PURE;   // get next object the folder
    STDMETHOD(          FindItemClose )     ( THIS_ HREPLFLD hFld ) PURE;                   // done enumerating

    // Object management routines
    STDMETHOD_(UINT,    ObjectToBytes )     ( THIS_ HREPLOBJ hObject, LPBYTE lpb ) PURE;
    STDMETHOD_(HREPLOBJ,BytesToObject )     ( THIS_ LPBYTE lpb, UINT cb ) PURE;
    STDMETHOD_(void,    FreeObject )        ( THIS_ HREPLOBJ hObject ) PURE;
    STDMETHOD_(BOOL,    CopyObject )        ( THIS_ HREPLOBJ hObjSrc, HREPLOBJ hObjDest ) PURE;
    STDMETHOD(          IsValidObject )     ( THIS_ HREPLFLD hFld, HREPLITEM hObject, UINT uFlags ) PURE;

    // UI related routines
    STDMETHOD(          ActivateDialog)     ( THIS_ UINT uidDialog, HWND hwndParent, HREPLFLD hFld, IEnumReplItem *penumItem ) PURE;
    STDMETHOD(          GetObjTypeUIData)   ( THIS_ HREPLFLD hFld, POBJUIDATA pData ) PURE;
    STDMETHOD(          GetConflictInfo )   ( THIS_ PCONFINFO pConfInfo ) PURE;
    STDMETHOD(          RemoveDuplicates )  ( THIS_ LPSTR lpszObjType, UINT uFlags ) PURE;
};
#endif

//
//=========== Section for object serializing & deserializing interfaces ==========
//
#define RSF_CONFLICT_OBJECT             0x00000001  // this is about getting/writting a conflicting object
#define RSF_NEW_OBJECT                  0x00000002  // this is a new object to be written
#define RSF_DUPLICATED_OBJECT           0x00000004  // the object is an exact duplicate of an existing object
#define RSF_COMBINE                     0x00000008  // the object is being writen to desktop during a combine operation
#define RSF_SYNC_DEVICE_ONLY            0x00000010  // the object should be sync'ed from device to desktop only
#define RSF_SYNC_DESKTOP_ONLY           0x00000020  // the object should be sync'ed from desktop to device only
#define RSF_UPDATED_HANDLE              0x00000040  // this is a new object, but the oid already exists (eg, file rename)
#define RSF_DISCARDED_OBJ               0x00000080  // used in DeleteObj. indicates the object is deleted as a result of RERR_DISCARD being returned by SetPacket
#define RSF_NEW_VOLUME                  0x00000100  // used by ActiveSync manager only.

#define RSF_RESERVED1                   0x00100000  // reserved by ActiveSync manager
#define RSF_RESERVED2                   0x00200000
#define RSF_RESERVED3                   0x00400000
#define RSF_RESERVED4                   0x00800000

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

    LPBYTE  lpbVolumeID;    // ID of the volume for the object. NULL if the object is in the default volume
    UINT    cbVolumeID;     // size of above ID in bytes
} REPLSETUP, *PREPLSETUP;


//========================= IReplObjHandler ==============================
//
// Specifies the interface for replication object handler 
// (object serializer/deserializer)
#undef  INTERFACE
#define INTERFACE   IReplObjHandler
DECLARE_INTERFACE_( IReplObjHandler, IUnknown )
{
    //  Called everytime when an object is about to be serialized/deserialized
    STDMETHOD( Setup )  ( THIS_ PREPLSETUP pSetup ) PURE;

    //  Called everytime when it's the time to clean up the serializer/deserializer for the object
    STDMETHOD( Reset ) ( THIS_ PREPLSETUP pSetup ) PURE;

    /* A request to get a data packet (serialize the object)
    handler should pass back the buffer along with the size bytes */
    STDMETHOD( GetPacket )(  THIS_ LPBYTE *lppbData,  DWORD *pcbData, DWORD cbRecommend ) PURE;

    /* A request to set a data packet (deserialize the byte stream) */
    STDMETHOD( SetPacket )( THIS_ LPBYTE lpbData, DWORD cbData ) PURE;

    /* A request to delete the given object */
    STDMETHOD( DeleteObj )( THIS_ PREPLSETUP pSetup ) PURE;
};

typedef struct tagObjTypeInfo
{
    UINT            cbStruct;       // Input. Size of the structure in bytes.
    OBJTYPENAMEW    szObjType;      // Input, the object type name
    UINT            uFlags;         // Reserved. Not in use yet.
    WCHAR           szName[ 80 ];   // Output, the name of a file system object storing all these object
    UINT            cObjects;       // Output, number of existing objects
    UINT            cbAllObj;       // Output, total number of bytes used to store existing objects
    FILETIME        ftLastModified; // Output, last time any object is modified
} OBJTYPEINFO, *POBJTYPEINFO;

#ifdef UNDER_CE

#define ONF_FILE            ((UINT)0x00000001)
#define ONF_DIRECTORY       ((UINT)0x00000002)
#define ONF_DATABASE        ((UINT)0x00000004)
#define ONF_RECORD          ((UINT)0x00000008)

#define ONF_CHANGED         ((UINT)0x00000010)  // set if the file system object is changed
#define ONF_DELETED         ((UINT)0x00000020)  // set if the file system object is deleted

#define ONF_CLEAR_CHANGE    ((UINT)0x00000040)  // client should clear the change bit for the object whose object id is pointed at by poid
#define ONF_CALL_BACK       ((UINT)0x00000080)  // Output, client asks server to call ObjectNotify 2 sec. later.
#define ONF_CALLING_BACK    ((UINT)0x00000100)  // set if this call is a result of ONF_CALL_BACK being set earlier

/*  Definitions of cOidChg, cOidDel and poid
    in all cases, poid points to a list of object id's

1) when ONF_CHANGED is set, cOidChg is the number of object id's in the list that should be synchronized. cOidDel is not used
2) when ONF_DELETED is set, cOidChg is not used, cOidDel is the number of deleted object id's in the list that should be synchronized
3) when both ONF_CHANGED & ONF_DELETED is not set,
    cOidChg is count of object id's in the first part of the list for objects that are changed
    cOidDel is count of object id's in the later part of the list for objects that are not changed
    
*/

typedef struct tagObjNotify
{
    UINT        cbStruct;       // Input. Size of the structure in bytes.
    OBJTYPENAME szObjType;      // Input, the object type name
    UINT        uFlags;         // Input, Flags, see ONF_xxx above
    UINT        uPartnerBit;    // Input, which partner this 
    CEOID       oidObject;      // Input. CEOID of the file system object changed/deleted
    CEOIDINFO   oidInfo;        // Input. Information about the file system object

    UINT        cOidChg;        // Output, see above comment for definition.
    UINT        cOidDel;        // Output, see above comment for definition.
    UINT        *poid;          // Output, see above comment for definition.
                                // Note that, memory pointed to by this pointer is owned by the clients. 
                                // It will not be freed by replication.
    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
} OBJNOTIFY, *POBJNOTIFY;

#define FO_MORE_VOLUME      ((UINT)0x00000001)  // set by ActiveSync module. there are more volumes of objects
#define FO_DONE_ONE_VOL     ((UINT)0x00000002)  // set by ActiveSync manager, let ActiveSync module to free up the memory allocated in FINDOBJINFO

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;        // an ActiveSync module can save anything it wants in this variable
} FINDOBJINFO, *PFINDOBJINFO;

#ifdef __cplusplus
extern "C"{
#endif 

// Functions exported by client's device module

// for Function: InitObjType
typedef BOOL (*PINITOBJPROC)( LPWSTR lpszObjType, IReplObjHandler **ppObjHandler, UINT uPartnerBit );

// for Function: ObjectNofity
typedef BOOL (*POBJNOTIFYPROC)( POBJNOTIFY );

// for Function: GetObjTypeInfo
typedef BOOL (*PGETOBJTYPEINFO)( POBJTYPEINFO );

// for Function: ReportStatus
typedef BOOL (*PREPORTSTATUS)( LPWSTR lpszObjType, UINT uCode, UINT uParam );

// for Function: FindObjects
typedef HRESULT (*PFINDOBJECTS)( PFINDOBJINFO );

// for Function: SyncData
typedef HRESULT (*PSYNCDATA )( PSDREQUEST psd );

#ifdef __cplusplus
}
#endif

#endif  // UNDER_CE

#define SZ_OUTSTORE_PROG_ID     TEXT( "MS.WinCE.OutLook" )
#define SZ_SCDSTORE_PROG_ID     TEXT( "MS.WinCE.SchedulePlus" )

#define SZ_APPT                 TEXT( "Appointment" )
#define SZ_CONTACT              TEXT( "Contact" )
#define SZ_TASK                 TEXT( "Task" )
#define SZ_FILE                 TEXT( "File" )
#define SZ_INBOX                TEXT( "Inbox" )

#endif  // _INC_CESYNC_H

Source Code for StockPor Sample Application

These source codes are provided here for your reference. They are the same code that is included in the Windows CE Software Development Kit.

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright (C) 1993-1997 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

common.h—Common header file used by all source code

#define Dim( s )                ( sizeof( s ) / sizeof( (s)[0] ) )
#define ClearStruct( s )        memset( &(s), 0, sizeof( s ) );

// special Window messages
#define WM_DATA_CHANGED         ( WM_USER + 101 )

#define SZ_STOCKPOR     TEXT( "StockPor" )
#define SZ_WND_CLASS    TEXT( "StockPortSample" )
#define SZ_REG_ROOT     TEXT( "Software\\Microsoft\\Stock Portfolio" )

// Common values shared by the app and the sync module
#ifdef UNDER_CE

extern LPTSTR   v_lpszDBVol;

    #define DBTYPE_STOCKPOR         21238
    #define SZ_DEFAULT_PORTFILE     TEXT( "\\StockPor.DB" )
    #define SZ_REG_DBVOL            TEXT( "DBVol" )

    // bit mask used in the flag field of each stock record
    #define SF_CHANGED1     ((UINT)0x00000001)  // change to record hasn't be sync'ed with first PC partner, must be valued at 1
    #define SF_CHANGED2     ((UINT)0x00000002)  // change to record hasn't be sync'ed with second PC partner, must be valued at 2
    #define SF_CHG_IN_SYNC  ((UINT)0x00000004)  // this object is changed again during sync
    #define SF_UPDATE_VIEW  ((UINT)0x00000008)

    #define PROP_TAG( ulPropType, ulPropID )    ((((ULONG)(ulPropID))<<16)|((ULONG)(ulPropType)))
    #define PROP_TYPE(ulPropTag)                (((ULONG)(ulPropTag))&0x0000FFFF)

    // property tags used in the stock record
    #define HHPR_FLAGS      PROP_TAG( CEVT_UI2,        0x8200 )
    #define HHPR_SYMBOL     PROP_TAG( CEVT_LPWSTR,     0x8201 )
    #define HHPR_COMPANY    PROP_TAG( CEVT_LPWSTR,     0x8202 )
    #define HHPR_PRICE      PROP_TAG( CEVT_LPWSTR,     0x8203 )
    #define HHPR_PUR_DATE   PROP_TAG( CEVT_LPWSTR,     0x8204 )
    #define HHPR_PUR_PRICE  PROP_TAG( CEVT_LPWSTR,     0x8205 )
    #define HHPR_GAIN_LOSS  PROP_TAG( CEVT_LPWSTR,     0x8206 )
    #define HHPR_UP_TIME    PROP_TAG( CEVT_FILETIME,   0x8207 )
#else
    #define PORTFILE_VERSION       0x324FE323
    #define MAX_STOCKS             500            // support up to 500 stocks in the portfolio
    #define MUTEX_TIMEOUT          5000
    #define SZ_MUTEX                "StockPortMutex"
    #define SZ_CHANGE_EVENT         "StockPorChange"
    #define SZ_DEFAULT_PORTFILE     "c:\\demo.por"
    #define SZ_STORE_PROG_ID        "MS.WinCE.StockPor2"

    #define SF_IN_VIEW      ((UINT)0x00000001)

    // structure to store each stock's information
    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;

    // structure of the stock portfolio file (which is followed by array of STOCK)
    typedef struct tagPortFile
    {
       UINT      uVer1;         // must be equal to FILE_VERSION
       UINT      uidCurrStock;   // current stock ID

        // change/delete logs used by the sync module
        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;

#endif

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

resource.h—Define resources symbols

#ifndef IDC_STATIC
    #define IDC_STATIC -1
#endif

#define IDR_MAIN_MENU                   100
#define IDC_VIEW                        1000
#define IDC_SYMBOL                      1001
#define IDC_COMPANY                     1002
#define IDC_LAST_PRICE                  1003
#define IDC_PUR_DATE                    1004
#define IDC_GAIN_LOSS                   1005
#define IDC_PUR_PRICE                   1006
#define IDC_ADD_CHG                     1007
#define IDC_FILE                        1008
#define IDC_SYNC_NOW                    1009
#define IDC_DB_VOL                      1010

#define IDC_SYNC_ALL                    1100
#define IDC_SYNC_AM                     1101
#define IDC_SYNC_NZ                     1102

#define IDC_OPEN                        40001
#define IDC_ADD                         40002
#define IDC_CHANGE                      40003
#define IDC_DELETE                      40004

#define IDI_ICON                        200

stockpor.rc—For resources used in both desktop and device application

#include <windows.h>
#include "resource.h"

#ifdef _WIN32_WCE
IDI_ICON                ICON    DISCARDABLE     "device\stockpor.ico"
#else
IDI_ICON                ICON    DISCARDABLE     "desktop\stockpor.ico"
#endif

IDR_MAIN_MENU MENU DISCARDABLE 
BEGIN
#ifndef _WIN32_WCE
    MENUITEM "&Open",                       IDC_OPEN
#endif
    MENUITEM "&Add Stock",                  IDC_ADD
    MENUITEM "&Change Stock",               IDC_CHANGE, GRAYED
    MENUITEM "&Delete Stock",               IDC_DELETE, GRAYED
#ifndef _WIN32_WCE
    MENUITEM "&Synchronize",                IDC_SYNC_NOW
#endif
END

ADD_CHANGE DIALOG DISCARDABLE  0, 0, 200, 100
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Add/Change Stock"
BEGIN
    LTEXT           "&Symbol:",IDC_STATIC,7,10,40,8
    EDITTEXT        IDC_SYMBOL,54,7,81,12,ES_UPPERCASE | ES_AUTOHSCROLL
    LTEXT           "C&ompany:",IDC_STATIC,7,25,40,8
    EDITTEXT        IDC_COMPANY,54,22,81,12,ES_AUTOHSCROLL
    LTEXT           "&Last Price:",IDC_STATIC,7,40,40,8
    EDITTEXT        IDC_LAST_PRICE,54,37,81,12,ES_AUTOHSCROLL
    LTEXT           "Pur. &Date:",IDC_STATIC,7,55,40,8
    EDITTEXT        IDC_PUR_DATE,54,52,81,12,ES_AUTOHSCROLL
    LTEXT           "Pur. &Price:",IDC_STATIC,7,70,40,8
    EDITTEXT        IDC_PUR_PRICE,54,67,81,12,ES_AUTOHSCROLL
    LTEXT           "&Gain/Loss:",IDC_STATIC,7,85,40,8
    EDITTEXT        IDC_GAIN_LOSS,54,82,81,12,ES_AUTOHSCROLL | ES_READONLY
    DEFPUSHBUTTON   "&Add",IDC_ADD_CHG,140,6,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,140,23,50,14
END

stockpor.cpp—Shared application code for both desktop and device

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#include "resource.h"
#include "common.h"

// define class CStocks
#ifdef UNDER_CE
    #include "device\stocks.h"
#else
    #include "desktop\stocks.h"
#endif

#define SZ_WND_NAME         TEXT( "Stock Portfolios" )

#ifdef UNDER_CE
    #define WS_OUR_STYLE    WS_VISIBLE
    HWND    v_hwndCommBar;
    LPTSTR  v_lpszDBVol;

    // list view column width
    #define COMP_WIDTH  120
    #define PRICE_WIDTH 65
    #define PUR_WIDTH   60
    #define MOD_WIDTH   100

    #define PRICE       TEXT( "Price" )
    #define PUR_DATE    TEXT( "Pur. Date" )
    #define PUR_PRICE   TEXT( "Pur. Price" )
#else
    #define WS_OUR_STYLE    WS_OVERLAPPEDWINDOW | WS_VISIBLE

    // list view column width
    #define COMP_WIDTH  160
    #define PRICE_WIDTH 65
    #define PUR_WIDTH   90
    #define MOD_WIDTH   120

    #define PRICE       TEXT( "Last Price" )
    #define PUR_DATE    TEXT( "Purchase Date" )
    #define PUR_PRICE   TEXT( "Purchase Price" )
#endif

HINSTANCE   v_hInstance;
HWND        v_hwndMain, v_hwndLv;
CStocks     *v_pStocks;
BOOL        v_fAddStock;

typedef struct tagColInfo
{
    LPTSTR  lpszName;
    UINT    uWidth;
    BYTE    bAlign;
    BYTE    bSortOrder;
} COLINFO, *PCOLINFO;

static COLINFO v_rgCol[] =
{
    { TEXT( "Sym." ),           40,             LVCFMT_LEFT,    0 },
    { TEXT( "Company Name" ),   COMP_WIDTH,     LVCFMT_LEFT,    0 },
    { PRICE,                    PRICE_WIDTH,    LVCFMT_LEFT,    0 },
    { PUR_DATE,                 PUR_WIDTH,      LVCFMT_LEFT,    0 },
    { PUR_PRICE,                PUR_WIDTH,      LVCFMT_LEFT,    0 },
    { TEXT( "Gain/Loss" ),      65,             LVCFMT_LEFT,    0 },
    { TEXT( "Last Updated" ),   MOD_WIDTH,      LVCFMT_LEFT,    0 },
};

LONG APIENTRY MainWndProc( HWND hWnd, UINT message, UINT wParam, LONG lParam );

/*++
--*/
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow )
{
    WNDCLASS    wc;
    MSG         msg;
    HMENU       hMenu = NULL;
    HINSTANCE   hInstCtrl = NULL;

    v_hInstance = hInstance;
    ClearStruct( wc );

    wc.lpfnWndProc     = (WNDPROC)MainWndProc;
    wc.hInstance       = hInstance;
    wc.hIcon           = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON ) );
    wc.hbrBackground   = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName   = SZ_WND_CLASS;
    if ( !RegisterClass( &wc ) )
        return 0;

#ifndef UNDER_CE
    FARPROC     pInit;

    hInstCtrl = LoadLibrary( TEXT( "COMCTL32.DLL" ) );
    if ( !hInstCtrl )
        return 0;

    if ( ( pInit = GetProcAddress( hInstCtrl, TEXT( "InitCommonControls" ) ) ) != NULL )
        (*pInit)();

    hMenu = LoadMenu( hInstance, MAKEINTRESOURCE( IDR_MAIN_MENU ) );
#else
    InitCommonControls();
#endif

    v_hwndMain = CreateWindow( SZ_WND_CLASS, SZ_WND_NAME, WS_OUR_STYLE,
                               CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
                               NULL, hMenu, hInstance, NULL );
    if ( !v_hwndMain )
    {
        if ( hMenu )
            DestroyMenu( hMenu );
        return 0;
    }

#ifdef UNDER_CE
    if ( v_hwndCommBar )
        CommandBar_Show( v_hwndCommBar, nCmdShow );

    HKEY    hKey;
    long    lErr;
    DWORD   dw;
    LPTSTR  lpszTitle;

    // open the registry key where database volume is stored
    lErr = RegOpenKeyEx( HKEY_LOCAL_MACHINE, SZ_REG_ROOT, 0, KEY_READ, &hKey );
    if ( lErr != ERROR_SUCCESS )
        lErr = RegCreateKeyEx( HKEY_LOCAL_MACHINE, SZ_REG_ROOT, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &hKey, &dw );

    // save command line parameter as the database volume we want to use
    v_lpszDBVol = ( lpCmdLine == NULL )? TEXT( "" ) : lpCmdLine;

    if ( lErr == ERROR_SUCCESS )
    {
        RegSetValueEx( hKey, SZ_REG_DBVOL, NULL, REG_SZ, (const LPBYTE)v_lpszDBVol, ( lstrlen( v_lpszDBVol ) + 1 ) * sizeof( TCHAR ) );
        RegCloseKey( hKey );
    }

    // change the main window title based on the given database volume name
    if ( lpCmdLine == NULL || *lpCmdLine == 0 )
        lpCmdLine = TEXT( "System Volume" );

    lpszTitle = new TCHAR[ lstrlen( SZ_WND_NAME ) + lstrlen( lpCmdLine ) + 5 ];
    wsprintf( lpszTitle, TEXT( "%s - %s" ), lpCmdLine, SZ_WND_NAME );
    SetWindowText( v_hwndMain, lpszTitle );
    delete lpszTitle;
#endif

    ShowWindow( v_hwndMain, nCmdShow );

    v_pStocks = new CStocks( v_hwndMain, v_hwndLv );

    while ( GetMessage( &msg, NULL, 0, 0 ) )
    {
#ifdef UNDER_CE
        if ( !v_hwndCommBar || !IsCommandBarMessage( v_hwndCommBar, &msg ) )
#endif
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    }

    delete v_pStocks;

    if ( hMenu )
        DestroyMenu( hMenu );

    if ( hInstCtrl )
        FreeLibrary( hInstCtrl );
    UnregisterClass( SZ_WND_CLASS, hInstance );
    return 0;
}

/*++
UINT SzToPrice
    Convert a string into a intergal price in the unit of 1/100th of a cent 
    (ie. 1/10000th of a dollar). The given string may have a leading dollar 
    sign. Returns 0 in case of unrecognizable string
--*/
UINT SzToPrice( LPTSTR lpszPrice )
{
    LPTSTR   lpsz = lpszPrice;
    UINT    u = 0, uFrac = 0, uOrder, u1, u2;
    BOOL    fSuccess = FALSE;

    // skip spaces
    while ( *lpsz && *lpsz == TEXT( ' ' ) )
        lpsz++;

    if ( *lpsz == '$' )
        lpsz++;

    // skip more spaces
    while ( *lpsz && *lpsz == ' ' )
        lpsz++;

    // the integer
    while ( *lpsz && *lpsz != '.' && *lpsz != ' ' && *lpsz != '/' )
    {
        if ( *lpsz < '0' || *lpsz > '9' )
            goto Exit;

        u = u * 10 + *lpsz - '0';
        lpsz++;
    }

    // calculate uFrac
    if ( *lpsz == '.' )
    {
        lpsz++;
        uOrder = 100 * 100;
        while ( *lpsz )
        {
            if ( *lpsz < '0' || *lpsz > '9' )
                goto Exit;

            uFrac = uFrac * 10 + *lpsz - '0';
            uOrder /= 10;
            if ( uOrder == 1 )
                break;
            lpsz++;
        }
        uFrac *= uOrder;
    }
    else if ( *lpsz == ' ' || *lpsz == '/' )
    {
        if ( *lpsz == ' ' )
        {
            // skip more spaces
            while ( *lpsz && *lpsz == ' ' )
                lpsz++;

            u1 = 0;
            while ( *lpsz && *lpsz != '/' )
            {
                if ( *lpsz < '0' || *lpsz > '9' )
                    goto Exit;
                u1 = u1 * 10 + *lpsz - '0';
                lpsz++;
            }

            if ( *lpsz != '/' ) goto Exit;
        }
        else
        {
            u1 = u;
            u = 0;
        }

        lpsz++;
        u2 = 0;
        while ( *lpsz )
        {
            if ( *lpsz < '0' || *lpsz > '9' )
                goto Exit;
            u2 = u2 * 10 + *lpsz - '0';
            lpsz++;
        }

        if ( u2 == 0 ) goto Exit;
        uFrac = u1 * 100 * 100 / u2;
    }
    else if ( *lpsz )
        goto Exit;

    // u is in dollar, convert it into 1/100 of a cent
    u *= 100 * 100;

    fSuccess = TRUE;

Exit:
    return fSuccess? u + uFrac: 0;
}

//
// ================ Add/Change Stock Dialog ==========================
//
BOOL CALLBACK dlgAddChg( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    int     iSel;
    LV_ITEM lvi;

    switch( uMsg )
    {
    case WM_INITDIALOG:
        if ( v_fAddStock )
        {
            SetDlgItemText( hDlg, IDC_ADD_CHG, TEXT( "&Add" ) );
        }
        else
        {
            iSel = ListView_GetNextItem( v_hwndLv, -1, LVNI_SELECTED );
            if ( iSel < 0 )
            {
                MessageBox( hDlg, TEXT( "Couldn't find a selected stock." ), TEXT( "Error" ), MB_OK | MB_ICONSTOP );
                PostMessage( hDlg, WM_COMMAND, IDCANCEL, 0 );
            }
            else
            {
                LV_ITEM lvi;
                ClearStruct( lvi );
                lvi.iItem = iSel;
                lvi.mask = LVIF_PARAM;
                if ( !ListView_GetItem( v_hwndLv, &lvi ) || !v_pStocks->SetupDlg( hDlg, lvi.lParam ) )
                {
                    MessageBox( hDlg, TEXT( "Unable to change stock." ), TEXT( "Error" ), MB_OK | MB_ICONSTOP );
                    PostMessage( hDlg, WM_COMMAND, IDCANCEL, 0 );
                }

                SetDlgItemText( hDlg, IDC_ADD_CHG, TEXT ( "&Change" ) );
            }
        }
        return TRUE;

    case WM_COMMAND:
        switch( LOWORD( wParam ) )
        {
        case IDC_ADD_CHG:
            if ( v_fAddStock )
            {
                if ( v_pStocks->Add( hDlg ) )
                    EndDialog( hDlg, IDOK ); 
            }
            else
            {
                iSel = ListView_GetNextItem( v_hwndLv, -1, LVNI_SELECTED );
                if ( iSel >= 0 )
                {
                    ClearStruct( lvi );
                    lvi.iItem = iSel;
                    lvi.mask = LVIF_PARAM;
                    if ( ListView_GetItem( v_hwndLv, &lvi ) && v_pStocks->Change( hDlg, lvi.lParam ) )
                        EndDialog( hDlg, IDOK ); 
                }
            }
            break;

        case IDCANCEL:      
            EndDialog( hDlg, IDCANCEL ); 
            break;

        case IDC_LAST_PRICE:
        case IDC_PUR_PRICE:
            if ( HIWORD( wParam ) == EN_CHANGE )
            {
                TCHAR   sz[ 80 ];
                UINT    uLast, uPur;

                // update the gain/loss field
                GetDlgItemText( hDlg, IDC_LAST_PRICE, sz, sizeof( sz ) );
                uLast = SzToPrice( sz );

                GetDlgItemText( hDlg, IDC_PUR_PRICE, sz, sizeof( sz ) );
                uPur = SzToPrice( sz );

                if ( !uPur || !uLast )
                    lstrcpy( sz, TEXT( "N/A" ) );
                else if ( uPur < uLast )
                    wsprintf( sz, TEXT( "Gain %d%%" ), uLast * 100 / uPur - 100 );
                else if ( uPur > uLast )
                    wsprintf( sz, TEXT( "Loss %d%%" ), 100 - uLast * 100 / uPur );
                else
                    lstrcpy( sz, TEXT( "Even" ) );

                SetDlgItemText( hDlg, IDC_GAIN_LOSS, sz );

            }
            break;
        };
        break;
    }
    return FALSE;
}


//
// ================ Messages and Command Handlers ==========================
//

/*++
--*/
void OnAddChg( void )
{
    if ( v_pStocks->BeforeAddChg() )
        DialogBox( v_hInstance, TEXT( "Add_Change" ), v_hwndMain, (DLGPROC)dlgAddChg );
}

/*++
--*/
void OnDelete( void )
{
    LV_ITEM lvi;
    UINT    ix, cDeleted, cItems = ListView_GetItemCount( v_hwndLv );
    LPARAM  *rgParam = new LPARAM[ cItems ];

    if ( rgParam == NULL ) return;

    cDeleted = 0;
    for ( ix = 0; ix < cItems; ix++ )
    {
        ClearStruct( lvi );

        lvi.mask = LVIF_PARAM | LVIF_STATE;
        lvi.iItem = ix;
        lvi.stateMask = 0x000F;

        if ( ListView_GetItem( v_hwndLv, &lvi ) && ( lvi.state & LVIS_SELECTED ) )
            rgParam[ cDeleted++ ] = lvi.lParam;
    }

    for ( ix = 0; ix < cDeleted; ix++ )
        v_pStocks->Delete( rgParam[ ix ] );

    delete [] rgParam;

    // update the list view
    PostMessage( v_hwndMain, WM_DATA_CHANGED, 0, 0 );
}

/*++
--*/
void OnSize( HWND hwnd, UINT state, int cx, int cy )
{
#ifdef UNDER_CE
    if ( v_hwndCommBar )
        cy -= CommandBar_Height( v_hwndCommBar );
#endif
    if ( v_hwndLv )
        SetWindowPos( v_hwndLv, NULL, 0, 0, cx, cy, SWP_NOMOVE );
}

/*++
--*/
BOOL OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct )
{
    UINT        ix, y = 0;
    LV_COLUMN   lvc;

#ifdef UNDER_CE
    // Create a command bar
    if ( !( v_hwndCommBar = CommandBar_Create( v_hInstance, hwnd, 1 ) ) )
        return FALSE;

    // Add the menus and adornments
    if ( !CommandBar_InsertMenubar( v_hwndCommBar, v_hInstance, IDR_MAIN_MENU, 0 ) )
        return FALSE;

    if ( !CommandBar_AddAdornments( v_hwndCommBar, 0, 0 ) )
        return FALSE;

    y = CommandBar_Height( v_hwndCommBar );
#endif

    v_hwndLv = CreateWindowEx( 0, WC_LISTVIEW, TEXT(""),
        WS_CHILD | WS_VISIBLE | LVS_AUTOARRANGE | LVS_REPORT | LVS_SHOWSELALWAYS,
        0, y, 0, 0, hwnd, (HMENU)IDC_VIEW, v_hInstance, NULL );
    if ( !v_hwndLv )
        return FALSE;

    ClearStruct( lvc );
    lvc.mask    = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;  
    for ( ix = 0; ix < Dim( v_rgCol ); ix++ )
    {
        lvc.iSubItem    = ix;
        lvc.pszText     = v_rgCol[ix].lpszName;
        lvc.cchTextMax  = lstrlen( v_rgCol[ix].lpszName );
        lvc.cx          = v_rgCol[ix].uWidth;
        lvc.fmt         = v_rgCol[ix].bAlign;

        ListView_InsertColumn( v_hwndLv, ix, &lvc );
    }

    PostMessage( hwnd, WM_COMMAND, IDC_OPEN, 111 );
    return TRUE;
}

/*++
--*/
void OnNotify( int idCtrl, LPNMHDR pHdr )
{
    if ( idCtrl != IDC_VIEW ) return;

    NM_LISTVIEW *pNMLV = (NM_LISTVIEW *)pHdr;

    switch( pHdr->code )
    {
    case NM_DBLCLK:
        PostMessage( v_hwndMain, WM_COMMAND, IDC_CHANGE, 0 );
        break;

    case LVN_ITEMCHANGED:
        if ( pNMLV->uChanged & LVIF_STATE )
        {
            HMENU   hMenu;

#ifdef UNDER_CE
            hMenu = CommandBar_GetMenu( v_hwndCommBar, 0 );
#else
            hMenu = GetMenu( v_hwndMain );
#endif

            int ix = ListView_GetNextItem( v_hwndLv, -1, LVNI_SELECTED );

            EnableMenuItem( hMenu, IDC_DELETE, ix >= 0? MF_ENABLED : MF_GRAYED );
            EnableMenuItem( hMenu, IDC_CHANGE, ix >= 0? MF_ENABLED : MF_GRAYED );

#ifdef UNDER_CE
            CommandBar_Show( v_hwndCommBar, FALSE );
            CommandBar_Show( v_hwndCommBar, TRUE );
#else
            DrawMenuBar( v_hwndMain );
#endif
        }
        break;
    }
}

//
// ================ Main Window Proc ==========================
//
/*++
--*/
LONG APIENTRY MainWndProc( HWND hwnd, UINT uMsg, UINT wParam, LONG lParam )
{
    LONG    lResult = 0;
    int     iSel;

    switch ( uMsg )
    {
    HANDLE_MSG( hwnd, WM_SIZE, OnSize );
    HANDLE_MSG( hwnd, WM_CREATE, OnCreate );

    case WM_DATA_CHANGED:
        v_pStocks->OnDataChange();
        break;

    case WM_COMMAND:
        switch( wParam )
        {
        case IDC_OPEN:      
            // try open the default file first, if requested (lParam == 111)
            if ( lParam == 111 && v_pStocks->Open( SZ_DEFAULT_PORTFILE, TRUE ) )
                break;

            v_pStocks->Open( NULL, FALSE );
            break;

        case IDC_ADD:   
            v_fAddStock = TRUE;
            OnAddChg(); 
            break;

        case IDC_CHANGE:
            iSel = ListView_GetNextItem( v_hwndLv, -1, LVNI_SELECTED );
            if ( iSel >= 0 )
            {
                v_fAddStock = FALSE;
                OnAddChg();
            }
            break;

        case IDC_DELETE:
            if ( MessageBox( v_hwndMain, TEXT( "Are you sure to delete selected stocks?" ), TEXT( "Warning" ), MB_YESNO | MB_ICONEXCLAMATION ) == IDYES )
            {
                OnDelete(); 
            }
            break;

        case IDC_SYNC_NOW:
            v_pStocks->SyncNow();
            break;
        };
        break;

    case WM_NOTIFY:
        OnNotify( (int)wParam, (LPNMHDR)lParam );
        break;

    case WM_DESTROY:                  
#ifdef UNDER_CE
        if ( v_hwndCommBar )
            CommandBar_Destroy( v_hwndCommBar );
#endif

        PostQuitMessage(0);
        break;

    default:
        lResult = DefWindowProc( hwnd, uMsg, wParam, lParam );
        break;
    }

    return lResult;
}

device\devsetup.cpp—Device setup program to help debugging

/*++
Module Name:   
      devsetup.cpp

Abstract:
      Setup the device registry for the Stock Portfolio device synchronization module
--*/

#include <windows.h>
#include "..\common.h"

const TCHAR v_szRegRoot[] = TEXT( "Windows CE Services\\Synchronization\\Objects" );
const TCHAR v_szSyncDll[] = TEXT( "\\Windows\\stdevs.dll" );
const TCHAR v_szTempDll[] = TEXT( "\\stdevs.dll" );
const TCHAR v_szSuccess[] = TEXT( "Installation completed. Please disconnect and reconnect the device to use the module." );
const TCHAR v_szFailure[] = TEXT( "Installation failed. Error Code: %d" );
const TCHAR v_szMsgTitle[] = TEXT( "Stock Portfolio Synchronization Module" );
const TCHAR v_szMoved[] = TEXT( "ActiveSync module is moved to \\Windows" );
const TCHAR v_szNotExist[] = TEXT( "ActiveSync module doesn't exist in \\Windows directory" );

int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, 
                      int nCmdShow )
{
    LONG    lErr;
    HKEY    hRootKey, hKey;
    DWORD   dw;

    lErr = RegCreateKeyEx( HKEY_LOCAL_MACHINE, v_szRegRoot, 0, 0,
                           REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hRootKey, &dw );
    if ( lErr != ERROR_SUCCESS ) goto Exit;

    lErr = RegCreateKeyEx( hRootKey, SZ_STOCKPOR, 0, 0, REG_OPTION_NON_VOLATILE,
                           KEY_ALL_ACCESS, 0, &hKey, &dw );
    if ( lErr != ERROR_SUCCESS ) goto Exit;

    lErr = RegSetValueEx( hKey, TEXT( "Store" ), NULL, REG_SZ, (const LPBYTE)v_szSyncDll,
                         ( wcslen( v_szSyncDll ) + 1 ) * sizeof( WCHAR ) );

    RegCloseKey( hKey );
    RegCloseKey( hRootKey ); 

Exit:
    if ( GetFileAttributes( v_szTempDll ) != (DWORD)-1 )
    {
        if ( GetFileAttributes( v_szSyncDll ) != (DWORD)-1 )
            DeleteFile( v_szSyncDll );

        if ( MoveFile( v_szTempDll, v_szSyncDll ) )
            MessageBox( NULL, v_szMoved, v_szMsgTitle, MB_OK );
    }

    if ( lErr == ERROR_SUCCESS )
    {
        if ( GetFileAttributes( v_szSyncDll ) == (DWORD)-1 )
            MessageBox( NULL, v_szNotExist, v_szMsgTitle, MB_OK | MB_ICONSTOP );
        else
            MessageBox( NULL, v_szSuccess, v_szMsgTitle, MB_OK );
    }
    else 
    {   
        TCHAR   szMsg[ 200 ];
        wsprintf( szMsg, v_szFailure, lErr );
        MessageBox( NULL, szMsg, v_szMsgTitle, MB_OK | MB_ICONSTOP );
    }

    return lErr;
}

device\stocks.h—Device specific header file

#define WM_REG_NOTIFICATION             ( WM_USER + 100 )
#define WM_DATA_CHANGED                 ( WM_USER + 101 )

class CStocks
{
public:
   CStocks( HWND hwndMain, HWND hwndLv );
   ~CStocks();

   BOOL SetupDlg( HWND hDlg, UINT uParam );
   BOOL Open( LPTSTR lpszFile, BOOL fFailOnNew );
   BOOL Add( HWND hDlg );
   BOOL Change( HWND hDlg, UINT uParam );
   void Delete( UINT uParam );
   BOOL BeforeAddChg( void );
   void OnDataChange( void );

private:
   void   Close( void );
   void   UpdateView( void );
   BOOL   DoAddChg( HWND hDlg, PEGOID oid );
   int      ShowMsg( LPTSTR lpszText, int iLastErr = -1, UINT uFlags = 0 );
   void   SetViewItemText( UINT ix, PEGPROPVAL *rgProps, UINT cProps );
   void   AddItem( PEGOID oid, PEGPROPVAL *rgProps, UINT cProps );

   HWND   m_hwndMain, m_hwndLv, m_hwndNotify;
   PEGOID   m_oidDatabase;
    HANDLE  m_hDatabase;
};

device\stocks.cpp—Device specific code for the application

#include <windows.h>
#include <windowsx.h>
#include <winbase.h>
#include <commctrl.h>
#include <tchar.h>

#include "..\resource.h"
#include "..\common.h"

// define class CStocks
#include "stocks.h"

typedef struct tagPropMap
{
    UINT    uid;            // id of the control in the dialog for this property
    UINT    uPropId;        // property id
    UINT    cbMax;          // max size of the field
    TCHAR   szText[ 100 ];  // contents of the property
} PROPMAP, *PPROPMAP;

// GUID for the database volume
CEGUID      v_guid;

static PROPMAP v_rgProps[] =
{
    { IDC_SYMBOL,       HHPR_SYMBOL,    10 },
    { IDC_COMPANY,      HHPR_COMPANY,   80 },
    { IDC_LAST_PRICE,   HHPR_PRICE,     20 },
    { IDC_PUR_DATE,     HHPR_PUR_DATE,  20 },
    { IDC_PUR_PRICE,    HHPR_PUR_PRICE, 20 },
    { IDC_GAIN_LOSS,    HHPR_GAIN_LOSS, 20 },
    { 0,                HHPR_UP_TIME,   0 },
};

void GetLocalFileTime( FILETIME *pft )
{
    SYSTEMTIME  st;
    GetLocalTime( &st );
    SystemTimeToFileTime( &st, pft );
}

/*++
--*/
LPTSTR FormatTime( FILETIME *pft, LPTSTR lpsz )
{
    TCHAR       szDate[ 40 ], szTime[ 40 ];
    SYSTEMTIME  st;

    FileTimeToSystemTime( pft, &st );
    memset( szDate, 0, sizeof( szDate ) );
    memset( szTime, 0, sizeof( szTime ) );

    GetDateFormat( LOCALE_SYSTEM_DEFAULT, 0, &st, NULL, szDate, sizeof( szDate ) );
    GetTimeFormat( LOCALE_SYSTEM_DEFAULT, 0, &st, NULL, szTime, sizeof( szTime ) );
    wsprintf( lpsz, TEXT( "%s %s" ), szDate, szTime );
    return lpsz;
}

/*++
--*/
void UpdateItem( HWND hDlg, CEPROPVAL *rgProps, UINT cProps, BOOL fReadDlg )
{
    UINT        ix, jx;
    CEPROPVAL   *ppv;
    PPROPMAP    pProp;

    for ( ix = 0, ppv = rgProps; ix < cProps; ix++, ppv++ )
    {
        // search for this property in our map
        for ( jx = 0, pProp = v_rgProps; jx < Dim( v_rgProps ) && pProp->uPropId != ppv->propid; jx++, pProp++ );
        if ( jx >= Dim( v_rgProps ) )
            continue;

        // special case for update time
        if ( pProp->uid == 0 )
        {
            if ( fReadDlg )
                GetLocalFileTime( &ppv->val.filetime );
        }
        else if ( fReadDlg )
        {
            GetDlgItemText( hDlg, pProp->uid, pProp->szText, pProp->cbMax );
            ppv->val.lpwstr = pProp->szText;
        }
        else
            SetDlgItemText( hDlg, pProp->uid, ppv->val.lpwstr );
    }
}

/*++
--*/
CStocks::CStocks( HWND hwndMain, HWND hwndLv )
{
    m_hwndMain      = hwndMain;
    m_hwndLv        = hwndLv;
    m_hwndNotify    = NULL;
    m_oidDatabase   = 0;
    m_hDatabase     = NULL;
}

CStocks::~CStocks()
{
    Close();
}

int CStocks::ShowMsg( LPTSTR lpszText, int iLastErr, UINT uFlags )
{
    TCHAR   szMsg[ 1024 ];

    if ( iLastErr != -1 )
    {
        wsprintf( szMsg, TEXT( "%s Last Error: %d" ), lpszText, iLastErr );
        lpszText = szMsg; 
    }

    if ( uFlags == 0 )
        uFlags = MB_OK | MB_ICONINFORMATION;

    return MessageBox( m_hwndMain, lpszText, TEXT( "Stock Portfolio" ), uFlags );
}

void CStocks::Close( void )
{
    if ( m_hDatabase )
    {
        if ( CHECK_SYSTEMGUID( &v_guid ) )
            CeUnmountDBVol( &v_guid );

        CloseHandle( m_hDatabase );
        m_hDatabase = NULL;
    }
    m_oidDatabase = 0;
}

/*++
--*/
BOOL CStocks::Open( LPTSTR lpszFile, BOOL fFailOnNew )
{
    HANDLE      hFind;
    CEOIDINFO   oidInfo;

    // must close existing database first
    Close();

    // Mount the external database volume
    CREATE_SYSTEMGUID( &v_guid );
    if ( *v_lpszDBVol )
    {
        if ( !CeMountDBVol( &v_guid, v_lpszDBVol, OPEN_EXISTING ) )
        {
            if ( fFailOnNew )
                return FALSE;
                
            if ( !CeMountDBVol( &v_guid, v_lpszDBVol, CREATE_NEW ) )
            {
                TCHAR   szMsg[ MAX_PATH + 50 ];
                wsprintf( szMsg, TEXT( "Failed to mount the database volume: %s. Error: %d" ), 
                          v_lpszDBVol, GetLastError() );
                ShowMsg( szMsg );
                return FALSE;
            }
        }
    }
   
    // search for the database, create one if not found
    hFind = CeFindFirstDatabaseEx( &v_guid, DBTYPE_STOCKPOR );
    if ( hFind != INVALID_HANDLE_VALUE ) 
    {
        for ( ;; )
        {
            m_oidDatabase = CeFindNextDatabase( hFind );   
            if ( !m_oidDatabase ||
                 CeOidGetInfoEx( &v_guid, m_oidDatabase, &oidInfo ) && oidInfo.wObjType == OBJTYPE_DATABASE &&
                  !wcscmp( oidInfo.infDatabase.szDbaseName, SZ_DEFAULT_PORTFILE ) )
                break;
        }
        CloseHandle( hFind );
    }

    if ( !m_oidDatabase )
    {
        CEDBASEINFO info;

        memset( &info, 0, sizeof( info ) );
        info.dwFlags = CEDB_VALIDNAME | CEDB_VALIDTYPE;
        lstrcpy( info.szDbaseName, SZ_DEFAULT_PORTFILE );
        info.dwDbaseType = DBTYPE_STOCKPOR;
        m_oidDatabase = CeCreateDatabaseEx( &v_guid, &info );
    }

    if ( !m_oidDatabase )
    {
        ShowMsg( TEXT( "Failed to create a new database or find an existing one." ), GetLastError() );
        return FALSE;
    }

    // open the database now
    m_hDatabase = CeOpenDatabaseEx( &v_guid, &m_oidDatabase, 0, 0, CEDB_AUTOINCREMENT, NULL );
    if ( !m_hDatabase )
    {
        ShowMsg( TEXT( "Failed to open the database." ), GetLastError() );
        return FALSE;
    }

    ListView_DeleteAllItems( m_hwndLv );
    UpdateView();

    return TRUE;
}

/*++
--*/
BOOL CStocks::BeforeAddChg( void )
{
    return m_hDatabase || Open( NULL, FALSE );
}


/*++
void CStocks::SetViewItemText
--*/
void CStocks::SetViewItemText( UINT ix, CEPROPVAL *pProps, UINT cProps )
{
    UINT        jx;
    PPROPMAP    pMap;
    TCHAR       sz[ 120 ];

    for ( ; cProps; cProps--, pProps++ )
    {
        // search for this property in our map
        for ( jx = 0, pMap = v_rgProps; jx < Dim( v_rgProps ) && pMap->uPropId != pProps->propid; jx++, pMap++ );
        if ( jx >= Dim( v_rgProps ) )
            continue;

        ListView_SetItemText( m_hwndLv, ix, jx, 
                              PROP_TYPE( pProps->propid ) == CEVT_FILETIME? FormatTime( &pProps->val.filetime, sz ) : pProps->val.lpwstr );
    }
}

/*++
void CStocks::UpdateView
    Update the list view with current list of stocks.
--*/
void CStocks::UpdateView( void )
{
    UINT        ix, jx, cItems, cOid;
    CEOID       oid;
    CEOIDINFO   oidInfo;
    DWORD       dwIndex;
    CEOID       *rgOid = NULL;
    int         *rgIx = NULL;

    if ( !CeOidGetInfoEx( &v_guid, m_oidDatabase, &oidInfo ) || oidInfo.wObjType != OBJTYPE_DATABASE )
        return;

    cItems = ListView_GetItemCount( m_hwndLv );
    if ( oidInfo.infDatabase.wNumRecords == 0 )
    { 
        if ( cItems ) 
        {
            // delete all list view items
            ListView_DeleteAllItems( m_hwndLv );
        }
        return;
    }

    // create a list of OIDs and their item indices from the list view
    if ( cItems )
    {
        rgOid = new CEOID[ cItems ];
        rgIx = new int[ cItems ];
    }

    cOid = 0;
    for ( ix = 0; ix < cItems; ix++ )
    {
        LV_ITEM lvi;
        ClearStruct( lvi );
        lvi.iItem = ix;
        lvi.mask = LVIF_PARAM;
        if ( ListView_GetItem( m_hwndLv, &lvi ) )
        {
            rgOid[ cOid ] = lvi.lParam;
            rgIx[ cOid ] = ix;
            cOid++;
        }
    }

    CeSeekDatabase( m_hDatabase, CEDB_SEEK_BEGINNING, 0, &dwIndex );
    for ( ;; )
    {
        WORD        cProps;
        DWORD       cbProps;
        CEPROPVAL   *rgProps = NULL;

        oid = CeReadRecordProps( m_hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps );
        if ( oid == 0 ) 
            break;

        // search the oid list for this item
        for ( ix = 0; ix < cOid && rgOid[ix] != oid; ix++ );

        // didn't find it in the list view, must be new
        if ( ix >= cOid )
        {
            AddItem( oid, rgProps, cProps );
            cItems++;
        }
        else
        {
            for ( jx = 0; jx < cProps; jx++ )
                if ( rgProps[ jx ].propid == HHPR_FLAGS )
                {
                    if ( rgProps[ jx ].val.uiVal & SF_UPDATE_VIEW )
                    {
                        // update the record
                        SetViewItemText( rgIx[ ix ], rgProps, cProps );
                        rgProps[ jx ].val.uiVal &= ~SF_UPDATE_VIEW;

                        // need to save the record back since SF_UPDATE_VIEW is reset
                        CeWriteRecordProps( m_hDatabase, oid, cProps, rgProps );
                    }
                    break;
                }

            // remove the oid from the list so it won't get deleted later
            cOid--;
            if ( ix != cOid )
            {
                memmove( rgOid + ix, rgOid + ix + 1, ( cOid - ix ) * sizeof( rgOid[0] ) );
                memmove( rgIx + ix, rgIx + ix + 1, ( cOid - ix ) * sizeof( rgIx[0] ) );
            }
        }

        if ( rgProps )
            LocalFree( rgProps );
    }

    // remove deleted stocks from the list view
    if ( cOid )
    {
        for ( ix = 0; ix < cItems; ix++ )
        {
            LV_ITEM lvi;
            ClearStruct( lvi );
            lvi.iItem = ix;
            lvi.mask = LVIF_PARAM;
            if ( ListView_GetItem( m_hwndLv, &lvi ) )
            {
                for ( jx = 0; jx < cOid && rgOid[jx] != (CEOID)lvi.lParam; jx++ );
                if ( jx < cOid )
                {
                    ListView_DeleteItem( m_hwndLv, ix );
                    ix--;
                    cItems--;
                }
            }
        }
    }

    if ( rgOid )
        delete [] rgOid;
}

/*++
--*/
void CStocks::AddItem( CEOID oid, CEPROPVAL *rgProps, UINT cProps )
{
    LV_ITEM     lvi;
    UINT        cItems = ListView_GetItemCount( m_hwndLv );

    ClearStruct( lvi );
    lvi.iItem       = cItems;
    lvi.mask        = LVIF_PARAM;
    lvi.lParam      = (LPARAM)oid;
    ListView_InsertItem( m_hwndLv, &lvi );

    SetViewItemText( cItems, rgProps, cProps ); 
}

/*++
--*/
BOOL CStocks::DoAddChg( HWND hDlg, CEOID oid )
{
    CEPROPVAL       *rgProps = NULL;
    WORD            cProps = Dim( v_rgProps ) + 1;
    DWORD           dwIndex;
    CEOID           oidWrote;
    UINT            ix;

    rgProps = new CEPROPVAL[ cProps ];
    memset( rgProps, 0, cProps * sizeof( CEPROPVAL ) );

    for ( ix = 0; ix < Dim( v_rgProps ); ix++ )
        rgProps[ix].propid = v_rgProps[ix].uPropId;

    UpdateItem( hDlg, rgProps, Dim( v_rgProps ), TRUE );

    rgProps[ cProps - 1 ].propid = HHPR_FLAGS;
    rgProps[ cProps - 1 ].val.uiVal = SF_CHANGED1 | SF_CHANGED2;

    // add/change an extra HHPR_FLAGS
    if ( oid )
    {
        // read the old flags
        CEPROPVAL   *rgPropsRead = NULL;
        DWORD       cbProps;
        WORD        cPropsRead;

        if ( CeSeekDatabase( m_hDatabase, CEDB_SEEK_CEOID, oid, &dwIndex ) == oid &&
             CeReadRecordProps( m_hDatabase, CEDB_ALLOWREALLOC, &cPropsRead, NULL, (LPBYTE *)&rgPropsRead, &cbProps ) == oid )
        {
            for ( ix = 0; ix < cPropsRead; ix++ )
                if ( rgPropsRead[ ix ].propid == HHPR_FLAGS )
                {
                    if ( rgPropsRead[ ix ].val.uiVal & ( SF_CHANGED1 | SF_CHANGED2 ) )
                        rgProps[ cProps - 1 ].val.uiVal |= SF_CHG_IN_SYNC;

                    rgProps[ cProps - 1 ].val.uiVal |= rgPropsRead[ ix ].val.uiVal;
                    break;
                }
        }

        if ( rgPropsRead )
            LocalFree( rgPropsRead );

        rgProps[ cProps - 1 ].val.uiVal |= SF_UPDATE_VIEW;
    }

    oidWrote = CeWriteRecordProps( m_hDatabase, oid, cProps, rgProps );

    if ( !oidWrote || oid && oidWrote != oid )
    {
        ShowMsg( TEXT( "Failed to write a record into the database" ), GetLastError() );
        delete [] rgProps;
        return FALSE;
    }

    if ( oid == 0 )
        AddItem( oidWrote, rgProps, Dim( v_rgProps ) );
    else
        UpdateView();

    delete [] rgProps;
    return TRUE;
}

/*++
BOOL CStocks::Add
    Takes input from the dialog and add a new stock 
--*/
BOOL CStocks::Add( HWND hDlg )
{
    return DoAddChg( hDlg, 0 );
}

/*++
--*/
void CStocks::Delete( UINT uParam )
{  
    CeDeleteRecord( m_hDatabase, uParam );
}

/*++
BOOL CStocks::SetupDlg
    Setup the Add/Change dialog with the given uParam of the selected item in list view
--*/
BOOL CStocks::SetupDlg( HWND hDlg, UINT uParam )
{
    DWORD       dwIndex, cbProps;
    WORD        cProps;
    CEPROPVAL   *rgProps = NULL;

    if ( CeSeekDatabase( m_hDatabase, CEDB_SEEK_CEOID, uParam, &dwIndex ) != uParam ||
         CeReadRecordProps( m_hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps ) != uParam )
        return FALSE;

    UpdateItem( hDlg, rgProps, cProps, FALSE );

    if ( rgProps )
        LocalFree( rgProps );

    return TRUE;
}

/*++
BOOL CStocks::Change
    Change the stock using data from the dialog
--*/
BOOL CStocks::Change( HWND hDlg, UINT uParam )
{
    return DoAddChg( hDlg, uParam );
}

/*++
void CStocks::OnDataChange
    Responds to data change notification and update the list view
--*/
void CStocks::OnDataChange( void )
{
    UpdateView();
}

/*++
void CStocks::SyncNow
    As of Windows CE Services ver 2.1, there is no way for the device app to initiate synchronization.
--*/
void CStocks::SyncNow( void )
{
    // NYI
}

device\sync\stdevs.h—Header file for device ActiveSync module

// 
//  === Handler to serialize/deserialize objects ====================
//
class CDataHandler : public IReplObjHandler
{
public:
    CDataHandler( void );
    ~CDataHandler();

    // ******** IUnknown methods **************
    STDMETHODIMP_(ULONG)    AddRef( void );
    STDMETHODIMP_(ULONG)    Release( void );
    STDMETHODIMP            QueryInterface( REFIID riid, void **ppvObject );
    
    // ******** IReplObjHandler methods **************
    STDMETHODIMP Setup( PREPLSETUP pSetup );
    STDMETHODIMP Reset( PREPLSETUP pSetup );

   STDMETHODIMP GetPacket( LPBYTE *lppbData,  DWORD *pcbData, DWORD cbRecommend );
    STDMETHODIMP SetPacket( LPBYTE lpbData, DWORD cbData );
    STDMETHODIMP DeleteObj( PREPLSETUP pSetup );

private:
   long       m_cRef;
    PREPLSETUP  m_pWriteSetup, m_pReadSetup;
    STPACKET    m_packet;
};

device\sync\stdevs.cpp—Source file for device ActiveSync module

#include <windows.h>
#include "..\..\common.h"
#include "..\..\..\cesync.h"
#include "stdevs.h"

 // GUID for the database volume
CEGUID  v_guid;
TCHAR   v_szDBVol[ MAX_PATH ];

BOOL WINAPI DllMain( HANDLE hInstDll, ULONG ulReason, LPVOID lpReserved )
{
    switch( ulReason )
    {
    case DLL_PROCESS_ATTACH :
        break;

    case DLL_PROCESS_DETACH:
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    }
    return TRUE;
}

// Current partner bit (1 or 2)
UINT    v_uPartnerBit;

// OID of the database we synchronize
CEOID   v_oidDb;

/*++
BOOL IsVolMatched
    Return TRUE if the given BLOB matches the GUID we are currently using
--*/
BOOL IsVolMatched( LPBYTE lpbVol, UINT cbVol )
{
    if ( CHECK_SYSTEMGUID( &v_guid ) )
        return lpbVol == NULL || cbVol == sizeof( v_guid ) && CHECK_SYSTEMGUID( (CEGUID *)lpbVol );

    return cbVol == sizeof( v_guid ) && !memcmp( lpbVol, &v_guid, sizeof( v_guid ) );
}

/*++
BOOL Open
    Search/open the database we synchronize
--*/
BOOL Open( HANDLE *phDatabase, CEOID *pOid = NULL )
{
    // search for the database we want to replicate, create one if not found
    HANDLE      hFind;
    CEOID       oid;
    CEOIDINFO   oidInfo;

    oid         = 0;
    hFind       = CeFindFirstDatabaseEx( &v_guid, DBTYPE_STOCKPOR );

    if ( hFind != INVALID_HANDLE_VALUE ) 
    {
        for ( ;; )
        {
            oid = CeFindNextDatabase( hFind );   
            if ( !oid ||
                 CeOidGetInfoEx( &v_guid, oid, &oidInfo ) && oidInfo.wObjType == OBJTYPE_DATABASE &&
                  !wcscmp( oidInfo.infDatabase.szDbaseName, SZ_DEFAULT_PORTFILE ) )
                break;
        }
        CloseHandle( hFind );
    }

    if ( !oid )
    {
        CEDBASEINFO info;

        memset( &info, 0, sizeof( info ) );
        info.dwFlags = CEDB_VALIDNAME | CEDB_VALIDTYPE;
        lstrcpy( info.szDbaseName, SZ_DEFAULT_PORTFILE );
        info.dwDbaseType = DBTYPE_STOCKPOR;
        oid = CeCreateDatabaseEx( &v_guid, &info );
    }

    if ( oid && phDatabase )
        *phDatabase = CeOpenDatabaseEx( &v_guid, &oid, 0,  0, 0, NULL );

    if ( pOid )
        *pOid = oid;

    return oid && ( !phDatabase || *phDatabase != INVALID_HANDLE_VALUE );
}

/*++
EXTERN_C BOOL InitObjType
    Initialize/Un-initialize the moudle. lpszObjType is NULL if un-initializing.
--*/
EXTERN_C BOOL InitObjType( LPWSTR lpszObjType, IReplObjHandler **ppObjHandler, UINT uPartnerBit )
{
    if ( lpszObjType == NULL )
    {
        // terminating the module
        // free up all resources used
        if ( CHECK_SYSTEMGUID( &v_guid ) )
            CeUnmountDBVol( &v_guid );
        return TRUE;
    }

    // find out what database volume we are sync'ing
    HKEY    hKey;
    DWORD   dw, cb;

    v_szDBVol[0] = 0;
    if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, SZ_REG_ROOT, 0, KEY_READ, &hKey ) == ERROR_SUCCESS )
    {
        cb = sizeof( v_szDBVol );
        RegQueryValueEx( hKey, SZ_REG_DBVOL, NULL, &dw, (LPBYTE)v_szDBVol, &cb );  
        RegCloseKey( hKey );
    }

    if ( v_szDBVol[0] )
        CeMountDBVol( &v_guid, v_szDBVol, OPEN_EXISTING );
    else
    {
        CREATE_SYSTEMGUID( &v_guid );
    }

    *ppObjHandler = new CDataHandler;
    v_uPartnerBit = uPartnerBit;
    return TRUE;
}

/*++
EXTERN_C HRESULT FindObjects
    Enumerate all objects in the database volume currently used
--*/
EXTERN_C HRESULT FindObjects( PFINDOBJINFO pfi )
{
    DWORD       dwIndex, ix, jx, cOid, cbProps;
    CEPROPVAL   *rgProps = NULL;
    WORD        cProps;
    CEOID       oid;
    CEOIDINFO   oidInfo;
    HANDLE      hDatabase;
    int         nChanged = 0, nUnChanged = 0;
    static      CEGUID ceGuid;
     
    if ( pfi->uFlags & FO_DONE_ONE_VOL )
    {
        if ( pfi->poid )
            LocalFree( pfi->poid );
        return NOERROR;
    }

    if ( !CeOidGetInfoEx( &v_guid, v_oidDb, &oidInfo ) || oidInfo.wObjType != OBJTYPE_DATABASE )
        return E_UNEXPECTED;

    // must return the DB GUID as the volume ID, even if there may not be object in the store yet
    if ( !CHECK_SYSTEMGUID( &v_guid ) )
    {
        pfi->cbVolumeID = sizeof( CEGUID );
        pfi->lpbVolumeID = (LPBYTE)&v_guid;
    }

    cOid = oidInfo.infDatabase.wNumRecords;
    if ( cOid == 0 )
        return NOERROR;

    if ( !Open( &hDatabase ) )
        return E_POINTER;

    // NOTE: for sake of simplicity, we are not checking memory allocation error here
    // for real world application, out of memory case must be taken care of.
    pfi->poid = (PUINT)LocalAlloc( LPTR, cOid * sizeof( UINT ) );
       
    oid = CeSeekDatabase( hDatabase, CEDB_SEEK_BEGINNING, 0, &dwIndex );

    for ( ix = 0; ix < cOid && oid != 0; ix++ )
    {
        if ( oid == CeReadRecordProps( hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps ) )
        {
        for ( jx = 0; jx < cProps; jx++ )
                if ( rgProps[jx].propid == HHPR_FLAGS )
                {
                    if ( rgProps[ jx ].val.uiVal & v_uPartnerBit )
                    {
                        // Yes, this one did change -- tack it on to the end of our array
                        pfi->poid[cOid - nChanged - 1] = oid;
                        nChanged++;

                        if ( rgProps[ jx ].val.uiVal & SF_CHG_IN_SYNC )
                        {
                            rgProps[ jx ].val.uiVal &= ~SF_CHG_IN_SYNC;

                            if ( CeSeekDatabase( hDatabase, CEDB_SEEK_CEOID, oid, &dwIndex ) == oid )
                                CeWriteRecordProps( hDatabase, oid, 1, rgProps + jx );
                        }
                    }
                    else
                    {
                        // No, this one did not change -- tack it on to the beginning of our array
                        pfi->poid[nUnChanged] = oid;
                        nUnChanged++;
                    }
                    break;
                }
            LocalFree( rgProps );
        }
        oid = CeSeekDatabase( hDatabase, CEDB_SEEK_CURRENT, 1, &dwIndex );
    }

    pfi->cChg = nChanged;
    pfi->cUnChg = nUnChanged;

    CloseHandle( hDatabase );
    return NOERROR;
}

/*++
EXTERN_C BOOL ObjectNotify
    Respond to change/delete of a file system object
--*/
EXTERN_C BOOL ObjectNotify( POBJNOTIFY pNotify )
{
    CEPROPVAL   *rgProps = NULL;
    HANDLE      hDatabase = INVALID_HANDLE_VALUE;
    UINT        uixFlags = 0, ix;
    BOOL        fRet = FALSE, fSave = FALSE;
    DWORD       dwIndex, cbProps;
    WORD        cProps;

    if ( pNotify->cbStruct < sizeof( OBJNOTIFY ) )
    {
        // stop if strucuture size is smaller
        goto Exit;
    }

    // quick check on ONF_* flags
    // to see if we're interested in this notification
    if ( !( pNotify->uFlags & ( ONF_RECORD | ONF_CLEAR_CHANGE ) ) )
        goto Exit;

    //tputDebugString( L"3\n" );
    if ( !( pNotify->uFlags & ONF_DELETED ) )
    {
        // make sure we are dealing with the records in our database
        if ( ( pNotify->uFlags & ONF_RECORD ) && 
             ( pNotify->oidInfo.wObjType != OBJTYPE_RECORD || pNotify->oidInfo.infRecord.oidParent != v_oidDb ||
             !IsVolMatched( pNotify->lpbVolumeID, pNotify->cbVolumeID ) ) )
            goto Exit;

        if ( !Open( &hDatabase ) )
            goto Exit;

        CEOID   oid = CeSeekDatabase( hDatabase, CEDB_SEEK_CEOID, pNotify->oidObject, &dwIndex );
        if ( oid != pNotify->oidObject )
            goto Exit;
        
        oid = CeReadRecordProps( hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps );
        if ( oid != pNotify->oidObject )
            goto Exit;

        for ( ix = 0; ix < cProps; ix++ )
            if ( rgProps[ix].propid == HHPR_FLAGS )
            {
                uixFlags = ix;
                break;
            }

        if ( ix >= cProps )
            goto Exit;
    }

    if ( pNotify->uFlags & ONF_CLEAR_CHANGE )
    {
        fSave = TRUE;

        // did object got changed again during sync?
        if ( rgProps[ uixFlags ].val.uiVal & SF_CHG_IN_SYNC ) 
        {
            // clear this bit now but keep the dirty bit
            rgProps[ uixFlags ].val.uiVal &= ~SF_CHG_IN_SYNC;
            fRet = TRUE;
        }
        else
        {
            // clear the dirty bit now
            rgProps[ uixFlags ].val.uiVal &= ~v_uPartnerBit;
            fRet = FALSE;
        }

        goto Exit;
    }

    pNotify->poid = (UINT *)&pNotify->oidObject;

    // determine what object ID to be returned
    // if you store one object per file/record, you simply need to return the file system object ID given
    // otherwise, you need to read the file system object and figure out the list of object ID's that 
    // have changed.

    if ( pNotify->uFlags & ONF_DELETED )
    {
        // object has been deleted
        pNotify->cOidDel = 1;
    }
    else if ( ( rgProps[ uixFlags ].val.uiVal & v_uPartnerBit ) != 0 )
    {
        // object has been deleted
        pNotify->cOidChg = 1;

        if ( rgProps[ uixFlags ].val.uiVal & SF_CHG_IN_SYNC )
        {
            rgProps[ uixFlags ].val.uiVal &= ~SF_CHG_IN_SYNC;
            fSave = TRUE;
        }
    }

    fRet = TRUE;

Exit:  
    if ( fSave && CeSeekDatabase( hDatabase, CEDB_SEEK_CEOID, pNotify->oidObject, &dwIndex ) == pNotify->oidObject )
        CeWriteRecordProps( hDatabase, pNotify->oidObject, cProps, rgProps );
   
    if ( hDatabase != INVALID_HANDLE_VALUE )
        CloseHandle( hDatabase );

    if ( rgProps )
        LocalFree( rgProps );

    return fRet;
}

/*++
EXTERN_C BOOL SyncData
    Allow desktop to send/receive data
--*/
EXTERN_C BOOL SyncData( PSDREQUEST psd )
{
    // do we need to read or write data?
    if ( psd->fSet )
    {
    }
    else
    {
        switch( psd->uCode )
        {
        case 1:
            // set the size of the database volume name we are about to return
            psd->cbData = ( wcslen( v_szDBVol ) + 1 ) * sizeof( WCHAR );

            // Note: ActiveSync manage will call this routine twice, first with lpbData set to NULL
            // after cbData is returned, ActiveSync manager allocates the memory and call this routine again
            if ( psd->lpbData )
                wcscpy( (LPTSTR)psd->lpbData, v_szDBVol );
            break;
        }
    }

     return TRUE;
}


/*++
EXTERN_C BOOL GetObjTypeInfo
    Return object type related information in the given structure
--*/
EXTERN_C BOOL GetObjTypeInfo( POBJTYPEINFO pInfo )
{
    if ( pInfo->cbStruct != sizeof( OBJTYPEINFO ) )
        return FALSE;

    CEOIDINFO   oidInfo;

     if ( !Open( NULL, &v_oidDb ) )
        return FALSE;

    ClearStruct( oidInfo );
    CeOidGetInfoEx( &v_guid, v_oidDb, &oidInfo );

    wcscpy( pInfo->szName, oidInfo.infDatabase.szDbaseName );
    pInfo->cObjects = oidInfo.infDatabase.wNumRecords;
    pInfo->cbAllObj = oidInfo.infDatabase.dwSize;
    pInfo->ftLastModified = oidInfo.infDatabase.ftLastModified;
    return TRUE;
}

/*++
--*/
EXTERN_C BOOL ReportStatus( LPWSTR lpszObjType, UINT uCode, UINT uParam )
{
    HWND    hwnd;

    switch( uCode )
    {
    case RSC_BEGIN_SYNC:
        break;

    case RSC_END_SYNC:
        // post a message to the Stock Portfolio app so it will refresh the display
        hwnd = FindWindow( SZ_WND_CLASS, NULL );
        if ( hwnd )
            PostMessage( hwnd, WM_DATA_CHANGED, 0, 0 );
        break;
    }
    return TRUE;
}

//
// =*=*================== Data Handler ===================================
//

CDataHandler::CDataHandler()
{
    m_cRef  = 1;
}

CDataHandler::~CDataHandler()
{
}

/*++
--*/
STDMETHODIMP_(ULONG) CDataHandler::AddRef()
{
    ULONG urc;
    urc = (ULONG)InterlockedIncrement( &m_cRef );
    return(urc);
}

/*++
--*/
STDMETHODIMP_(ULONG) CDataHandler::Release()
{
    ULONG urc;
    urc = (ULONG)InterlockedDecrement( &m_cRef ); 
    if (urc == 0 ) 
        delete this;

    return urc;
}

/*++
--*/
STDMETHODIMP CDataHandler::QueryInterface( REFIID iid, LPVOID  *ppvObj )
{
    *ppvObj = NULL;
    return E_NOINTERFACE;
}

/*++
--*/
STDMETHODIMP CDataHandler::Reset( PREPLSETUP pSetup )
{                                            
    // we don't have resources to clean up
    return NOERROR;
}

/*++
--*/
STDMETHODIMP CDataHandler::Setup( PREPLSETUP pSetup )
{
    // we could be reading and writing at the same time, so need to save the pointer to setup struct differently
    if ( pSetup->fRead )
        m_pReadSetup = pSetup;
    else
        m_pWriteSetup = pSetup;

    return NOERROR;
}

/*++
--*/
STDMETHODIMP CDataHandler::DeleteObj( PREPLSETUP pSetup )
{
    DWORD   dwIndex;
    HANDLE  hDatabase;
    
    if ( !Open( &hDatabase ) )
        return E_UNEXPECTED;

    if ( CeSeekDatabase( hDatabase, CEDB_SEEK_CEOID, pSetup->oid, &dwIndex ) == pSetup->oid )
    {
        CeDeleteRecord( hDatabase, pSetup->oid );
        CloseHandle( hDatabase );
        return NOERROR;
    }

    CloseHandle( hDatabase );
    return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
}

/*++
--*/
STDMETHODIMP CDataHandler::GetPacket( LPBYTE *lppbPacket, DWORD *pcbPacket, DWORD cbRecommend )
{
    DWORD       dwIndex;
    CEPROPVAL   *rgProps = NULL, *pProp;
    DWORD       cbProps;
    WORD        cProps;
    UINT        ix;
    HRESULT     hr = RWRN_LAST_PACKET;
    HANDLE      hDatabase = INVALID_HANDLE_VALUE;
    
    if ( !Open( &hDatabase ) )
    {
        hr = E_FAIL;
        goto Exit;
    }

    if ( CeSeekDatabase( hDatabase, CEDB_SEEK_CEOID, m_pReadSetup->oid, &dwIndex ) != m_pReadSetup->oid ||
         CeReadRecordProps( hDatabase, CEDB_ALLOWREALLOC, &cProps, NULL, (LPBYTE *)&rgProps, &cbProps ) != m_pReadSetup->oid )
    {
        hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
        goto Exit;
    }

    ClearStruct( m_packet );
    for ( ix = 0, pProp = rgProps; ix < cProps; ix++, pProp++ )
    {
        switch( pProp->propid )
        {
        case HHPR_SYMBOL:       wcscpy( m_packet.wszSym, pProp->val.lpwstr ); break;
        case HHPR_COMPANY:      wcscpy( m_packet.wszCompany, pProp->val.lpwstr ); break;
        case HHPR_PRICE:        wcscpy( m_packet.wszLastPrice, pProp->val.lpwstr ); break;
        case HHPR_PUR_DATE:     wcscpy( m_packet.wszPurDate, pProp->val.lpwstr ); break;
        case HHPR_PUR_PRICE:    wcscpy( m_packet.wszPurPrice, pProp->val.lpwstr ); break;
        case HHPR_GAIN_LOSS:    wcscpy( m_packet.wszGain, pProp->val.lpwstr ); break;
        case HHPR_UP_TIME:      m_packet.ftUpdated = pProp->val.filetime; break;
        }
    }

Exit:
    if ( rgProps )
        LocalFree( rgProps );

    if ( hDatabase != INVALID_HANDLE_VALUE )
        CloseHandle( hDatabase );

    *pcbPacket = sizeof( m_packet );
    *lppbPacket = (LPBYTE)&m_packet;

    return hr;
}

/*++
--*/
STDMETHODIMP CDataHandler::SetPacket( LPBYTE lpbPacket, DWORD cbPacket )
{
    CEPROPVAL   rgProps[8];
    CEOID       oid;
    HANDLE      hDatabase = INVALID_HANDLE_VALUE;
    HRESULT     hr = NOERROR;
    PSTPACKET   pPacket = (PSTPACKET)lpbPacket;

    memset( rgProps, 0, sizeof( rgProps ) );

    // write the packet
    if ( cbPacket != sizeof( STPACKET ) )
    {
        hr = E_UNEXPECTED;
        goto Exit;
    }

    if ( !Open( &hDatabase ) )
    {
        hr = E_FAIL;
        goto Exit;
    }

    // must return the DB GUID as the volume ID
    if ( !CHECK_SYSTEMGUID( &v_guid ) )
    {
        m_pWriteSetup->cbVolumeID = sizeof( CEGUID );
        m_pWriteSetup->lpbVolumeID = (LPBYTE)&v_guid;
    }

    rgProps[0].propid       = HHPR_SYMBOL;
    rgProps[0].val.lpwstr   = pPacket->wszSym;

    rgProps[1].propid       = HHPR_COMPANY;
    rgProps[1].val.lpwstr   = pPacket->wszCompany;

    rgProps[2].propid       = HHPR_PRICE;
    rgProps[2].val.lpwstr   = pPacket->wszLastPrice;

    rgProps[3].propid       = HHPR_PUR_DATE;
    rgProps[3].val.lpwstr   = pPacket->wszPurDate;

    rgProps[4].propid       = HHPR_PUR_PRICE;
    rgProps[4].val.lpwstr   = pPacket->wszPurPrice;

    rgProps[5].propid       = HHPR_GAIN_LOSS;
    rgProps[5].val.lpwstr   = pPacket->wszGain;

    rgProps[6].propid       = HHPR_UP_TIME;
    rgProps[6].val.filetime = pPacket->ftUpdated;

    rgProps[7].propid       = HHPR_FLAGS;
    rgProps[7].val.uiVal    = SF_UPDATE_VIEW | ( ( SF_CHANGED1 | SF_CHANGED2 ) & ~v_uPartnerBit );

    // create a new stock record or overwriting the existing record
    if ( m_pWriteSetup->dwFlags & RSF_NEW_OBJECT )
        oid = m_pWriteSetup->oidNew = CeWriteRecordProps( hDatabase, 0, Dim( rgProps ), rgProps );
    else
        oid = CeWriteRecordProps( hDatabase, m_pWriteSetup->oidNew, Dim( rgProps ), rgProps );

    if ( !oid )
    {
        // most likely because we're out of memory 
        hr = E_OUTOFMEMORY;
        goto Exit;
    }

Exit:
    if ( hDatabase != INVALID_HANDLE_VALUE )
        CloseHandle( hDatabase );

    return hr;
}

desktop\stocks.cpp—Desktop specific code for the application

/*++
Module Name:   
      stocks.cpp

Abstract:
    Implementation of CStocks class
--*/

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>

#include "..\resource.h"
#include "..\common.h"

// define class CStocks
#include "stocks.h"

void GetLocalFileTime( FILETIME *pft )
{
    SYSTEMTIME  st;
    GetLocalTime( &st );
    SystemTimeToFileTime( &st, pft );
}
LPSTR FormatTime( FILETIME *pft, LPSTR lpsz )
{
    char        szDate[ 40 ], szTime[ 40 ];
    SYSTEMTIME  st;

    FileTimeToSystemTime( pft, &st );
    memset( szDate, 0, sizeof( szDate ) );
    memset( szTime, 0, sizeof( szTime ) );

    GetDateFormat( GetUserDefaultLCID(), 0, &st, NULL, szDate, sizeof( szDate ) );
    GetTimeFormat( GetUserDefaultLCID(), 0, &st, NULL, szTime, sizeof( szTime ) );
    wsprintf( lpsz, "%s %s", szDate, szTime );
    return lpsz;
}

void UpdateItem( HWND hDlg, PSTOCK pStock, BOOL fReadDlg )
{
    if ( fReadDlg )
    {
        GetLocalFileTime( &pStock->ftUpdated );
        GetDlgItemText( hDlg, IDC_SYMBOL, pStock->szSym, sizeof( pStock->szSym ) );
        GetDlgItemText( hDlg,IDC_COMPANY,pStock->szCompany, sizeof( pStock->szCompany ));
        GetDlgItemText( hDlg, IDC_LAST_PRICE, pStock->szLastPrice, 
                        sizeof( pStock->szLastPrice ) );
        GetDlgItemText( hDlg, IDC_PUR_DATE, pStock->szPurDate, 
                        sizeof( pStock->szPurDate ) );
        GetDlgItemText( hDlg, IDC_PUR_PRICE, pStock->szPurPrice, 
                        sizeof( pStock->szPurPrice ) );
        GetDlgItemText( hDlg, IDC_GAIN_LOSS, pStock->szGain, sizeof( pStock->szGain ) );
    }
    else
    {
        SetDlgItemText( hDlg, IDC_SYMBOL, pStock->szSym );
        SetDlgItemText( hDlg, IDC_COMPANY, pStock->szCompany );
        SetDlgItemText( hDlg, IDC_LAST_PRICE, pStock->szLastPrice );
        SetDlgItemText( hDlg, IDC_PUR_DATE, pStock->szPurDate );
        SetDlgItemText( hDlg, IDC_PUR_PRICE, pStock->szPurPrice );
        SetDlgItemText( hDlg, IDC_GAIN_LOSS, pStock->szGain );
    }
}


CStocks::CStocks( HWND hwndMain, HWND hwndLv )
{
    memset( m_szFile, 0, sizeof( m_szFile ) );
    m_hFile         = INVALID_HANDLE_VALUE;
    m_hMapObj       = NULL;
    m_hMutex        = NULL;
    m_pStocks       = NULL;
    m_hwndMain      = hwndMain;
    m_hwndLv        = hwndLv;
    m_hChgEvent     = CreateEvent( NULL, FALSE, FALSE, SZ_CHANGE_EVENT );
}

CStocks::~CStocks()
{
    CloseHandle( m_hChgEvent );
    Close();
}

void CStocks::Close( void )
{
    if ( m_hMutex )
    {
        CloseHandle( m_hMutex );
        m_hMutex = NULL;
    }

    if ( m_pStocks )
    {
        UnmapViewOfFile( m_pStocks );
        m_pStocks = NULL;
    }
    
    if ( m_hMapObj )
    {
        CloseHandle( m_hMapObj );
        m_hMapObj = NULL;
    }
    
    if ( m_hFile != INVALID_HANDLE_VALUE )
    {
        CloseHandle( m_hFile );
        m_hFile = INVALID_HANDLE_VALUE;
    }
    memset( m_szFile, 0, sizeof( m_szFile ) );
}

LPSTR MakeMapObjName( LPSTR lpszFile )
{
static char v_szMapObjName[ MAX_PATH ];
    UINT    ix;
    LPSTR   lpsz;
    
    for ( ix = 0, lpsz = lpszFile; *lpsz; ix++, lpsz++ )
        v_szMapObjName[ix] = ( *lpsz >= 'a' && *lpsz <= 'z' || *lpsz >= 'A' && 
                               *lpsz <= 'Z' )? *lpsz : 'A';
    v_szMapObjName[ix] = 0;
    return v_szMapObjName;
}

BOOL CStocks::Open( LPSTR lpszFile, BOOL fFailOnNew )
{
    OPENFILENAME    of;
    char            szMsg[ MAX_PATH * 2 ];
    BOOL            fSuccess = FALSE;
    BOOL            fNewFile = TRUE;

    // must close existing file first
    Close();

    memset( &szMsg, 0, sizeof( szMsg ) );

    if ( lpszFile )
        lstrcpy( m_szFile, lpszFile );
    else
    {
        ClearStruct( of );
        of.lStructSize      = sizeof( of );
        of.hwndOwner        = m_hwndMain;
        of.lpstrTitle       = "Open Stock Portfolios";
        of.lpstrFilter      = "Stock Portfolios File (*.por)\0*.por\0";
        of.nFilterIndex     = 1;
        of.lpstrFile        = m_szFile;
        of.nMaxFile         = sizeof( m_szFile );
        of.Flags            = OFN_HIDEREADONLY;
        of.lpstrDefExt      = "por";
        if ( !GetOpenFileName( &of ) ) goto Exit;
    }

    fNewFile = ( GetFileAttributes( m_szFile ) == (DWORD)-1 );
    if ( fNewFile )
    {
        if ( fFailOnNew ) goto Exit;

        wsprintf(szMsg,"%s does not exist. Do you want to create a new file?", m_szFile);    
        if ( MessageBox( m_hwndMain, szMsg, "File does not exist", 
                         MB_YESNO | MB_ICONEXCLAMATION ) == IDNO )
        {
            szMsg[ 0 ] = 0;
            goto Exit;
        }
        szMsg[ 0 ] = 0;

        m_hFile = CreateFile( m_szFile, GENERIC_READ | GENERIC_WRITE, 
                              FILE_SHARE_READ | FILE_SHARE_WRITE, 
                              NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
        if ( m_hFile == INVALID_HANDLE_VALUE )
        {
            wsprintf( szMsg, "Failed to create %s. Error: %d", m_szFile, GetLastError());  
            goto Exit;
        }
    }
    else
    {
        m_hFile = CreateFile( m_szFile, GENERIC_READ | GENERIC_WRITE, 
                              FILE_SHARE_READ | FILE_SHARE_WRITE, 
                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if ( m_hFile == INVALID_HANDLE_VALUE )
        {
            wsprintf( szMsg, "Failed to open %s. Error: %d", m_szFile, GetLastError() );    
            goto Exit;
        }
    }

    m_hMapObj = CreateFileMapping( m_hFile, NULL, PAGE_READWRITE, 0, sizeof( PORTFILE ),
                                   MakeMapObjName( m_szFile ) );
    if ( m_hMapObj == NULL )
    {
        wsprintf( szMsg, "Failed to create a file mapping using %s. Error: %d", 
                  m_szFile, GetLastError() );
        goto Exit;
    }

    // Get a pointer to the file-mapped shared memory:
    m_pStocks = (PPORTFILE)MapViewOfFile( m_hMapObj, FILE_MAP_WRITE, 0, 0, 0 );
    if ( m_pStocks == NULL ) 
    {
        wsprintf( szMsg, "Failed to map a file view on %s. Error: %d", 
                  m_szFile, GetLastError() );
        goto Exit;
    }

    // Get the Mutex
    if ( !m_hMutex )
        m_hMutex = CreateMutex( NULL, FALSE, SZ_MUTEX );

    if ( !m_hMutex )
    {
        wsprintf( szMsg, "Failed to create mutex named %s. Error: %d", SZ_MUTEX,
                  GetLastError() );
        goto Exit;
    }
    
    if ( fNewFile )
    {
        WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );

        ClearStruct( *m_pStocks );
        m_pStocks->uVer1        = PORTFILE_VERSION;
        m_pStocks->uVer2        = PORTFILE_VERSION;
        m_pStocks->uidCurrStock = 1;
        FlushViewOfFile( 0, sizeof( PORTFILE ) );

        ReleaseMutex( m_hMutex );
    }
    else
    {
        WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );
        if ( m_pStocks->uVer1 != PORTFILE_VERSION || m_pStocks->uVer2 !=PORTFILE_VERSION)
        {
            wsprintf( szMsg, "%s is not a valid portfolio file.", m_szFile );   
            ReleaseMutex( m_hMutex );
            goto Exit;
        }

        UpdateView();
        ReleaseMutex( m_hMutex );
    }

    fSuccess = TRUE;

Exit:
    if ( szMsg[0] )
        MessageBox( m_hwndMain, szMsg, "Error", MB_OK | MB_ICONSTOP );

    if ( !fSuccess )
        Close();
    else
    {
        wsprintf( szMsg, "Stock Portfolio - %s", m_szFile );
        SetWindowText( m_hwndMain, szMsg );
    }
    
    return fSuccess;
}

BOOL CStocks::BeforeAddChg( void )
{
    return m_szFile[0] || Open( NULL, FALSE );
}

/*++
PSTOCK CStocks::FindStock
    Find a stock using the stock ID, return NULL if not found.
    m_pStocks must be protected by the mutex before calling this routine and
    before finish using the pointer it returns
--*/
PSTOCK CStocks::FindStock( UINT uidStock, PUINT puix )
{
    UINT    ix;
    for ( ix = 0; ix < m_pStocks->cStocks; ix++  )
        if ( m_pStocks->rgStocks[ix].uidStock == uidStock )
            break;

    if ( puix )
        *puix = ix < m_pStocks->cStocks? ix : (UINT)-1;

    return ix < m_pStocks->cStocks? m_pStocks->rgStocks + ix: NULL;
}

void CStocks::SetViewItemText( UINT ix, PSTOCK pStock )
{
    char    sz[ 200 ];

    ListView_SetItemText( m_hwndLv, ix, 0, pStock->szSym );
    ListView_SetItemText( m_hwndLv, ix, 1, pStock->szCompany );
    ListView_SetItemText( m_hwndLv, ix, 2, pStock->szLastPrice );
    ListView_SetItemText( m_hwndLv, ix, 3, pStock->szPurDate );
    ListView_SetItemText( m_hwndLv, ix, 4, pStock->szPurPrice );
    ListView_SetItemText( m_hwndLv, ix, 5, pStock->szGain );
    ListView_SetItemText( m_hwndLv, ix, 6, FormatTime( &pStock->ftUpdated, sz ) );

    pStock->ftViewTime = pStock->ftUpdated;
}

/*++
void CStocks::UpdateView
    Update the list view with current list of stocks.
    m_pStocks must be protected by the mutex before calling this routine
--*/
void CStocks::UpdateView( void )
{
    int     ix, cItems;
    PSTOCK  pStock;

    for ( ix = 0, pStock = m_pStocks->rgStocks; 
          ix < (int)m_pStocks->cStocks; 
          ix++, pStock++ )
        pStock->uFlags &= ~SF_IN_VIEW;

    cItems = ListView_GetItemCount( m_hwndLv );
    for ( ix = 0; ix < cItems; ix++ )
    {
        LV_ITEM lvi;
        ClearStruct( lvi );
        lvi.iItem = ix;
        lvi.mask = LVIF_PARAM;
        if ( !ListView_GetItem( m_hwndLv, &lvi ) ) continue;

        pStock = FindStock( lvi.lParam );

        // remove deleted items from list view
        if ( !pStock )
        {
            ListView_DeleteItem( m_hwndLv, ix );
            ix--;
            cItems--;
            continue;
        }

        // update the item in list view if it's changed
        if ( CompareFileTime( &pStock->ftLastModified, &pStock->ftViewTime ) > 0 )
            SetViewItemText( ix, pStock );

        pStock->uFlags |= SF_IN_VIEW;
    }

    // add new items into list view
    for ( ix = 0, pStock = m_pStocks->rgStocks; 
          ix < (int)m_pStocks->cStocks; 
          ix++, pStock++ )
        if ( !( pStock->uFlags & SF_IN_VIEW ) )
            AddItem( pStock );


    ix = ListView_GetNextItem( m_hwndLv, -1, LVNI_SELECTED );

    EnableMenuItem( GetMenu( m_hwndMain ), IDC_CHANGE, 
                    m_pStocks->cStocks && ix >= 0? MF_ENABLED : MF_GRAYED );
    EnableMenuItem( GetMenu( m_hwndMain ), IDC_DELETE, 
                    m_pStocks->cStocks && ix >= 0? MF_ENABLED : MF_GRAYED );
    EnableMenuItem( GetMenu( m_hwndMain ), IDC_ADD, 
                    m_pStocks->cStocks >= MAX_STOCKS? MF_GRAYED : MF_ENABLED );
    DrawMenuBar( m_hwndMain );
}

void CStocks::AddItem( PSTOCK pStock )
{
    LV_ITEM     lvi;
    UINT        cItems = ListView_GetItemCount( m_hwndLv );

    ClearStruct( lvi );
    lvi.iItem       = cItems;
    lvi.mask        = LVIF_PARAM;
    lvi.lParam      = (LPARAM)pStock->uidStock;
    ListView_InsertItem( m_hwndLv, &lvi );

    SetViewItemText( cItems, pStock );  
}

/*++
BOOL CStocks::Add
    Takes input from the dialog and add a new stock 
--*/
BOOL CStocks::Add( HWND hDlg )
{
    STOCK       stock;

    UpdateItem( hDlg, &stock, TRUE );

    WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );

    stock.uidStock = m_pStocks->uidCurrStock;
    GetLocalFileTime( &stock.ftLastModified );
    m_pStocks->uidCurrStock++;
    m_pStocks->rgStocks[ m_pStocks->cStocks ] = stock;
    m_pStocks->cStocks++;

    // add the stock id to the change/delete log so we can sync it
    if ( m_pStocks->cChg < Dim( m_pStocks->rgidChg ) - 1 )
        m_pStocks->rgidChg[ m_pStocks->cChg++ ] = stock.uidStock;
    AddItem( &stock );

    if ( m_pStocks->cStocks >= MAX_STOCKS )
    {
        EnableMenuItem( GetMenu( GetParent( hDlg ) ), IDC_ADD_CHG, 
                        MF_BYCOMMAND | MF_GRAYED );
        DrawMenuBar( GetParent( hDlg ) );
    }

    ReleaseMutex( m_hMutex );

    // let the sync module to synchronize now!
    SetEvent( m_hChgEvent );
    return TRUE;
}

void CStocks::Delete( UINT uParam )
{
    UINT    ix;
    PSTOCK  pStock;

    WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );

    pStock = FindStock( uParam, &ix );
    if ( pStock )
    {
        // add the stock id to the change/delete log so we can sync it
        if ( m_pStocks->cDel < Dim( m_pStocks->rgidDel ) - 1 )
            m_pStocks->rgidDel[ m_pStocks->cDel++ ] = pStock->uidStock;

        m_pStocks->cStocks--;
        if ( ix != m_pStocks->cStocks )
            memmove( m_pStocks->rgStocks + ix, 
                     m_pStocks->rgStocks + ix + 1, 
                     ( m_pStocks->cStocks - ix ) * sizeof( m_pStocks->rgStocks[0] ) );
    }

    ReleaseMutex( m_hMutex );

    // let the sync module to synchronize now!
    SetEvent( m_hChgEvent );
}

/*++
BOOL CStocks::SetupDlg
    Setup the Add/Change dialog with the given uParam of the selected item in list view
--*/
BOOL CStocks::SetupDlg( HWND hDlg, UINT uParam )
{
    BOOL    fRet = FALSE;

    WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );

    PSTOCK  pStock = FindStock( uParam );
    if ( pStock )
    {
        UpdateItem( hDlg, pStock, FALSE );
        fRet = TRUE;
    }

    ReleaseMutex( m_hMutex );
    return fRet;
}

/*++
BOOL CStocks::Change
    Change the stock using data from the dialog
--*/
BOOL CStocks::Change( HWND hDlg, UINT uParam )
{
    BOOL    fRet = FALSE;
    WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );

    PSTOCK  pStock = FindStock( uParam );
    if ( pStock )
    {
        // add the stock id to the change/delete log so we can sync it
        if ( m_pStocks->cChg < Dim( m_pStocks->rgidChg ) - 1 )
            m_pStocks->rgidChg[ m_pStocks->cChg++ ] = uParam;

        GetLocalFileTime( &pStock->ftLastModified );
        UpdateItem( hDlg, pStock, TRUE );
        UpdateView();
        fRet = TRUE;
    }

    ReleaseMutex( m_hMutex );

    // let the sync module to synchronize now!
    SetEvent( m_hChgEvent );
    return fRet;
}

/*++
void CStocks::OnDataChange
    Responds to data change notification and update the list view
--*/
void CStocks::OnDataChange( void )
{
    WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT );
    UpdateView();
    ReleaseMutex( m_hMutex );
}

desktop\sync\stsync.rc—Resource file for desktop ActiveSync module

#include <windows.h>
#include "..\..\resource.h"

IDI_ICON                ICON    DISCARDABLE     "..\\stockpor.ico"

SYNCOPTDLG DIALOG DISCARDABLE  0, 0, 233, 211
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Stock Portfolio Synchronization"
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL         "Synchronize All Stocks",IDC_SYNC_ALL,"Button",
                    BS_AUTORADIOBUTTON,7,7,88,10
    CONTROL         "Synchronize Stocks From A to M",IDC_SYNC_AM,"Button",
                    BS_AUTORADIOBUTTON,7,21,119,10
    CONTROL         "Synchronize Stocks From N to Z",IDC_SYNC_NZ,"Button",
                    BS_AUTORADIOBUTTON,7,35,118,10
    LTEXT           "Current Database Volume:",IDC_STATIC,7,121,80,8
    EDITTEXT        IDC_DB_VOL,7,131,166,14,ES_AUTOHSCROLL | ES_READONLY
    LTEXT           "Synchronize with:",IDC_STATIC,7,150,141,8
    EDITTEXT        IDC_FILE,7,160,166,14,ES_AUTOHSCROLL
    DEFPUSHBUTTON   "OK",IDOK,60,190,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,118,190,50,14
END

desktop\sync\mainmod.h—Header file for desktop ActiveSync module

#include "..\..\..\cesync.h"
#include "..\..\common.h"
#include "..\..\resource.h"

class CStore;
class CDataHandler;

#define OBJECT_VERSION  1

#define OT_ITEM     1
#define OT_FOLDER   2

class CReplObject
{
public:
    virtual ~CReplObject() {}

    UINT    m_uType;
};

#define SO_ALL      0
#define SO_AM       1
#define SO_NZ       2

class CFolder: public CReplObject
{
public:
    CFolder( void )     { m_uType = OT_FOLDER; m_fChanged = FALSE; }
    virtual ~CFolder()  {}

    BOOL    m_fChanged;
};

class CItem: public CReplObject
{
public:
    CItem( void )       { m_uType = OT_ITEM; ClearStruct( m_ftModified ); }
    virtual ~CItem()    {}

    UINT        m_uid;
    FILETIME    m_ftModified;
};

typedef CItem *PITEM;

// 
//  === Handler to serialize/deserialize objects ====================
//
class CDataHandler : public IReplObjHandler
{
public:
    CDataHandler( CStore *pStore );
    ~CDataHandler();

    // ******** IUnknown methods **************
    STDMETHODIMP_(ULONG)    AddRef( void );
    STDMETHODIMP_(ULONG)    Release( void );
    STDMETHODIMP            QueryInterface( REFIID riid, void **ppvObject );
    
    // ******** IReplObjHandler methods **************
    STDMETHODIMP Setup( PREPLSETUP pSetup );
    STDMETHODIMP Reset( PREPLSETUP pSetup );

    STDMETHODIMP GetPacket( LPBYTE *lppbData,  DWORD *pcbData, DWORD cbRecommend );
    STDMETHODIMP SetPacket( LPBYTE lpbData, DWORD cbData );
    STDMETHODIMP DeleteObj( PREPLSETUP pSetup );

private:
   long       m_cRef;
    CStore      *m_pStore;
    PREPLSETUP  m_pWriteSetup, m_pReadSetup;
    STPACKET    m_packet;
};

#define ISF_INITIALIZED ((UINT)0x80000000)  // set if the store was initialized successfully

/////////////////////////////////////////////////////////////////////////////
class CStore: public IReplStore
{
private:
    LONG                m_cRef;
    LPUNKNOWN           m_pUnkOuter;

public:
    CStore( LPUNKNOWN );
    ~CStore();

    // ******** IUnknown methods **************
    STDMETHODIMP            QueryInterface(REFIID riid, 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 );    // get first object the folder
    STDMETHODIMP        FindNextItem( HREPLFLD hFolder,  HREPLITEM *phItem, BOOL *pfExist );     // get next object the folder
    STDMETHODIMP        FindItemClose( HREPLFLD hFolder );                                    // done enumerating

    // STD 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 hwndParent, HREPLFLD hFolder, IEnumReplItem *penum );
    STDMETHODIMP        GetObjTypeUIData( HREPLFLD hFolder, POBJUIDATA pData );
    STDMETHODIMP        GetConflictInfo( PCONFINFO pConfInfo );
    STDMETHODIMP        RemoveDuplicates( LPSTR, UINT );

private:
friend CALLBACK dlgSyncOpt( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam );
friend DWORD WINAPI Listen( LPVOID pvStore );
friend class CDataHandler;

    HRESULT         Open( BOOL fCreateNew );
    HRESULT         Close( void );
    PSTOCK          FindStock( UINT uidStock, PUINT puix  = NULL );
    void            Lock( void ) { WaitForSingleObject( m_hMutex, MUTEX_TIMEOUT ); }
    void            Unlock( void ) { ReleaseMutex( m_hMutex ); }
    PITEM           MakeNewItem( UINT uidStock );

    char            m_szStockFile[ MAX_PATH ];
   HANDLE          m_hFile, m_hMapObj, m_hMutex;
   PPORTFILE       m_pStocks;

    UINT            m_uFlags;
    IReplNotify     *m_pNotify;
    CDataHandler    *m_pObjHandler;
    HANDLE          m_hListen, m_hKillListen, m_hStockChg;

    // for enumeration of objects
    CItem           **m_rgpItems;
    UINT            m_ixCurrItem;
    UINT            m_cItems;
};

desktop\sync\guids.cpp—Define GUIDs used in the ActiveSync module

#include <windows.h>
#define  INITGUIDS
#include <initguid.h>
#include "mainmod.h"

desktop\sync\mainmod.cpp—Implement IReplStore interface

#include <windows.h>
#include "mainmod.h"

HINSTANCE       v_hInst;
static char     v_szStockFile[ MAX_PATH ];
static char     v_szDBVol[ MAX_PATH ]; 
static UINT     v_uSyncOpt;
static CStore   *v_pStore;

BOOL WINAPI DllMain ( HANDLE hInstDll, ULONG ulReason, LPVOID lpReserved )
{
    switch( ulReason )
    {
    case DLL_PROCESS_ATTACH :
        v_hInst = hInstDll;
        break;

    case DLL_PROCESS_DETACH:
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    }
    return TRUE;
}


//
//  ============ Required OLE implementation for InProc servers ======================
//
// CClassFactory object creates CStore objects

class CClassFactory : public IClassFactory 
{
private:
    LONG    m_cRef; 

public:
    CClassFactory( void ) : m_cRef( 0 ) {};

    virtual STDMETHODIMP            QueryInterface( REFIID iid, LPVOID* ppv);
    virtual STDMETHODIMP_(ULONG)    AddRef(); 
    virtual STDMETHODIMP_(ULONG)    Release();

    // IClassFactory members
    virtual STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    virtual STDMETHODIMP LockServer(BOOL);
};


// Count number of objects and number of locks
static LONG v_cObj = 0;
static LONG v_cLock = 0;

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)  
{
    if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_IClassFactory))
        return CLASS_E_CLASSNOTAVAILABLE;

    //return our IClassFactory for CStore objects
    *ppv = (LPVOID)new CClassFactory();
    if ( NULL == *ppv ) 
        return E_OUTOFMEMORY;

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();

    return NOERROR;
}

STDAPI DllCanUnloadNow(void)  
{
    return ResultFromScode( 0L == v_cObj && 0 == v_cLock? S_OK : S_FALSE );
}


STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, LPVOID* ppv)  
{
    *ppv=NULL;
    if ( IsEqualIID( riid, IID_IUnknown ) || IsEqualIID( riid, IID_IClassFactory ) ) 
        *ppv=(LPVOID)this;

    if( NULL != *ppv )  
    {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CClassFactory::AddRef( void )
{
    ULONG urc;
    urc = (ULONG)InterlockedIncrement( &m_cRef );
    return(urc);
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
    ULONG urc;
    urc = (ULONG)InterlockedDecrement( &m_cRef ); 
    if (urc == 0 ) 
        delete this;

    return urc;
}

STDMETHODIMP CClassFactory::CreateInstance( LPUNKNOWN pUnkOuter, REFIID riid, LPVOID* ppvObj )
{
    CStore  *pObj;
    HRESULT hr;
    DWORD   dwLastError = 0;
    
    *ppvObj = NULL;
    hr = E_OUTOFMEMORY;

    if ( NULL != pUnkOuter && !IsEqualIID( riid, IID_IUnknown ) )
        return E_NOINTERFACE;

    pObj = new CStore( pUnkOuter );
    if ( NULL == pObj ) 
        return E_OUTOFMEMORY;

    hr = pObj->QueryInterface( riid, ppvObj );

    InterlockedIncrement( &v_cObj );

    if ( FAILED( hr ) ) 
    {
        delete pObj; // <-- this destroys LastError
        if( dwLastError )
            SetLastError( dwLastError );
    }

    return hr;
}


STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
    if (fLock)
        InterlockedIncrement( &v_cLock );
    else     
        InterlockedDecrement( &v_cLock );

    return NOERROR;
}



//
// ================== Basic Implementation of IReplStore ====================
//

/*++
--*/
CStore::CStore( LPUNKNOWN pUnkOuter )
{
    m_cRef          = 0;
    m_pUnkOuter     = pUnkOuter;
    m_uFlags        = 0;
    m_pObjHandler   = new CDataHandler( this );
    m_rgpItems      = NULL;
    m_ixCurrItem    = 0;
    m_cItems        = 0;

    lstrcpy( m_szStockFile, SZ_DEFAULT_PORTFILE );
    m_hFile         = INVALID_HANDLE_VALUE;
    m_hMapObj       = NULL;
    m_hMutex        = NULL;
    m_pStocks       = NULL;

    m_hListen       = NULL;
    m_hKillListen   = CreateEvent( NULL, FALSE, FALSE, NULL );
    m_hStockChg     = CreateEvent( NULL, FALSE, FALSE, SZ_CHANGE_EVENT );
}

/*++
--*/
CStore::~CStore()
{
    CloseHandle( m_hKillListen );
    CloseHandle( m_hStockChg );

    delete m_pObjHandler;
    InterlockedDecrement( &v_cObj );
}


//
// ==================== IUnknown Implementation ===========================
//

/*++
--*/
STDMETHODIMP CStore::QueryInterface( REFIID iid, LPVOID  *ppvObj )
{
    *ppvObj = NULL;   // set to NULL, in case we fail.

    if ( IsEqualIID( iid, IID_IUnknown ) )
        *ppvObj = (void*)this;
    else if ( IsEqualIID( iid, IID_IReplStore ) )
        *ppvObj = (void*)(IReplStore *)this;
    else if ( m_pUnkOuter )
        return m_pUnkOuter->QueryInterface( iid, ppvObj );

    if ( *ppvObj )
    {
        ((IUnknown *)(*ppvObj))->AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

/*++
--*/
STDMETHODIMP_(ULONG) CStore::AddRef()
{
    ULONG   urc;

    if ( m_pUnkOuter )
        urc = m_pUnkOuter->AddRef();
    else
        urc = (ULONG)InterlockedIncrement( &m_cRef );
    return urc;
}

/*++
--*/
STDMETHODIMP_(ULONG) CStore::Release()
{
    ULONG   urc;

    if ( m_pUnkOuter )
        urc = m_pUnkOuter->Release();
    else
    {
        urc =(ULONG)InterlockedDecrement( &m_cRef );
        if ( urc == 0 )
            delete this;
    }
    return urc;
}

//
// ================= thread listening to the changes/deletes in the Stock Portfolio app
//
DWORD WINAPI Listen( LPVOID pvStore )
{
    CStore  *pStore = (CStore *)pvStore;
    DWORD   dwObj;
    HANDLE  rgHandles[] = { pStore->m_hKillListen, pStore->m_hStockChg };
    UINT    ix, jx;

    for ( ;; )
    {
        dwObj = WaitForMultipleObjects( 2, rgHandles, FALSE, INFINITE );

        // will quit this thread for any other value
        if ( dwObj != WAIT_OBJECT_0 + 1 )
            break;

        if ( !pStore->m_pNotify ) continue;

        // get the change/delete log from the Stock Portfolio app
        pStore->Lock();
        for ( ix = 0; ix < pStore->m_pStocks->cChg; ix++ )
        {
            // check if this change is deleted too
            for ( jx = 0; jx < pStore->m_pStocks->cDel && pStore->m_pStocks->rgidChg[ix] != pStore->m_pStocks->rgidDel[jx]; jx++ );
            if ( jx >= pStore->m_pStocks->cDel )
            {
                PITEM   pItem = pStore->MakeNewItem( pStore->m_pStocks->rgidChg[ ix ] );
                if ( pItem && 
                     FAILED( pStore->m_pNotify->OnItemNotify( RNC_MODIFIED, SZ_STORE_PROG_ID, SZ_STOCKPOR, (HREPLITEM)pItem, 0 ) ) )
                    delete pItem;           
            }
        }

        for ( ix = 0; ix < pStore->m_pStocks->cDel; ix++ )
        {
            PITEM   pItem = pStore->MakeNewItem( pStore->m_pStocks->rgidDel[ ix ] );
            if ( pItem &&
                 FAILED( pStore->m_pNotify->OnItemNotify( RNC_DELETED, SZ_STORE_PROG_ID, SZ_STOCKPOR, (HREPLITEM)pItem, 0 ) ) )
                delete pItem;           
        }

        pStore->m_pStocks->cChg = pStore->m_pStocks->cDel = 0;
        pStore->Unlock();
    }
    return 0;
}


//
// ==================== IReplStore Implementation ===========================
//

/*++
--*/
STDMETHODIMP CStore::Initialize
(
    IReplNotify *pNotify,
    UINT        uFlags      // either ISF_SELECTED_DEVICE or ISF_REMOTE_CONNECTED
)
{
    LONG    lErr;
    HKEY    hRootKey;
    DWORD   dw, dwSize;
    char    szFile[ MAX_PATH * 2 ];
    HRESULT hr = NOERROR;

    m_uFlags    = uFlags;
    m_pNotify   = pNotify;

    // get the correct registry key for stock sync options
    hr = m_pNotify->QueryDevice( ( uFlags & ISF_SELECTED_DEVICE )? QDC_SEL_DEVICE_KEY : QDC_CON_DEVICE_KEY, (LPVOID *)&hRootKey );
    if ( FAILED( hr ) )
        goto Exit;

    // read the registry for the stock portfolio file to sync
    dw = REG_SZ;
    dwSize = sizeof( szFile );
    lErr = RegQueryValueEx( hRootKey, "Stock File", NULL, &dw, (const LPBYTE)szFile, &dwSize );
    if ( lErr == ERROR_SUCCESS )
        lstrcpy( m_szStockFile, szFile );
    
    // read the registry for sync option
    dwSize = sizeof( v_uSyncOpt );
    v_uSyncOpt = SO_ALL;
    RegQueryValueEx( hRootKey, "Sync Option", NULL, &dw, (const LPBYTE)&v_uSyncOpt, &dwSize );

    RegCloseKey( hRootKey );

    hr = Open( TRUE );

    // do not show any blocking UI (like the message box) if we are connected remotely
    if ( FAILED( hr ) && !( uFlags & ISF_REMOTE_CONNECTED ) )
    {
        wsprintf( szFile, "Unable to open Stock Portfolio file %s for synchronization. Error Code: 0x%X", m_szStockFile, hr );
        MessageBox( m_pNotify->GetWindow( 0 ), szFile, "Synchronization Error", MB_OK | MB_ICONSTOP );
    }

Exit:
    if ( SUCCEEDED( hr ) )
        m_uFlags |= ISF_INITIALIZED;

    return hr;
}

/*++
--*/
STDMETHODIMP CStore::GetStoreInfo
(
    PSTOREINFO pInfo    // pointers to the STOREINFO structure
)
{
    if ( pInfo->cbStruct != sizeof( STOREINFO ) )
        return E_INVALIDARG;

    pInfo->uFlags = SCF_SINGLE_THREAD;

    // ProgId of the store
    lstrcpy( pInfo->szProgId, SZ_STORE_PROG_ID );
    lstrcpy( pInfo->szStoreDesc, "Stock Portfolio" );

    // done here if store is not yet initialized
    if ( !( m_uFlags & ISF_INITIALIZED ) )
        return NOERROR;

    // construct something that uniquely identifies the store
    pInfo->cbStoreId = lstrlen( m_szStockFile ) + 1;
    if ( pInfo->cbStoreId > pInfo->cbMaxStoreId )
        return E_OUTOFMEMORY;

    if ( pInfo->lpbStoreId == NULL )
        return E_POINTER;

    memcpy( pInfo->lpbStoreId, m_szStockFile, lstrlen( m_szStockFile ) + 1 );
    return NOERROR;
}

/*++
--*/
STDMETHODIMP_(int) CStore::CompareStoreIDs
(
    LPBYTE  lpbID1,     // points to the first store ID
    UINT    cbID1,      // size of the first store ID
    LPBYTE  lpbID2,     // points to the second store ID
    UINT    cbID2       // size of the second store ID
)
{
    if ( cbID1 < cbID2 )
        return -1;

    if ( cbID1 > cbID2 )
        return 1;

    return memcmp( lpbID1, lpbID2, cbID1 );
}

/*++
--*/
STDMETHODIMP CStore::ReportStatus
(
    HREPLFLD    hFolder,    // Handle of the folder this status applies to. NULL if status applies to all folders
    HREPLITEM   hItem,      // Handle of the object this status applies to. NULL if status applies to all objects
    UINT        uStatus,    // See RSC_xxx defined in cesync.h for all possibble code
    UINT        uParam      // Additional information about the status, based on uStatus code
)
{
    switch( uStatus )
    {
    case RSC_INTERRUPT:     // client should abort whatever it's doing now
        break;

    case RSC_BEGIN_SYNC:    // ActiveSync service manager is about to start
        break;

    case RSC_END_SYNC:      // ActiveSync service manager is about to end
        break;

    case RSC_BEGIN_CHECK:   // FindFirstItem is about to be called, followed by FindNextItem
        break;

    case RSC_END_CHECK:     // FindItemClose has been called
        break;

    case RSC_DATE_CHANGED:  // System Date has changed
        break;

    case RSC_RELEASE:       // ActiveSync service manager is about to release the service provider
        // close file
        Close();

        // wait for the listen thread to die
        if ( m_hListen )
        {
            SetEvent( m_hKillListen );
            WaitForSingleObject( m_hListen, 10000 );
            CloseHandle( m_hListen );
            m_hListen = NULL;
        };
        break;

    case RSC_REMOTE_SYNC:   // Indicates if remote sync is about to start. uParam will TRUE if all sync 
                            // will be remote until this status is reported again with uParam set to FALSE
        break;

    case RSC_BEGIN_SYNC_OBJ:    // ActiveSync service manager is about to start on an object type. uReserved is a pointer to a IEnumReplItem
        break;

    case RSC_END_SYNC_OBJ:      // ActiveSync service manager is about to end on an object type.
        break;

    case RSC_OBJ_TYPE_ENABLED:  // ActiveSync service manager of the given object is enabled, hFolder is indeed a pointer to a string (object type name)
        break;

    case RSC_OBJ_TYPE_DISABLED: // ActiveSync service manager of the given object is disabled, hFolder is indeed a pointer to a string (object type name)
        break;

    case RSC_BEGIN_BATCH_WRITE: // A series of SetPacket will be called on a number of objects, this is the right time for some service providers to start a transaction
        break;

    case RSC_END_BATCH_WRITE:   // above write ends, this is the right time for some service providers to commit the transaction
        break;

    case RSC_CONNECTION_CHG:    // connection status has changed. uParam is TRUE if connection established. FALSE otherwise.
        break;

    case RSC_WRITE_OBJ_FAILED:  // failed writing an object on the device. uParam is the HRESULT code.
        break;

    case RSC_DELETE_OBJ_FAILED: // failed deleting an object on the device. uParam is the HRESULT code.
        break;
    }

    return NOERROR;
}


//
//  ==================== Object management routines =====================
//

/*++
--*/
STDMETHODIMP_(UINT) CStore::ObjectToBytes
(
    HREPLOBJ    hObject,
    LPBYTE      lpb         // Points to a buffer where the array of bytes should be store. Could be NULL.
)
{
    LPBYTE      lpbStart = lpb;
    CReplObject *pObject = (CReplObject *)hObject;
    CFolder     *pFolder = (CFolder *)pObject;
    CItem       *pItem = (CItem *)pObject;

    if ( lpbStart )
        *lpb = OBJECT_VERSION;
    lpb++;

    if ( lpbStart )
        *(PUINT)lpb = pObject->m_uType;
    lpb += sizeof( pObject->m_uType );

    switch( pObject->m_uType )
    {
    case OT_FOLDER:
        break;

    case OT_ITEM:
        if ( lpbStart )
            *(PUINT)lpb = pItem->m_uid;
        lpb += sizeof( pItem->m_uid );

        if ( lpbStart )
            *(FILETIME *)lpb = pItem->m_ftModified;
        lpb += sizeof( pItem->m_ftModified );
        break;
    }

    return lpb - lpbStart;
}

/*++
--*/
STDMETHODIMP_(HREPLOBJ) CStore::BytesToObject
(
    LPBYTE  lpb,        // Points to a buffer where the array of bytes should be store. Could be NULL.
    UINT    cb          // size of the buffer
)
{
    CReplObject *pObject = NULL;
    CFolder     *pFolder;
    CItem       *pItem;

    BYTE    bVersion = *lpb++;
    UINT    uType = *(PUINT)lpb;
    lpb += sizeof( uType );

    if ( bVersion != OBJECT_VERSION )
    {
        // convert the data based on bVersion
    }

    switch( uType )
    {
    case OT_FOLDER:
        pObject = pFolder = new CFolder;
        break;

    case OT_ITEM:
        pObject = pItem = new CItem;

        pItem->m_uid = *(PUINT)lpb;
        lpb += sizeof( pItem->m_uid );

        pItem->m_ftModified = *(FILETIME *)lpb;
        lpb += sizeof( pItem->m_ftModified );

        break;
    }

    return (HREPLOBJ)pObject;
}

/*++
--*/
STDMETHODIMP_(void) CStore::FreeObject
(
    HREPLOBJ    hObject     // handler of the object whose contents need to be freed
)
{
    delete (CReplObject *)hObject;
}

/*++
--*/
STDMETHODIMP_(BOOL) CStore::CopyObject
(
    HREPLOBJ    hObjSrc,    // handle to the source object
    HREPLOBJ    hObjDst     // handle to the destination object
)
{
    CReplObject *pObjSrc = (CReplObject *)hObjSrc;
    CReplObject *pObjDst = (CReplObject *)hObjDst;

    if ( pObjSrc->m_uType != pObjDst->m_uType )
        return FALSE;

    switch( pObjSrc->m_uType )
    {
    case OT_ITEM:
        ((CItem *)pObjDst)->m_uid = ((CItem *)pObjSrc)->m_uid;
        ((CItem *)pObjDst)->m_ftModified = ((CItem *)pObjSrc)->m_ftModified;
        break;

    case OT_FOLDER:
        break;
    }

    return TRUE;
}

/*++
--*/
STDMETHODIMP CStore::IsValidObject
( 
    HREPLFLD    hFolder,    // handle of the folder where this item belongs
    HREPLITEM   hItem,      // handle of the object, could be NULL
    UINT        uFlags      // Reserved. Must be 0.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItem = (CItem *)hItem;
    PSTOCK  pStock;

    if ( pFolder )
    {
        if ( pFolder->m_uType != OT_FOLDER )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );
    }

    if ( pItem )
    {
        if ( pFolder->m_uType != OT_ITEM )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );

        Lock();
        pStock = FindStock( pItem->m_uid );
        Unlock();

        if ( !pStock )
            return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
    }

    return NOERROR;
}

//
// ============= folder related routines  ================
//
/*++
--*/
STDMETHODIMP CStore::GetFolderInfo
(
    LPSTR           lpszName,       // Name of the object type. It's taken from the registry.
    HREPLFLD        *phFolder,      // Output pointers, points to the handle of the new folder 
    IUnknown        **ppObjHandler  // Output pointers, points to the object handler of this object type
)
{
    CFolder *pFolder = (CFolder *)*phFolder;
    BOOL    fNew = (pFolder == NULL);

    // either set up the new CFolder class (when fNew is TRUE) or reinitialize the class (when fNew is FALSE)
    if ( fNew )
        pFolder = new CFolder;

    *phFolder = (HREPLFLD)pFolder;
    *ppObjHandler = m_pObjHandler;

    // we need only to set m_fChange to TRUE here since IsFolderChanged need only to return TRUE once
    pFolder->m_fChanged = TRUE;

    return NOERROR;
}

/*++
--*/
STDMETHODIMP CStore::IsFolderChanged
(
    HREPLFLD    hFolder,    // Handle of the folder
    BOOL        *pfChanged  // Points to a Boolean that will be set to TRUE if folder is changed
)
{
    // since we support real time detection of changes/deletes, we can simply return FALSE here.
    if ( pfChanged )
        *pfChanged = ((CFolder *)hFolder)->m_fChanged;

    ((CFolder *)hFolder)->m_fChanged = FALSE;
    return NOERROR;
}



//
// ============= Enumeration of folder objects ================
//

/*++
--*/
STDMETHODIMP CStore::FindFirstItem
(
    HREPLFLD    hFolder,        // handle to a folder
    HREPLITEM   *phItem,        // Output, points to the handle of the new object
    BOOL        *pfExist        // Output, points to a boolean value that will be set to TRUE if there is an object in the folder
)
{
    UINT    ix;
    CFolder *pFolder = (CFolder *)hFolder;

    // file should be opened by now and make sure enumeration is not nested
    if ( m_hFile == INVALID_HANDLE_VALUE || m_rgpItems )
        return E_UNEXPECTED;

    // take a snap shot of the stock allocate and allocate all items at once
    Lock();

    m_ixCurrItem = 0;
    m_cItems = m_pStocks->cStocks;
    m_rgpItems = new PITEM[ m_cItems ];
    if ( !m_rgpItems )
    {
        Unlock();
        return E_OUTOFMEMORY;
    }

    for ( ix = 0; ix < m_cItems; ix++ )
    {
        m_rgpItems[ix] = new CItem;
        if ( !m_rgpItems[ix] ) 
        {
            Unlock();
            return E_OUTOFMEMORY;
        }
        m_rgpItems[ix]->m_uid           = m_pStocks->rgStocks[ix].uidStock;
        m_rgpItems[ix]->m_ftModified    = m_pStocks->rgStocks[ix].ftLastModified;
    }

    // we don't need change/delete log any more
    m_pStocks->cChg = m_pStocks->cDel = 0;

    Unlock();
    return FindNextItem( hFolder, phItem, pfExist );
}

/*++
--*/
STDMETHODIMP CStore::FindNextItem
(
    HREPLFLD    hFolder,        // handle to a folder
    HREPLITEM   *phItem,        // Output, points to the handle of the new object
    BOOL        *pfExist        // Output, points to a boolean value that will be set to TRUE if there is an object in the folder
)
{
    CFolder *pFolder = (CFolder *)hFolder;

    if ( pfExist )
        *pfExist = FALSE;

    if ( !m_rgpItems ) 
        return E_UNEXPECTED;

    if ( m_ixCurrItem < m_cItems )
    {
        *phItem = (HREPLITEM)m_rgpItems[ m_ixCurrItem ];

        // now ActiveSync service manager owns the handle, reset ours to NULL so it won't be deleted
        m_rgpItems[ m_ixCurrItem ] = NULL;

        m_ixCurrItem++;

        if ( pfExist )
            *pfExist = TRUE;
    }
    return NOERROR;
}

/*++
--*/
STDMETHODIMP CStore::FindItemClose
(
    HREPLFLD    hFolder        // handle to a folder
)
{
    DWORD   dw;

    if ( !m_rgpItems ) 
        return E_UNEXPECTED;

    delete [] m_rgpItems;
    m_rgpItems = NULL;

    // spawn a second thread to listen to changes, if we haven't do so already
    if ( !m_hListen )
        m_hListen = CreateThread( NULL, 0, Listen, (LPVOID)this, 0, &dw );

    return NOERROR;
}

//
// ================== object related routines ================
//

/*++
--*/
STDMETHODIMP_(int) CStore::CompareItem
(
    HREPLITEM hItem1,  // Points to the handle of first object. This handle is guaranteed to be created by IReplStore::FindFirstObject or IReplStore::FindNextObject
    HREPLITEM hItem2   // Points to the handle of second object. This handle is guaranteed to be created by IReplStore::FindFirstObject or IReplStore::FindNextObject
)
{
    CItem   *pItem1 = (CItem *)hItem1;
    CItem   *pItem2 = (CItem *)hItem2;

    if ( pItem1->m_uid == pItem2->m_uid )
        return 0;

    if ( pItem1->m_uid < pItem2->m_uid )
        return -1;

    return 1;
}

/*++
--*/
STDMETHODIMP_(BOOL) CStore::IsItemChanged
(
    HREPLFLD    hFolder,    // Handle of a folder
    HREPLITEM   hItem,      // Handle of an object
    HREPLITEM   hItemComp   // Handle of the object used for comparison, could be NULL
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItem = (CItem *)hItem;
    CItem   *pItemComp = (CItem *)hItemComp;
    BOOL    fChanged = FALSE;

    if ( pItemComp )
        fChanged = CompareFileTime( &pItem->m_ftModified, &pItemComp->m_ftModified );
    else
    {
        PSTOCK      pStock;

        // read the modification time stamp from the object into ft
        Lock();
        pStock = FindStock( pItem->m_uid );
        fChanged = pStock && CompareFileTime( &pItem->m_ftModified, &pStock->ftLastModified );
        Unlock();
    }

    return fChanged;
}

/*++
--*/
STDMETHODIMP_(BOOL) CStore::IsItemReplicated
(
    HREPLFLD    hFolder,    // Handle of a folder
    HREPLITEM   hItem       // Handle of an object
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItem = (CItem *)hItem;
    PSTOCK  pStock;
    char    cSym;

    // hItem can be passed NULL.
    if ( pItem == NULL )
        return TRUE;

    // check if pItem should be replicated using information stored both in pFolder & pItem
    Lock();
    pStock = FindStock( pItem->m_uid );
    if ( pStock )
        cSym = pStock->szSym[0]; 
    Unlock();

    if ( !pStock ) return FALSE;

    switch ( v_uSyncOpt )
    {
    case SO_ALL:    return TRUE;
    case SO_AM:     return cSym >= 'A' && cSym <= 'M';
    case SO_NZ:     return cSym >= 'N' && cSym <= 'Z';
    }

    return FALSE;
}

/*++
--*/
STDMETHODIMP_(void) CStore::UpdateItem
(
    HREPLFLD    hFolder,    // Handle of a folder
    HREPLITEM   hItemDst,   // Handle of the destination object
    HREPLITEM   hItemSrc    // Handle to the source object, could be NULL.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItemDst = (CItem *)hItemDst;
    CItem   *pItemSrc = (CItem *)hItemSrc;

    if ( pItemSrc )
    {
        pItemDst->m_ftModified = pItemSrc->m_ftModified;
    }
    else 
    {
        // Update the time stamp stored in the given handle
        Lock();
        PSTOCK pStock = FindStock( pItemDst->m_uid );
        if ( pStock )
            pItemDst->m_ftModified = pStock->ftLastModified;
        Unlock();
    }
}

//
//  ==================== UI related routines =====================
//

/*++
--*/
STDMETHODIMP CStore::GetConflictInfo( PCONFINFO pConfInfo )
{
    // make sure we have the right version of OBJUIDATA
    if ( pConfInfo->cbStruct != sizeof( CONFINFO ) )
        return E_INVALIDARG;

    lstrcpy( pConfInfo->szLocalName, "Stock" );
    lstrcpy( pConfInfo->szRemoteName, "Stock" );

    CItem   *pLocalItem = (CItem *)pConfInfo->hLocalItem;
    CItem   *pRemoteItem = (CItem *)pConfInfo->hRemoteItem;
    PSTOCK  pLocalStock, pRemoteStock;

    Lock();

    pLocalStock = FindStock( pLocalItem->m_uid );
    pRemoteStock = FindStock( pRemoteItem->m_uid );
    if ( pLocalStock && pRemoteStock )
    {
        // resolve the conflict automatically if two stocks are considered identical
        if ( !lstrcmpi( pLocalStock->szCompany, pRemoteStock->szCompany ) && 
             !lstrcmpi( pLocalStock->szSym, pRemoteStock->szSym ) && 
             !lstrcmpi( pLocalStock->szLastPrice, pRemoteStock->szLastPrice ) && 
             !lstrcmpi( pLocalStock->szPurDate, pRemoteStock->szPurDate ) && 
             !lstrcmpi( pLocalStock->szPurPrice, pRemoteStock->szPurPrice ) )
        {
            Unlock();
            return RERR_IGNORE;
        }
    }

    if ( pLocalStock )
        wsprintf( pConfInfo->szLocalDesc, "%s\r\nPrice: %s\r\nPur. Price: %s", 
                  pLocalStock->szCompany, pLocalStock->szLastPrice,
                  pLocalStock->szPurPrice, pLocalStock->szCompany );
   
    if ( pRemoteStock )
        wsprintf( pConfInfo->szRemoteDesc, "%s\r\nPrice: %s\r\nPur. Price: %s", 
                  pRemoteStock->szCompany, pRemoteStock->szLastPrice,
                  pRemoteStock->szPurPrice, pRemoteStock->szCompany );

    Unlock();
    return NOERROR;
}


BOOL CALLBACK dlgSyncOpt( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    switch( uMsg )
    {
    case WM_INITDIALOG:
        if ( v_uSyncOpt == SO_AM )
            CheckDlgButton( hDlg, IDC_SYNC_AM, TRUE );
        else if ( v_uSyncOpt == SO_NZ )
            CheckDlgButton( hDlg, IDC_SYNC_NZ, TRUE );
        else
            CheckDlgButton( hDlg, IDC_SYNC_ALL, TRUE );

        SetDlgItemText( hDlg, IDC_DB_VOL, v_szDBVol );
        SetDlgItemText( hDlg, IDC_FILE, v_pStore->m_szStockFile );
        return TRUE;

    case WM_COMMAND:
        switch( LOWORD( wParam ) )
        {
        case IDOK:
            if ( IsDlgButtonChecked( hDlg, IDC_SYNC_AM ) )
                v_uSyncOpt = SO_AM;
            else if ( IsDlgButtonChecked( hDlg, IDC_SYNC_NZ ) )
                v_uSyncOpt = SO_NZ;
            else
                v_uSyncOpt = SO_ALL;

            GetDlgItemText( hDlg, IDC_FILE, v_szStockFile, sizeof( v_szStockFile ) );

            EndDialog( hDlg, IDOK ); 
            break;

        case IDCANCEL:      
            EndDialog( hDlg, IDCANCEL ); 
            break;
        };
        break;
    }
    return FALSE;
}

/*++
--*/
STDMETHODIMP CStore::ActivateDialog
( 
    UINT            uDlg,           // Which dialog should be actiavted
    HWND            hwndParent,     // Handle of the window that should be used as parent for the dialog
    HREPLFLD        hFolder,        // Points to a valid STD for the folder
    IEnumReplItem   *penum          // Points to a enumerator of object STD for objects stored in the folder
)
{
    HRESULT     hr;
    SDREQUEST   sd;
    WCHAR       wszDBVol[ MAX_PATH ];

    if ( uDlg != OPTIONS_DIALOG )
        return E_NOTIMPL;

    v_pStore = this;
    lstrcpy( v_szStockFile, m_szStockFile );

    // call device to get the database volume name, the QueryDevice will return error if no device is connected
    memset( wszDBVol, 0, sizeof( wszDBVol ) );
    memset( &sd, 0, sizeof( sd ) );
    lstrcpy( sd.szObjType, SZ_STOCKPOR );
    sd.fSet     = FALSE;                // we are reading data from device
    sd.uCode    = 1;                    // we can have up to 8 different code
    sd.lpbData  = (LPBYTE)wszDBVol;    // we are passing a buffer to the call, 
                                        // we can also pass NULL, in which case, 
                                        // we must free the buffer using GlobalFree after we are done
    sd.cbData   = sizeof( wszDBVol );

    hr = m_pNotify->QueryDevice( QDC_SYNC_DATA, (LPVOID *)&sd );
    if ( hr == RERR_NO_DEVICE )
        lstrcpy( v_szDBVol, "<Device is not connected>" );
    else if ( FAILED( hr ) )
        lstrcpy( v_szDBVol, "<Erroring reading data from device>" );
    else if ( wszDBVol[0] == 0 )
        lstrcpy( v_szDBVol, "System Volume" );
    else
    {
        // need to convert Unicode
        WideCharToMultiByte( CP_ACP, 0, wszDBVol, -1, v_szDBVol, sizeof( v_szDBVol ) - 1, NULL, NULL );
    }

    if ( DialogBox( v_hInst, TEXT( "SyncOptDlg" ), hwndParent, (DLGPROC)dlgSyncOpt ) == IDOK )
    {
        // see if the stock file is changed, and if the new file is valid
        if ( lstrcmpi( v_szStockFile, m_szStockFile ) && GetFileAttributes( v_szStockFile ) != (DWORD)-1 )
        {
            // save the new file into registry
            HKEY    hKey;

            // get the correct registry key for stock sync options
            if ( SUCCEEDED( m_pNotify->QueryDevice( ( m_uFlags & ISF_SELECTED_DEVICE )? QDC_SEL_DEVICE_KEY : QDC_CON_DEVICE_KEY, (LPVOID *)&hKey ) ) )
            {
                // read the hKey for the stock portfolio file to sync
                RegSetValueEx( hKey, "Stock File", NULL, REG_SZ, (const LPBYTE)v_szStockFile, lstrlen( v_szStockFile ) + 1 );
                RegSetValueEx( hKey, "Sync Option", NULL, REG_DWORD, (const LPBYTE)&v_uSyncOpt, sizeof( v_uSyncOpt ) );

                RegCloseKey( hKey );

                // ask ActiveSync service manager to unload service providers so the new option can be applied
                return RERR_UNLOAD;
            }
        }

        return NOERROR;
    }

    return RERR_CANCEL;
}

/*++
--*/
STDMETHODIMP CStore::GetObjTypeUIData
( 
    HREPLFLD    hFolder,        // Input, points to a STD of a folder that stores the object 
    POBJUIDATA  pData           // Output, points to a OBJUIDATA structure.
)
{
    // make sure we have the right version of OBJUIDATA
    if ( pData->cbStruct != sizeof( OBJUIDATA ) )
        return E_INVALIDARG;

    pData->hIconLarge = (HICON)LoadImage( v_hInst, MAKEINTRESOURCE( IDI_ICON ), IMAGE_ICON, 32, 32, 0 );
    pData->hIconSmall = (HICON)LoadImage( v_hInst, MAKEINTRESOURCE( IDI_ICON ), IMAGE_ICON, 16, 16, 0 );

    lstrcpy( pData->szName, "Stock Portfolio Data" );
    lstrcpy( pData->szTypeText, "Database" );
    lstrcpy( pData->szPlTypeText, "Databases" );

    lstrcpy( pData->szSyncText, m_szStockFile );

    return E_NOTIMPL;
}


LPSTR MakeMapObjName( LPSTR lpszFile )
{
static char v_szMapObjName[ MAX_PATH ];
    UINT    ix;
    LPSTR   lpsz;
    
    for ( ix = 0, lpsz = lpszFile; *lpsz; ix++, lpsz++ )
    {
        if ( *lpsz >= 'a' && *lpsz <= 'z' )
            v_szMapObjName[ix] = *lpsz - 'a' + 'A';
        else if ( *lpsz >= 'A' && *lpsz <= 'Z' )
            v_szMapObjName[ix] = *lpsz;
        else
            v_szMapObjName[ix] = 'A';
    }
    v_szMapObjName[ix] = 0;
    return v_szMapObjName;
}

/*++
HRESULT CStore::Open
    Open the file named by m_szStockFile
--*/
HRESULT CStore::Open( BOOL fCreateNew )
{
    // is it open already?
    if ( m_hFile != INVALID_HANDLE_VALUE )
        return NOERROR;

    BOOL    fNewFile = ( GetFileAttributes( m_szStockFile ) == (DWORD)-1 );
    HRESULT hr = fNewFile && !fCreateNew? E_FAIL : NOERROR;

    if ( FAILED( hr ) ) goto Exit;   

    // need to create this file if it doesn't exist
    if ( fNewFile )
        m_hFile = CreateFile( m_szStockFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 
                              NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
    else
        m_hFile = CreateFile( m_szStockFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 
                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

    if ( m_hFile == INVALID_HANDLE_VALUE )
    {
        hr = E_FAIL;
        goto Exit;
    }

    m_hMapObj = CreateFileMapping( m_hFile, NULL, PAGE_READWRITE, 0, sizeof( PORTFILE ), MakeMapObjName( m_szStockFile ) );
    if ( m_hMapObj == NULL )
    {
        hr = E_FAIL;
        goto Exit;
    }

    // Get a pointer to the file-mapped shared memory:
    m_pStocks = (PPORTFILE)MapViewOfFile( m_hMapObj, FILE_MAP_WRITE, 0, 0, 0 );
    if ( m_pStocks == NULL ) 
    {
        hr = E_FAIL;
        goto Exit;
    }

    // Get the Mutex
    if ( !m_hMutex )
        m_hMutex = CreateMutex( NULL, FALSE, SZ_MUTEX );

    if ( !m_hMutex )
    {
        hr = E_FAIL;
        goto Exit;
    }
    
    if ( fNewFile )
    {
        Lock();
        ClearStruct( *m_pStocks );
        m_pStocks->uVer1        = PORTFILE_VERSION;
        m_pStocks->uVer2        = PORTFILE_VERSION;
        m_pStocks->uidCurrStock = 1;
        FlushViewOfFile( 0, sizeof( PORTFILE ) );
        Unlock();
    }
    else
    {
        Lock();
        if ( m_pStocks->uVer1 != PORTFILE_VERSION || m_pStocks->uVer2 != PORTFILE_VERSION )
        {
            hr = E_UNEXPECTED;
            Unlock();
            goto Exit;
        }
        Unlock();
    }

Exit:
    return hr;
}


/*++
HRESULT CStore::Close
    Close the file named by m_szStockFile
--*/
HRESULT CStore::Close( void )
{
    if ( m_hMutex )
    {
        CloseHandle( m_hMutex );
        m_hMutex = NULL;
    }

    if ( m_pStocks )
    {
        UnmapViewOfFile( m_pStocks );
        m_pStocks = NULL;
    }
    
    if ( m_hMapObj )
    {
        CloseHandle( m_hMapObj );
        m_hMapObj = NULL;
    }
    
    if ( m_hFile != INVALID_HANDLE_VALUE )
    {
        CloseHandle( m_hFile );
        m_hFile = INVALID_HANDLE_VALUE;
    }

    return NOERROR;
}

/*++
PSTOCK CStore::FindStock
    Find a stock using the stock ID, return NULL if not found.
    m_pStocks must be protected by the mutex before calling this routine and
    before finish using the pointer it returns
--*/
PSTOCK CStore::FindStock( UINT uidStock, PUINT puix )
{
    UINT    ix;
    for ( ix = 0; ix < m_pStocks->cStocks; ix++  )
        if ( m_pStocks->rgStocks[ix].uidStock == uidStock )
            break;

    if ( puix )
        *puix = ix < m_pStocks->cStocks? ix : (UINT)-1;

    return ix < m_pStocks->cStocks? m_pStocks->rgStocks + ix: NULL;
}


/*++
--*/
PITEM CStore::MakeNewItem( UINT uidStock )
{
    PITEM   pItem = new CItem;
    PSTOCK  pStock;
    if ( pItem )
    {
        pItem->m_uid = uidStock;

        // set the time stamp if we can
        Lock();
        pStock = FindStock( uidStock );
        if ( pStock )
            pItem->m_ftModified = pStock->ftLastModified;
        Unlock();
    }
    return pItem;
}

desktop\sync\sthand.cpp—Implement IReplObjHandler interface

/*++
Module Name:   
      replhand.cpp

Abstract:
    Implementation of IReplObjHandler class
--*/

#include <windows.h>
#include "mainmod.h"

void GetLocalFileTime( FILETIME *pft )
{
    SYSTEMTIME  st;
    GetLocalTime( &st );
    SystemTimeToFileTime( &st, pft );
}

CDataHandler::CDataHandler( CStore *pStore )
{
    m_pStore = pStore;
}

CDataHandler::~CDataHandler()
{
}

STDMETHODIMP CDataHandler::QueryInterface( REFIID iid, LPVOID  *ppvObj )
{
    *ppvObj = NULL;   // set to NULL, in case we fail.

    if ( IsEqualIID( iid, IID_IUnknown ) )
        *ppvObj = (void*)this;

    if ( *ppvObj )
    {
        ((IUnknown *)(*ppvObj))->AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CDataHandler::AddRef()
{
    // don't need reference counting
    return 0;
}
STDMETHODIMP_(ULONG) CDataHandler::Release()
{
    // don't need reference counting
    return 0;
}


STDMETHODIMP CDataHandler::Setup
( 
    PREPLSETUP pSetup   // Points to REPLSETUP, which has information about the object 
                        // to be serialized/deserialized
)
{
    // we could be reading and writing at the same time, so need to save the pointer 
    // to setup struct differently
    if ( pSetup->fRead )
        m_pReadSetup = pSetup;
    else
        m_pWriteSetup = pSetup;

    return NOERROR;
}

BOOL CALLBACK FindStockWin( HWND hwnd, LPARAM lParam )
{
    char    szName[ MAX_PATH * 2 ];

    GetClassName( hwnd, szName, sizeof( szName ) );
    if ( !lstrcmp( szName, SZ_WND_CLASS ) )
        PostMessage( hwnd, WM_DATA_CHANGED, 0, 0 );

    return TRUE;
}

STDMETHODIMP CDataHandler::Reset
( 
    PREPLSETUP pSetup   // Points to REPLSETUP, which has information about the object 
                        // just serialized/deserialized
)
{
    // we don't have any resources we need to free
    return NOERROR;
}

STDMETHODIMP CDataHandler::GetPacket
( 
    LPBYTE  *lppbPacket, 
    DWORD   *pcbPacket, 
    DWORD   cbRecommend 
)
{
    PSTOCK      pStock;

    if ( m_pReadSetup->hItem == NULL )
        return E_UNEXPECTED;
    m_pStore->Lock();

    // setup the packet
    ClearStruct( m_packet );
    pStock = m_pStore->FindStock( ((PITEM)m_pReadSetup->hItem)->m_uid );
    if ( pStock )
    {
        MultiByteToWideChar( CP_ACP, 0, pStock->szSym, -1, m_packet.wszSym, 
                             sizeof( m_packet.wszSym ) - 1 );
        MultiByteToWideChar( CP_ACP, 0, pStock->szCompany, -1, m_packet.wszCompany,
                             sizeof( m_packet.wszCompany ) - 1 );
        MultiByteToWideChar( CP_ACP, 0, pStock->szLastPrice, -1, m_packet.wszLastPrice,
                             sizeof( m_packet.wszLastPrice ) - 1 );
        MultiByteToWideChar( CP_ACP, 0, pStock->szPurDate, -1, m_packet.wszPurDate,
                             sizeof( m_packet.wszPurDate ) - 1 );
        MultiByteToWideChar( CP_ACP, 0, pStock->szPurPrice, -1, m_packet.wszPurPrice,
                             sizeof( m_packet.wszPurPrice ) - 1 );
        MultiByteToWideChar( CP_ACP, 0, pStock->szGain, -1, m_packet.wszGain,
                             sizeof( m_packet.wszGain ) - 1 );
        m_packet.ftUpdated = pStock->ftUpdated;
    }

    m_pStore->Unlock();

    *pcbPacket = sizeof( m_packet );
    *lppbPacket = (LPBYTE)&m_packet;

    return pStock? RWRN_LAST_PACKET : E_UNEXPECTED;
}

STDMETHODIMP CDataHandler::SetPacket( LPBYTE lpbPacket, DWORD cbPacket )
{
    // write the packet
    if ( cbPacket != sizeof( STPACKET ) )
        return E_UNEXPECTED;

    PSTPACKET   pPacket = (PSTPACKET)lpbPacket;
    PSTOCK      pStock = NULL;
    PITEM       pItem = new CItem;
    STOCK       stock;

    if ( !pItem )
        return E_OUTOFMEMORY;

    m_pStore->Lock();

    // write the packet
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszSym, -1, stock.szSym, 
                         sizeof( stock.szSym ) - 1, NULL, NULL );
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszCompany, -1, stock.szCompany, 
                         sizeof( stock.szCompany ) - 1, NULL, NULL );
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszLastPrice, -1, stock.szLastPrice, 
                         sizeof( stock.szLastPrice ) - 1, NULL, NULL );
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszPurDate, -1, stock.szPurDate, 
                         sizeof( stock.szPurDate ) - 1, NULL, NULL );
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszPurPrice, -1, stock.szPurPrice, 
                         sizeof( stock.szPurPrice ) - 1, NULL, NULL );
    WideCharToMultiByte( CP_ACP, 0, pPacket->wszGain, -1, stock.szGain, 
                         sizeof( stock.szGain ) - 1, NULL, NULL );
    stock.ftUpdated = pPacket->ftUpdated;
    GetLocalFileTime( &stock.ftLastModified );

    // change an existing stock or create a new stock
    if ( m_pWriteSetup->hItem )
    {
        pStock = m_pStore->FindStock( ((PITEM)m_pWriteSetup->hItem)->m_uid );
        if ( pStock )
        {
            stock.uidStock = pStock->uidStock;
            stock.uFlags = pStock->uFlags;
            stock.ftViewTime = pStock->ftViewTime;
            *pStock = stock;
        }
    }
    else
    {
        if ( m_pStore->m_pStocks->cStocks < MAX_STOCKS - 1 )
        {
            stock.uidStock = m_pStore->m_pStocks->uidCurrStock;
            stock.ftViewTime = stock.ftLastModified;
            m_pStore->m_pStocks->uidCurrStock++;
            m_pStore->m_pStocks->rgStocks[ m_pStore->m_pStocks->cStocks ] = stock;
            m_pStore->m_pStocks->cStocks++;
            pStock = &stock;
        }
    }

    if ( pStock )
    {
        pItem->m_uid = pStock->uidStock;
        pItem->m_ftModified = stock.ftLastModified;
    }

    m_pStore->Unlock();

    // find all instance of Stock Portfolio application and post a message to 
    // let it refersh
    if ( pStock )
    {
        m_pWriteSetup->hItem = (HREPLITEM)pItem;
        EnumWindows( (WNDENUMPROC)FindStockWin, NULL );
    }
    else
        delete pItem;

    return pStock? NOERROR : E_UNEXPECTED;
}

STDMETHODIMP CDataHandler::DeleteObj
( 
    PREPLSETUP  pSetup
)
{
    UINT    ix;
    PSTOCK  pStock;

    if ( !pSetup->hItem ) return E_UNEXPECTED;
    m_pStore->Lock();

    pStock = m_pStore->FindStock( ((PITEM)pSetup->hItem)->m_uid, &ix );
    if ( !pStock )
    {
        m_pStore->Unlock();
        return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
    }

    m_pStore->m_pStocks->cStocks--;
    if ( ix != m_pStore->m_pStocks->cStocks )
        memmove( m_pStore->m_pStocks->rgStocks + ix, 
                 m_pStore->m_pStocks->rgStocks + ix + 1, 
                 ( m_pStore->m_pStocks->cStocks - ix ) * 
                 sizeof( m_pStore->m_pStocks->rgStocks[0] ) );

    m_pStore->Unlock();

    // find all instance of Stock Portfolio application and post a message 
    // to let it refersh
    EnumWindows( (WNDENUMPROC)FindStockWin, NULL );
    return NOERROR;
}

STDMETHODIMP CStore::RemoveDuplicates
( 
    LPSTR   lpszObjType,  // Points to the name of object type for which this operation 
                          // is intended. NULL if all object types should be checked.
    UINT    uFlags        // Reserved. Always 0.
)
{
    if ( lpszObjType && lstrcmp( lpszObjType, SZ_STOCKPOR ) )
        return E_NOTIMPL;

    UINT    ix, jx;
    PSTOCK  ps1, ps2;
    BOOL    fUpdate = FALSE;

    Lock();

    for ( ix = 0, ps1 = m_pStocks->rgStocks; ix < m_pStocks->cStocks; ix++, ps1++ )
    {
        for ( jx = ix + 1, ps2 = m_pStocks->rgStocks + jx; 
              jx < m_pStocks->cStocks; jx++, ps2++ )
        {
            if ( !lstrcmp( ps1->szSym, ps2->szSym ) &&
                 !lstrcmp( ps1->szCompany, ps2->szCompany ) &&
                 !lstrcmp( ps1->szLastPrice, ps2->szLastPrice ) &&
                 !lstrcmp( ps1->szPurPrice, ps2->szPurPrice ) &&
                 !lstrcmp( ps1->szPurDate, ps2->szPurDate ) &&
                 !lstrcmp( ps1->szGain, ps2->szGain ) )
            {
                // notify ActiveSync service manager that this stock is deleted
                PITEM   pItem = MakeNewItem( ps2->uidStock );
                if ( pItem &&
                     FAILED( m_pNotify->OnItemNotify( RNC_MODIFIED, 
                                 SZ_STORE_PROG_ID, SZ_STOCKPOR, (HREPLITEM)pItem, 0 ) ) )
                        delete pItem;

                // remove this stock
                m_pStocks->cStocks--;
                if ( jx != m_pStocks->cStocks )
                    memmove( ps2, ps2 + 1, ( m_pStocks->cStocks - jx ) * 
                             sizeof( m_pStocks->rgStocks[0] ) );
                jx--;

                fUpdate = TRUE;
                continue;
            }
        }
    }

    Unlock();

    if ( fUpdate )
    {
        // find all instance of Stock Portfolio application and post a message 
        // to let it refersh
        EnumWindows( (WNDENUMPROC)FindStockWin, NULL );
    }
    return NOERROR;
}

-------------------------------------------------------------------------------------------------