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
ProgID
key, in this case, would be MS.WinCE.Outlook.
The CLSID is used by COM (CoCreateInstance) to create an instance
of the store interfaces.
After the service provider is registered, you must register each
object type it synchronizes in a subdirectory under
HKEY_LOCAL_MACHINE. The following example registers the object
types Appointment, Contact, and Task:
HKEY_LOCAL_MACHINE Software Microsoft Windows CE
Services Services Synchronization Objects
Appointment
Contact
Task
Each object type name is a key. Under each key, you must define
the five values: default, Display Name, Plural Name, Store, and
Disabled. For the appointment object type in our Microsoft Outlook
example, we have the following values:
[Default] "Outlook Appointment Object" Display
Name "Appointment" Plural Name "Appointments" Store
"MS.WinCE.Outlook" Disabled 0
The default is a descriptive name of the synchronization object.
The Display Name and Plural Names are text, which will be displayed
in various user interfaces. The
Store
value is the name of the
ProgID
of the ActiveSync service provider.
Disabled
tells if synchronization of this object type
should be disabled by default.
Whenever a new device is connected to the desktop and a new
device profile is to be created in Mobile Device Folder (this is
the desktop program where user manages device profiles), the
registry keys for the synchronization objects under
HKEY_LOCAL_MACHINE
are automatically copied to
HKEY_CURRENT_USER
. In our example, we would have
Appointment, Contact, and Tasks registered as follows:
HKEY_CURRENT_USER Software Microsoft Windows CE
Services Partners <Device ID> Services Synchronization
Objects Appointment Contact Tasks
The registration on the device is similar but simpler. All you
need to do is register the ActiveSync device component under
HKEY_LOCAL_MACHINE. The following shows the proper registration for
our Microsoft Outlook example:
HKEY_LOCAL_MACHINE Windows CE Services
Synchronization Objects
Appointment
Contact
Tasks
Again, each object type name is a key, but now only two values
are defined: Store and Display Name. For the Appointment object
type in our example, we would define:
Store "pegobj.dll" Display Name
"Appointment"
Here, the Store refers to the DLL which exports the functions
for this object type, while Display Name is the same as it was for
the desktop registration under HKEY_LOCAL_MACHINE.
The Sample StockPor
Application
To help developers to develop ActiveSync service providers,
Windows CE Software Development Kit (SDK) provides sample
application named
StockPor. Here's a listing of files:
Directory |
File Name |
Description |
|
stockpor.cpp
|
Source file shared by both desktop and device
application. |
|
common.h
|
Header file for structures and constants shared by
the application and the ActiveSync provider. |
|
resource.h
|
Header file for resource constants. |
|
stockpor.rc
|
Resource file shared by both desktop and device
application. |
desktop
for the desktop application
|
stocks.h
|
Header file for structures and constants used by
desktop application. |
|
stockpor.ico
|
Desktop application icon file. |
|
stocks.cpp
|
The rest of desktop application source code. |
desktop\sync
for ActiveSync service provider's desktop component
|
guids.cpp
|
Source file for the GUIDs used. |
|
stsync.def
|
Module definition file needed for the desktop
DLL. |
|
stsync.rc
|
Resource file. |
|
stsync.reg
|
Registry setup file. |
|
mainmod.h
|
Header file for classes, structures and
constants. |
|
sthand.cpp
|
Source file that implements IreplObjHandler. |
|
mainmod.cpp
|
Source file that implements IreplStore. |
device
for the device application
|
stockpor.ico
|
Device application icon file. |
|
stocks.cpp
|
The rest of device application source code. |
|
stocks.h
|
Header file for structures and constants used by
device application. |
|
devsetup.cpp
|
Source file for the device setup application. |
device\sync
for ActiveSync service provider's device component
|
stdevs.def
|
Module definition file needed for the device
DLL. |
|
stdevs.h
|
Header file for structures and constants. |
|
stdevs.cpp
|
Source file that implements the ActiveSync device
component. |
The StockPor application can be used to keep track of one's
stock holdings. The application runs on both the desktop PC and the
device and uses the same user interface. An ActiveSync service
provider is implemented such that any changes or deletions made on
either side can be synchronized automatically. The object type is
named "Stock". After the ActiveSync service provider is installed
in Windows CE Services, "Stock" appears in the ActiveSync status
window in the same way as "Appointment" or "Task" is displayed for
Microsoft Outlook.
Figure 2. ActiveSync Status window
The desktop application shares much of the user interface code
with the device application. All platform-dependent code and data
are separated into a class named CStock, which is defined
differently for the desktop and the device, as illustrated in the
following code from stockpor.cpp:
// define class CStocks #ifdef UNDER_CE #include
"device\stocks.h" #else #include "desktop\stocks.h" #endif
The CStore class defines many platform-dependent methods:
Method Name |
Description |
BOOL Open( LPSTR lpszFile, BOOL fFailOnNew)
|
Open the stock portfolio database. |
BOOL SetupDlg( HWND hDlg, UINT uParam )
|
Set up a dialog for user to change the stock
data. |
BOOL Add( HWND hDlg )
|
Add a new stock from the given dialog. |
BOOL Change( HWND hDlg, UINT uParam )
|
Change the stock data. |
void Delete( UINT uParam )
|
Delete the selected stock. |
void OnDataChange( void )
|
Called whenever stock data is changed. |
BOOL BeforeAddChg( void )
|
Called before a stock is about to be added or
changed. |
For the desktop, these methods are implemented in the source
file desktop\stocks.cpp. For the device, these methods are
implemented in device\stocks.cpp.
The following chapters explain the sample application and the
ActiveSync service provider in detail.
The Desktop StockPor
Application
Figure 3. Stock Portfolio application on desktop PC
The user interface is straightforward. The
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_DEVICE
passed 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_CONNECTED
is set if the device is remotely
connected, for example, through a modem or an Ethernet card.
Whenever this flag is set, the ActiveSync provider should avoid
using any blocking UI, such as MessageBox or a dialog box. The
provider should instead take any default actions without prompting
the user on the desktop because, when a device is connected
remotely, a user may not be able to respond to the user
interface.
In general,
IReplStore::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:
- After detecting a conflict, the ActiveSync manager sends down a
command to the device ActiveSync provider to read the object.
IReplObjHandler::GetPacket is called on the device.
- The packets for the object are brought up and sent to the
desktop ActiveSync provider. A temporary object could be created on
the desktop. IReplObjHandler::SetPacket is called on the desktop
and a new HREPLITEM handle is created and returned to the
ActiveSync manager.
- The ActiveSync manager sets the handles for both objects in
CONFINFO structure in call to IReplStore::GetConflictInfo. The
ActiveSync provider can use these two handles to extract
information from the objects and save them in CONFINFO.
- Using the description text returned in the CONFINFO structure,
the ActiveSync manager presents the standard conflict resolution
dialog to the user.
- IReplObjHandler::DeleteObject is called to delete the temporary
object.
- If user chooses to skip, nothing is done and conflict
resolution on this item will begin again in the next
synchronization.
- If the user chooses device-win, the desktop object is marked
up-to-date so the device object can be brought to the desktop.
- If the user chooses desktop-win, the device object is marked
up-to-date so the desktop object can be sent to the device later.
The ActiveSync manager sets the RSF_CONFLICT_OBJECT flag in all
calls to IReplObjHandler methods.
See
Flowchart 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.
- Define how many different types of object you need to
synchronize. Name each object type reasonably. If the name
coincides with another ActiveSync name, the original service
provider will be replaced. It makes sense to develop a single
ActiveSync provider to support synchronization of object types that
are similar or exist in the same store. For example, one ActiveSync
provider is developed to synchronize Appointment, Contact, Task,
and Message
object types.
- Define the object ID of objects in each object type.
- Determine how objects in each object type can be enumerated.
- Determine how to check whether an object has changed or not.
Typically, a time stamp of the last modification is used. A change
number (a number incremented by one every time an object is
changed) can also be used.
- Define the structure used for HREPLITEM and HREPLFLD.
Typically, HREPLITEM is cast into a structure that contains the
object ID, the timestamp and any other object specific data.
HREPLFLD is cast into a different structure that contains the
filter for the object type.
- Define a unique ProgID for the store. Example:
MS.WinCE.Outlook. Obtain a GUID for the store.
- Implement various methods of the desktop interfaces and device
functions
- The implementation of
IReplStore::BytesToObject,
IReplStore::ObjectToBytes,
IReplStore::CompareItem, and
ObjectNotifymust be efficient because they are called
frequently.
- Read the section
"Questions and Answers"and make
sure all points are taken care of.
- Compile, configure, and test the ActiveSync service provider.
What's New in ActiveSync for
Windows CE 2.1
Windows CE version 2.1 is the operating system used in devices
such as the Handheld PC Pro. It has many improvements over Windows
CE version 2.0, which is the operating system used by the Handheld
PC. For example, you can now create independent database volumes in
the internal file system or on a storage card (PC Card or Compact
Flash Card).
Each database volume is given a unique CEGUID. Each record in
each database is assigned with an object ID. This ID is unique only
within the volume.
A new device function,
FindObjects, is added to support synchronization of database
volumes. Using this function, you can directly enumerate all
objects that you want to synchronize and return a list of object
IDs to the ActiveSync manager. If you synchronize more than one
volume, you need to return multiple lists, one for each volume.
The following is the prototype for
FindObjects:
typedef HRESULT (*PFINDOBJECTS)( PFINDOBJINFO
);
The following is the definition of FINDOBJINFO structure used by
the function:
#define FO_MORE_VOLUME ((UINT)0x00000001) #define
FO_DONE_ONE_VOL ((UINT)0x00000002) typedef struct tagFindObjInfo {
UINT uFlags; // See FO_* above OBJTYPENAME szObjType; // what
object type we need to enumerate UINT *poid; // points to list of
object ID's, // first part is for unchanged objects, // last part
is for changed objects UINT cUnChg; // # of unchanged object ID's
in above list UINT cChg; // # of changed object ID's in above list
LPBYTE lpbVolumeID; // ID of the volume where all above objects
lives. // NULL if the objects are in RAM UINT cbVolumeID; // size
of above ID in bytes LPVOID lpvUser; // anything provider wants in
this variable } FINDOBJINFO, *PFINDOBJINFO;
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;
}
-------------------------------------------------------------------------------------------------