Windows CE 2.1 Technical Articles  

Programmer's Guide for ActiveSync

Important:
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

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 hereto 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 objectis 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 typeis the name for a group of synchronization objects. For example, "appointment" for the set of appointments in Microsoft Outlook. Folderis similar to object typeand 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 storecontains 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: IReplObjHandlerand IReplStore. The device module also implements the IReplObjHandlerinterface and six functions: InitObjType, ObjectNotify, GetObjTypeInfo, ReportStatus, FindObjects,and SyncData.The implementation of ReportStatus is optional. The FindObjectsand SyncDatafunctions 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 IReplObjHandlerinterface.

Figure 1. ActiveSync structural diagram

Desktop Interfaces

IReplStoreis 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

    IReplObjHandleris 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 IReplNotifyfor 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 FindObjectsand SyncData(see the section "What's New in ActiveSync for Windows CE 2.1"for details). It can also optionally implement and export ReportStatus. InitObjTypeis used to both initialize and terminate a device ActiveSync provider. ObjectNotifyis used to handle object identification and change detection. GetObjTypeInfois used to retrieve device information about the object type. ReportStatusallows 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>\ProgIDHKEY_CLASSES_ROOT\<ProgID>\CLSID

    The Defaultvalue of the InprocServer32key is the full path of the 32-bit DLL that implements the IReplStoreinterface. For example, for Microsoft Outlook, the Default value would be the full path to outstore.dll. The Default value of the ProgIDkey, 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 Storevalue is the name of the ProgIDof the ActiveSync service provider. Disabledtells 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_MACHINEare 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 Opencommand opens a data file, Add Stockadds a new stock, Change Stockchanges information about one stock, Delete Stockdeletes 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::uidCurrStockis 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::rgidChgis an array of object IDs for changed stocks. PORTFILE::rgidDelis 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_FLAGSis 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 ObjectNotifyfunction 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: IReplStoreand 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::FindNextItemor 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::CompareItemchecks 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::CompareItemwill 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::FreeObjectto 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::ObjectToBytesto convert a HREPLITEM or HREPLFLD to a series of bytes and IReplStore::BytesToObjectto 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::GetStoreInfoto 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::uTimerResaccordingly. If STOREINFO::uTimerResis set to a nonzero time in microseconds, the ActiveSync manager automatically starts enumeration of the store once every interval. If STOREINFO::uTimerResis 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::GetStoreInfofirst with STOREINFO::cbMaxStoreIdset to 0. The ActiveSync provider should set the required size for a store ID in STOREINFO::cbStoreIdand 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::Initializeensures 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_DEVICEpassed into IReplStore::Initializeis 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_CONNECTEDis 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::Initializeis 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::Initializebeing called first.

        the ActiveSync manager calls IReplStore::GetObjTypeUIDatato 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 1in 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::ReportStatuswith 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::FindFirstItemis 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::FindItemCloseis 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::IsItemReplicatedto implement the filter.

        If the enumeration takes more than a few seconds to complete, the ActiveSync provider can call IReplNotify::SetStatusTextto 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::IsFolderChangedis called. When the IReplStore::IsFolderChangedis 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::GetFolderInfofor 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 2in 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::FindFirstItemor 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::IsItemChangedto see if the object is changed since last time it is synchronized. If so, the ActiveSync manager calls IReplStore::CopyObjectto copy the data from the returned handle into the handle it saves. It then calls IReplStore::IsItemReplicatedto 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:

        • OnItemNotifynotifies 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.
        • SetStatusTextsets 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.
        • GetWindowreturns a Window handle that is used as a parent window of any modal dialog or message box.
        • QueryDevicereturns 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::OnItemNotifyto 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::OnItemNotifywith 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 4in 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::OnItemNotifycall).

          IReplObjHandleris 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 IReplObjHandlerinterface 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, IReplObjHandlerinterface 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 3in 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::DeleteObjectis 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 5in 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::GetConflictInfois 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::dwFlagswhen 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::RemoveDuplicatesfunction. 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::SetPacketis 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::Resetmethod. Note, do not use the structure passed in IReplObjHandler::Setup. Also, it should set REPLSETUP::hItemto 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::ActivateDialogso 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::GetWindowto 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::ActivateDialogcan return RERR_UNLOAD. If the user cancels the option dialog, IReplStore::ActivateDialogshould 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::ActivateDialogis called. But there is a big difference with setting options for a connected device. If the device is not yet connected, IReplStore::Initializemay not be called as the first method into the store. The ActiveSync provider needs to make sure IReplStore::GetStoreInfodoesn't return an error even when IReplStore::Initializehas been called yet. As mentioned in the earlier section "Initialization and Termination,"there are a number of methods that may be called before IReplStore::Initializein 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 ObjectNotifyfunction 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 IReplObjHandlerinterface. All packets received are guaranteed to be in the exact same order and of the exact same size.

                      Recommended Steps to Develop the ActiveSync Providers

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

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

                        FindObjectsis 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 FindObjectsagain, 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 FindObjectson 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::SetPacketmust create a new HREPLITEM handle and set it in the hItem member of the REPLSETUP structure passed in the IReplObjHandler::Setupcall. 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::Setupbefore a IReplObjHandler::Resetcall. One call to IReplObjHandler::Setupmay 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::SetPacketin 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::GetConflictInfocan 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::SetPacketcan 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::IsFolderChangedby 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::ReportStatusfunction 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::IsItemChangedmethod. 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::ReportStatuswith RSC_DATE_CHANGED for each and every object. An ActiveSync provider typically resets a bit flag in the given HREPLITEM so that when IReplStore::IsItemReplicatedis 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::GetStoreInforeturn any error code when IReplStore::Initializeis 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::Initializeand 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::IsFolderChangedto always set *pfChanged to FALSE. You need to set it to *pfChanged when IReplStore::IsFolderChangedis 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::uTimerResto –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::IsValidObjectto 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::RemoveDuplicatescauses the ActiveSync manager to start the synchronization process again. Returning any other error code causes the ActiveSync manager to call IReplStore::RemoveDuplicatesagain 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::ReportStatuswith 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::ReportStatuswith 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::Initializeis 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::dwFlagsgiven in the IReplObjHandler::Setupcall.

                        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::OnItemNotifywith 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::ReportStatuswith 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::ReportStatusis 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::RemoveDuplicatesafter successful synchronization.

                        0: No need to call IReplStore::RemoveDuplicates.

                        Last Checked Date N/A The last date IReplStore::ReportStatusis 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;
                        }

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