Overview of Connection Services

One of the fastest-growing market segments in the computing world today is for portable desktop companions. In creating the Windows CE operating system (OS), Microsoft fulfilled a need for a compact, scalable OS that communicates with the Internet, networks, and desktop computers while delivering the same familiar and easy to use Windows interface. Products such as the Handheld PC and the Palm-size PC device categories deliver connectivity that allows a strong partnership between a Windows CE–based device and the Windows-based desktop computer.

To accommodate the need for this highly functional connectivity, Windows CE provides a multitude of functions that allow communication between applications on the desktop computer and the remote Windows CE–based device.


Enabling a Partnership in Windows CE

This chapter discusses the tools and components that you use to design applications that enable functional partnerships between a desktop computer and a Windows CE–based device. By using the following components, you program applications with the capability of making Windows CE–based devices the perfect mobile companion to a desktop computer:

  • Remote API (RAPI)
  • File filters
  • Connection notification
  • CEUTIL functions
  • Windows CE Services

The following illustration shows the relationship between the various components that are available on the Windows CE OS and Windows CE–based devices.


RAPI

This set of application programming interfaces (APIs) allows applications running on the desktop computer to invoke functions directly on the remote Windows CE–based device. Windows CE provides one-way remote API (RAPI), with which the Windows CE–based device is the RAPI server and the desktop computer is the RAPI client. The application runs on the desktop computer—the client side—which calls functions that are executed on the Windows CE–based device, the server side.

RAPI under Windows CE is designed so that desktop computer applications can manage the Windows CE–based device remotely. The exported functions relate to the registry, file system, and databases, as well as to functions for querying the system configuration. While most RAPI functions are duplicates of functions in the Windows CE API, a few functions extend the API. You use these functions to initialize the RAPI subsystem and enhance performance of the communication link by compressing iterative operations into one RAPI call.

Essentially, RAPI is a remote procedure call (RPC). RAPI communicates requests from a desktop computer application to invoke a function and returns the results of that function.


File Filters

Windows CE file filters are Component Object Model (COM) objects that exist on the desktop computer. They are loaded and called by Windows CE Services, which is an application that runs on the desktop computer and enables connection with a Windows CE–based device. When a file is copied to or from the Windows CE–based device to the desktop computer by using Windows CE Services, Windows CE Services checks to see whether a file converter is registered for the file type being transferred. If so, Windows CE Services loads the file filter and issues a request to convert the file. This all takes place on the desktop computer (the client side of the link), where most processing between the desktop computer and the device takes place.

If a file is being exported from a Windows CE–based device to the desktop computer, it is copied in its original form to the desktop computer, converted by the file filter, and stored on the desktop computer. In much the same way, if a file is being imported to a Windows CE–based device, it is converted by the file filter, and then the file is copied to the Windows CE–based device.

Windows CE file filters are dependent on the Mobile Devices folder that is provided by Windows CE Services. Only files that are moved to and from a Windows CE–based device by users dragging them to the Mobile Devices folder are converted. If a file is transferred to a Windows CE–based device by any other method, such as downloading a file from the Internet by using Pocket Inbox, the file filter is not loaded and the file is not converted.


Connection Notification

Windows CE Services gives you two ways of notifying desktop computer-based applications when a connection is made with a Windows CE–based device:

  • Registry-based notification, in which all the applications that are listed under a given registry are launched. When a connection is broken, all applications that are listed under a different key are launched.
  • COM interface–based notification, which involves two interfaces: IDccManSink, which must be implemented by the application seeking notification, and IDccMan, which is provided by Rapi.dll.

Registry-based notification is appropriate for applications that do not need some control of the connection manager or the ability to register and deregister for connection notifications. The COM interface–based notification method is more complex than registry-based notification; however, it provides you with some control of the connection manager and the ability to register and deregister for connection notifications.


CEUTIL Functions

Windows CE Services uses the registry on the desktop computer to store large amounts of information about the Windows CE–based devices that have created a partnership with the desktop computer. Windows CE Services uses the registry on the desktop computer to store configuration information. While most of these registry keys are documented, if you access them by name you are assuming that those key names always remain the same. However, this might not be the case, especially in international versions of Windows where registry keys can be in a different language.

The CEUTIL DLL exports functions that provide an abstraction layer over the registry keys that are used by Windows CE Services. Using this DLL allows a desktop computer application to query the devices that are currently registered, and to add or delete registry values underneath the keys that hold data for specific devices. The CEUTIL DLL does not communicate with a remote Windows CE–based device. Instead, it looks in the desktop registry for information that has previously been placed there by Windows CE Services.

You can use the CEUTIL DLL to manage desktop registry entries for Windows CE Services; register desktop file filters and synchronization services; access device-partnership settings that are used for both file filters and synchronization services; and add custom menu items to Windows CE Explorer.


Windows CE Services

The Windows CE system comprises three components:

  • The OS, which runs on the Windows CE–based device.
  • The Windows CE–based device, which runs Windows CE–based applications.
  • Windows CE Services, an application that provides connection services and runs on the desktop computer.

Windows CE Services is an application that runs on the desktop computer and facilitates connections with the Windows CE–based device. The helper DLLs, communications support, and functionality described in the preceding sections are available in the Windows CE Services application. Though not supported through the Windows CE OS, Windows CE Services is shipped with Windows CE platforms, such as a Handheld PC running Microsoft Windows CE for the Handheld PC.

You add components to Windows CE Services to allow data sharing between a desktop computer and a platform-specific Windows CE–based device, and also between a Windows CE–based application and its counterpart on the desktop computer. By defining application-specific file filters and registering applications with Windows CE Services, you ensure data transmission and conversion capability between desktop computer and Windows CE based–device formats.

Users open Windows CE Services by double-clicking the Mobile Devices icon on their desktop computer. In the resulting window, users can establish a connection partnership between their desktop computer and the Windows CE–based device by choosing options from the Partnership Wizard during initial setup. Later, users can change connection settings by choosing options that are available on the Mobile Devices menu. The following screen shot shows the Tools menu in the Mobile Devices window.

Once the connection partnership process is complete, Windows CE Services provides the following functionality between the desktop computer and the Windows CE–based device:

  • Data synchronization
  • File conversion between the desktop computer and device formats
  • Importing and exporting database tables
  • Preparing the desktop for remote connections

Additionally, Windows CE Services provides the following functionality on the Windows CE–based device:

  • Backing up and restoring device data
  • Adding and removing programs


Synchronizing Data with ActiveSync

Windows CE Services contains Microsoft ActiveSync™ technology, which provides data replication and synchronization. ActiveSync does this by comparing the data on a Windows CE–based device with the data on the desktop computer to which the device is connected. After comparing data, ActiveSync updates both the device and the desktop computer with the most recent information.

ActiveSync is composed of the service manager and the service provider. The service manager is a synchronization engine that is built into Windows CE Services and resides on both the desktop computer and the Windows CE–based device. The service provider comprises two modules that you must implement in your application to perform the synchronization tasks that are specific to your data. One module, called the desktop provider module, resides on the desktop computer and the other module, called the device provider module, resides on the device. By creating and registering the service provider, you decide which data is tracked for changes by the service manager. And by creating the proper file converters, you enable ActiveSync to maintain the same information on a device as on the desktop computer.

Built-in functionality gives users the option of choosing a subset of information to synchronize. For example, they can synchronize only e-mail, only certain files, only their Pocket Outlook data, or only certain categories of information that they define, such as business contacts or personal appointments. The following screen shot shows the ActiveSync Options page for the Handheld PC.

ActiveSync gives a user the option to synchronize data either manually, automatically on connection, or continuously while a connection is active so that changes appear immediately on both the device and the desktop computer. In addition, the user can synchronize both at their desktop computer and away from it, over serial, infrared, Ethernet LAN, and modem connections. Users can synchronize their device information with two desktop computers, such as their home and office computer, or they can synchronize information on one desktop computer with several devices—for example, to share files within a department.


Backing Up and Restoring Device Data

Using the built-in Windows CE Services backup and restore feature, a user can create a backup file on their desktop computer containing all of the files, databases, Pocket Outlook data, RAM-based programs, and other information on their device. If their device data becomes lost or corrupted, they can restore it from this backup file.


Transferring Files Between a Device and the Desktop Computer

Windows CE Services provides the Mobile Devices folder in Microsoft Windows Explorer on a user’s desktop computer. When a device is connected to the desktop computer, this folder provides a view of the files and folders on the device. Users can move, copy, and automatically convert files between the device and the desktop computer by dragging them to and from this folder.


Adding Programs to and Removing Programs from a Device

The Application Manager, in the Mobile Devices folder, enables users to see programs that are currently installed on their device, as well as applications on their desktop computer that are available for installation on the device. They can also add programs to and remove programs from the device or a memory card.


Importing and Exporting Database Tables

When the user chooses the Import Database Table command on the Tools menu of the Mobile Devices folder, an Open window is exposed in which the user can select the location of the database. This option can be used to move database tables from the desktop computer to the device. Alternately, by choosing Export Database Table, the user initiates the copy and convert process. During this process, database information from the device is collected, converted, and transferred to the selected location on the desktop computer.


Preparing for Remote Connection

If users want to establish a remote connection between their desktop computer and their device, Windows CE Services helps them prepare their device for the remote connection. They follow a series of steps to set up a dial-up connection to a modem, a dial-up connection to a network, or a network (Ethernet) connection.


Synchronizing Data

Microsoft Windows CE Services includes Microsoft® ActiveSync™, which allows you to synchronize, or coordinate, data stored on a Windows-based desktop computer with data stored on a Windows CE–based device. ActiveSync also enables you to coordinate device data with two or more computers or synchronize information on one computer with several devices.

Note ActiveSync does not support synchronization between two devices, a device and a server, or a device and mounted volumes.

Through serial, infrared, Ethernet LAN, or modem connections, the synchronization process automatically transfers and tracks data changes, including additions, deletions, and other revisions to information. As a result, shared information is always up to date and accurate. ActiveSync is a client/server architecture that consists of a service manager (the server) and a service provider (the client):

  • The service manager, which is built into Windows CE Services, is a synchronization engine that resides on both the desktop and the device. The service manager performs many common synchronization tasks, which include providing connectivity, detecting changes in data, resolving data conflicts, as well as mapping and transferring data objects.
  • The service provider comprises two modules, which typically are dynamic-link libraries (DLLs), that you must implement in your application to perform the synchronization tasks that are specific to your data. One module, called the desktop provider module, resides on the desktop and the other module, called the device provider module, resides on the device.

The service provider interfaces with both the service manager and the application, thereby exposing the application and the application’s data to the service manager. In turn, this interaction enables the service manager to synchronize different types of data from different types of applications. The service provider also facilitates all requests made by the service manager, such as displaying a user interface (UI) or reporting status.

Creating a service provider is a two-part process:

  1. Create the service provider by developing the desktop provider module and the device provider module.
  2. Register the service provider on both the Windows-based computer and the Windows CE–based device.

The service manager implements a single interface, IReplNotify, which is built into ActiveSync and Windows CE Services. Thus, once you register the service provider, the synchronization process is automatic. The service manager, with the help of the service provider, directs and controls all synchronization tasks.

The following illustration shows how the service manager interfaces with the service provider to access data.


Creating an ActiveSync Service Provider

The synchronization process transfers only data that has changed. For this reason, the service provider must track data changes as efficiently as possible to ensure that the least amount of data is transferred during synchronization.

To create an efficient ActiveSync service provider for an application, you first must analyze the data to be synchronized by defining the object, the object type, and the object identifier, as well as the folder and the store to hold the objects. You also must decide on a method to compare objects and a method to indicate that an object has changed. As you do this, keep in mind the following terminology and requirements, which are specific to the synchronization process:

  • An object is a logical unit of data, such as a single appointment. The object definition depends on your application. The definition can be an appointment, an address, or some other item.
  • An object type is a name for a particular group of objects contained in a folder. For example, appointment is an object type naming all appointments in a Microsoft Schedule+ folder.
  • An object identifier is a value that uniquely identifies an object. The object identifier must satisfy the following criteria:
    • It cannot change once it is created.
    • It cannot be reused for any other object.
    • It must be ordered, so that you can determine which object comes first.
    • It must signify changes since the last synchronization.

    For example, globally unique identifiers (GUIDs) satisfy the criteria. The criteria allow the service manager to compare object identifiers to aid in synchronization.

  • The store is a database that holds the data, or objects, to be synchronized. The store must accommodate the objects, but its format can vary. The store can be a flat file, a database, or some other custom format.

Once you have analyzed the data, you are ready to create a service provider by developing the desktop provider module and the device provider module:

  • The desktop provider module handles the bulk of communication with the service manager and implements two Component Object Model (COM) interfaces: IReplStore and IReplObjHandler:
COM interface
Description
IReplStore | Enumerates objects in a store, checks for changes in an object, and displays a UI so that the user can set synchronization options and resolve conflicts.
IReplObjHandler | Serializes data and deletes objects.

  • The device provider module must implement the same IReplObjHandler interface as the desktop provider module, as well as the following functions:
Function
Description
InitObjType | Initializes data and returns a pointer to the IReplObjHandler interface when the device provider module loads and, when the device provider module terminates, frees allocated resources.
ObjectNotify | Calls the service manager when an object in the object store for a device is changed or deleted.
GetObjNotify | Returns the type of the specified object.
ReportStatus | Optional; gets status for synchronization objects.


Developing the Desktop Provider Module

In contrast to the device provider, which has limited functionality, the desktop provider handles the bulk of all communication with the manager. The desktop provider, hereafter referred to simply as the provider, implements two COM interfaces: IReplStore and IReplObjHandler. IReplStore enumerates objects in a store, checks for changes in an object, and displays a user interface so that the user can set synchronization options and resolve conflicts. IReplObjHandler serializes data and deletes objects. For more information on the methods contained in IReplStore and IReplObjHandler, see the Windows CE Programmer Reference.

To implement the desktop provider module

  1. Create one GUID for the store, by using the Microsoft Visual C++® GUID generator tool, Guidgen.exe.
  2. Initialize the store.
  3. Compare store identifiers and handle mismapped store identifiers.
  4. Define object handles to provide access to data objects stored on a device.

    The object handles typically are pointers to data structures and include HREPLITEM, HREPLFLD, and HREPLOBJ. HREPLOBJ is a generic handle to either an item or a folder.

  5. Provide a folder handle for the specified object type and, to access folders, return a pointer to the IReplObjHandler interface.
  6. Enumerate objects for a specified object type.
  7. Detect changes in object types.
  8. Send and receive objects.
  9. Create a UI to set synchronization options for a device and handle conflicts.


Initializing the Store

When a connection event occurs, the service manager calls IReplStore::Initialize to ensure that the data file for the application exists and is open. The service manager:

  • Initializes the data store and compares store identifiers to determine if the desktop object and the device object are mapped correctly.
  • Accesses the data store and enumerates each desktop object.
  • Determines what data has changed and transfers that data to the device.

To accomplish these tasks, the service manager places a series of calls to the desktop provider module, prompting the desktop provider module to respond with the necessary information.

The following illustration shows the order in which the service manager calls the desktop provider module.

In order for the service manager to initialize the desktop provider module, you must store the name of the file that needs to be synchronized in the registry. The registry location for this file name depends on whether you are initializing the desktop provider module for a connected device or a selected device.

A selected device is a disconnected device that a user selects from the list of profiles stored in the Windows CE Services Mobile Device folder. A user selects a profile to change synchronization options. When a user selects a device, the service manager passes the bit flag ISF_SELECTED_DEVICE into IReplStore::Initialize.

To access the profile information for a device:

  • To get the registry key for a connected device profile, call IReplNotify::QueryDevice with QDC_CON_DEVICE_KEY.
  • To get the registry key for a selected device profile, call IReplNotify::QueryDevice with QDC_SEL_DEVICE_KEY.

If a device is remotely connected to the desktop, the service manager passes the ISF_REMOTE_CONNECTED bit flag into IReplStore::Initialize. Whenever this flag is set, the desktop provider module should not display any blocking UI, such as a message box or a dialog box, because the user may not be able to respond to the UI. Instead, the desktop provider module should accept all default actions, without prompting the user on the desktop.

If the service provider returns an error in IReplStore::Initialize, the service manager automatically displays an error message. If you want the desktop provider module rather than the service manager to display an error message that you create, return RERR_NO_ERR_PROMPT.

The service manager typically calls IReplStore::Initialize immediately after a connection event occurs. However, the service manager can call other methods first, such as when a user changes the synchronization options for a disconnected device.

The following table shows the methods that can be called before IReplStore::Initialize.

IReplStore method
Description
IReplStore::GetStoreInfo | Gets information about a store, which is displayed in the ActiveSync option dialog box.
IReplStore::GetObjTypeUIData | Gets information about an object type, which is displayed in the ActiveSync option dialog box under Status.
IReplStore::GetFolderInfo | Gives the folder handle, HREPLFLD, to the desktop provider module.
IReplStore::ActivateDialog | Allows a user to change options for an ActiveSync service.
IReplStore::BytesToObject | Converts a series of bytes to a HREPLITEM handle or a HREPLFLD handle.
IReplStore::ObjectToBytes | Converts a HREPLITEM handle or a HREPLFLD handle to a series of bytes.
IReplStore::ReportStatus | Informs the desktop provider module about events that are taking place. This method is optional.

The following code example shows how to implement IReplStore::Initialize.

STDMETHODIMP CStore::Initialize
(
    IReplNotify *pNotify,    // Pointer to the IReplNotify interface
    UINT uFlags              // Either ISF_SELECTED_DEVICE or
                            // ISF_REMOTE_CONNECTED
)
{
    m_pNotify = pNotify;
    m_uFlags = uFlags;

    // Get the correct registry for the synchronization options.
    HKEY hKey;
    HRESULT hr;
    hr = m_pNotify->QueryDevice( ( uFlags & ISF_SELECTED_DEVICE )?
            QDC_SEL_DEVICE_KEY : QDC_CON_DEVICE_KEY, (LPVOID *)&hKey );

 // Read the registry for the type of files to synchronize.
 // ...

    // The service provider should suppress all UI that requires
    // user input
    // if ISF_REMOTE_CONNECTED is set in uFlags.
    // ...

    return NOERROR;
}

CStore is a COM class whose base is IReplStore. The following code example shows the CStore definition.

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

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

    // ******** IUnknown methods **************
    // QueryInterface, AddRef, and Release

    // ******** IReplStore methods **************
    // Store manipulation routines
     // Initialize, GetStoreInfo, ReportStatus, CompareStoreIDs

    // Object-related routines
     // CompareItem, IsItemChanged, IsItemReplicated, UpdateItem

    // Folder-related routines
    // GetFolderInfo, IsFolderChanged

    // Enumeration of folder objects
    // FindFirstItem, FindNextItem, FindItemClose

    // STD management routines
     // ObjectToBytes, BytesToObject, FreeObject, CopyObject,
    // IsValidObject

    // UI-related routines
    // ActivateDialog, GetObjTypeUIData, GetConflictInfo,
    // RemoveDuplicates

private:
    IreplNotify        *m_pNotify;
    CDataHandler    *m_pObjHandler;
    UINT                m_uFlags;
};


Comparing Store Identifiers

When a user connects a device to a desktop:

  1. The service manager calls IReplStore::GetStoreInfo to retrieve the store’s identifier number.

    This number is stored in Repl.dat.

  2. The service manager reads the store identifier for the store to be synchronized and passes this identifier number to IReplStore::CompareStoreIDs.
  3. The desktop provider module determines whether this store identifier matches the one loaded from Repl.dat, which identifies the store that was used in the previous synchronization.

If the identifiers do not match, the service manager reestablishes the mapping between the desktop and device objects by issuing a request to the user either to combine two sets of data or to discard the device data and send all desktop objects to the device.

The combine and discard process is required when Repl.dat is corrupt and cannot be read, as well as when the user:

  • Synchronizes a device with existing data for the first time.
  • Uses an existing device to delete the device profile, and then reconnects the device to create a new partnership.
  • Chooses a desktop store that is different from the one that was used in the last synchronization. The service manager detects the new selection by comparing store identifiers.
  • Restores the device data from a backup file.
  • Does a discard operation on one computer, and then synchronizes the device with a second computer.

If a user chooses to combine the data, all objects on both the device and the desktop are marked as changed. If a user chooses to discard the data, all objects on the device are marked as deleted and all desktop objects are marked as changed. Synchronization occurs immediately after a user makes a selection.

Combining data typically creates duplicate objects in the desktop store. To find the duplicate objects in the desktop store and remove them, use IReplStore::RemoveDuplicates. The service manager calls this method after the first successful synchronization occurs. Once duplicated objects are removed, the desktop provider module returns control to the service manager to begin enumerating the store. Through enumeration, the service manager determines which desktop objects are deleted and informs the device to remove the corresponding device objects.

In addition to using IReplStore::GetStoreInfo to provide identification information, you can use this method to perform other tasks, such as changing the size of an object store or the time interval between enumerations. When the service manager calls IReplStore::GetStoreInfo, it passes the structure STOREINFO. Use STOREINFO to change store information:

  • To receive a variable-sized store identifier, the service manager calls IReplStore::GetStoreInfo with STOREINFO::cbMaxStoreId set to 0. This call should prompt the desktop provider module to set the required size for the store identifier in STOREINFO::cbStoreId and return E_OUTOFMEMORY. In response, the service manager allocates the required memory and passes a pointer to the store identifier in STOREINFO::lpbStoreId. The desktop provider module then can use this pointer to save the store identifier.
  • If your desktop provider module does not support real-time notification of changes, you need to set STOREINFO::uTimerRes:
    • To enable the service manager to automatically start enumerating the store at a set interval, set STOREINFO::uTimerRes to the non-zero time, in micro-seconds, that you want to use for the interval.
    • To enable the service manager to begin the enumeration process only when the user activates the ActiveSync status window or immediately before synchronization begins, set STOREINFO::uTimerRes to –1.

The following code example shows how to implement the IReplStore::CompareStoreIDs and IReplStore::GetStoreInfo methods and the STOREINFO structure.

STDMETHODIMP_(int) CStore::CompareStoreIDs
(
    LPBYTE  lpbID1,     // Points to the first store identifier
    UINT    cbID1,      // Size of the first store identifier
    LPBYTE  lpbID2,     // Points to the second store identifier
    UINT    cbID2       // Size of the second store identifier
)
{
    // If the size of the first store identifier is smaller than
    // the size of the second store identifier
    if ( cbID1 < cbID2 )
        return -1;

    // If the size of the first store identifier is larger than
    // the size of the second store identifier
    if ( cbID1 > cbID2 )
        return 1;

    return memcmp( lpbID1, lpbID2, cbID1 );
}

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

    pInfo->uFlags = SCF_SINGLE_THREAD | SCF_SIMULATE_RTS;

    // ProgId of the store. You can change this to match your company
    // and your product.
    lstrcpy( pInfo->szProgId, "MyCompany.WinCE.DeskSamp" );

    // The description of the store. This will be displayed to the user.
    // You can change this to suit your requirements.
    lstrcpy( pInfo->szStoreDesc, "Files" );

    // Let replication scan the store every 5 seconds.
    pInfo->uTimerRes = 5000;

    // Construct something that uniquely identifies the store. In this     // example, because the differences in the stores are
    //inconsequential,
    // set the size of the store identifier to any value.
    pInfo->cbStoreId = 10;

    // Compare the size of the the store identifier with the maximum
    // size of the store identifier.
    if ( pInfo->cbStoreId > pInfo->cbMaxStoreId )
        return E_OUTOFMEMORY;

    // Check if the pointer to the store identifier is NULL.
    if ( pInfo->lpbStoreId == NULL )
        return E_POINTER;

    memset( pInfo->lpbStoreId, 0, 10 );
    return NOERROR;
}


Accessing Objects

In order for the service manager to access a data object, you must provide an HREPLITEM handle to the object. HREPLITEM is an important data type because each handle uniquely identifies one object. To the service manager, this handle is a 32-bit number created by the desktop provider module. To the desktop provider module, this handle is a pointer to an internal structure or class instance.

Whenever the service manager needs information about the object, it calls methods in IReplStore or IReplObjHandler and passes the HREPLITEM of the object as a parameter. From the object identifier and the time stamp or version number contained in the HREPLITEM, the desktop provider module can determine whether two handles represent the same object, as well as which of the two handles represents a more recent version of the object.

To compare information, IReplStore::CompareItem checks the object identifiers contained in the two handles. The following table shows the possible return values for this method.

Value
Condition
1 | The first object identifier is greater than the second object identifier.
–1 | The first object identifier is less than the second object identifier.
0 | The first object identifier is equal to the second object identifier.

The ordering of the object identifiers allows the service manager to use a binary search on its table of object identifiers.

The following code example shows how to implement IReplStore::CompareItem.

STDMETHODIMP_(int) CStore::CompareItem
(
    HREPLITEM hItem1,    // Points to the handle of the first object.
    HREPLITEM hItem2        // Points to the handle of the second object.
                        // Both handles are guaranteed to be
                        // returned 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;
}

CFolder and CItem are COM classes based on CReplObject. The following code example shows the definition of these classes.

#define OT_ITEM     1
#define OT_FOLDER   2

class CReplObject
{
public:
    virtual ~CReplObject() {}
    UINT    m_uType;
};

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

class CItem: public CReplObject
{
public:
    CItem( void ) { m_uType = OT_ITEM;
                    memset( &m_ftModified, 0, sizeof( m_ftModified )); }
    virtual ~CItem() {}
    UINT        m_uid;
    FILETIME    m_ftModified;
};


Accessing Folders

For the service manager to access a folder, you must implement HREPLFLD, which is a handle that identifies a folder. This handle contains the filter for the object type and other object-specific information.

The service manager calls IReplStore::GetFolderInfo to obtain the folder handle. The service manager also calls IReplStore::IsFolderChanged, IReplStore::CopyObject, IReplStore::IsValidObject, and IReplStore::FreeObject to manipulate folder and item handles.

Because the service manager saves the data that is stored in the HREPLITEM or HREPLFLD handles to Repl.dat, which is a file that the service manager creates and maintains, you must also implement IReplStore::ObjectToBytes and IReplStore::ObjectToBytes.

The following code example shows how to implement IReplStore::GetFolderInfo.

STDMETHODIMP CStore::GetFolderInfo
(
    LPSTR lpszName,            // Name of the object type taken from
                            // the registry
    HREPLFLD *phFolder,        // Output pointers to the handle of the
                            // new folder
    IUnknown **ppObjHandler    // Output pointers to the pointer of
                            // the IReplObjHandler interface
)
{
    // Check if phFolder points to a handle that has NULL value.
    CFolder *pFolder = (CFolder *)*phFolder;
    BOOL fNew = (pFolder == NULL);

    // Create a new handle for the specific folder.
    if ( fNew )
        pFolder = new CFolder;

    // Either set up the new CFolder class (when fNew is TRUE) or
    // reinitialize the class (when fNew is FALSE).
    // ...

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

    return NOERROR;
}

To determine if any object contained in a folder has changed, the service manager calls IReplStore::IsFolderChanged. The service manager also can call the following methods to manipulate folder or item handles:

  • IReplStore::CopyObject to copy the data from one handle to another handle that represents the same object.
  • IReplStore::IsValidObject to ensure that the handle still represents a valid object, not a deleted object.
  • IReplStore::FreeObject to remove a handle from memory, thereby allowing the desktop provider module to free the memory resources that are used by the handle.

The following code examples show how to implement each of these methods.

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;

    // Check to see if the source and destination types are the same.
    if ( pObjSrc->m_uType != pObjDst->m_uType )
        return FALSE;

    switch( pObjSrc->m_uType )
    {
    case OT_ITEM:            // If the source object is an item
        ((CItem *)pObjDst)->m_uid = ((CItem *)pObjSrc)->m_uid;
        ((CItem *)pObjDst)->m_ftModified =
                            ((CItem *)pObjSrc)->m_ftModified;
        break;

    case OT_FOLDER:            // If the source object is a 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;

    if ( pFolder )
    {
        // Check whether hFolder is a valid folder handle.
        if ( pFolder->m_uType != OT_FOLDER )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );
    }

    if ( pItem )
    {
        // Check whether hItem is a valid item handle.
        if ( pFolder->m_uType != OT_ITEM )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );

         // Search for the item. If the item is not found, return
        // to HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ).
        // ...
    }

    return NOERROR;
}

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

The service manager saves the data stored in the HREPLITEM or HREPLFLD handles to Repl.dat. The desktop provider module must implement IReplStore::ObjectToBytes to convert an HREPLITEM object or HREPLFLD object into a series of bytes so that the service manager can save the data. The desktop provider module also must implement IReplStore::BytesToObject to convert the same series of bytes back to an object.

When a user connects a device to a desktop, the service manager reads Repl.dat and restores all handles that were used in the previous synchronization. As long as a device is connected, the service manager continues to save handles into Repl.dat.

The following code examples show how to implement IReplStore::ObjectToBytes and IReplStore::BytesToObject.

STDMETHODIMP_(UINT) CStore::ObjectToBytes
(
    HREPLOBJ    hObject,        // Handle to the object.
    LPBYTE      lpb           // Points to a buffer where the array of
                            // bytes should be stored; 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:            // If the object is a folder
        break;

    case OT_ITEM:            // If the object is an 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 stored; could be NULL.
    UINT    cb          // The 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:        // If the object is a folder
        pObject = pFolder = new CFolder;
        break;

    case OT_ITEM:        // If the object is an 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;
}


Enumerating Objects

Enumeration is the process of accessing each object in a directory or folder to determine whether it has changed and whether it meets any specified filtering criteria. The enumeration process is as follows:

  1. The service manager calls IReplStore::IsFolderChanged.
  2. If IReplStore::IsFolderChanged returns TRUE, the service manager calls IReplStore::FindFirstItem.
  3. If additional objects exist, IReplStore::FindFirstItem sets *pfExist to TRUE.
  4. The service manager calls IReplStore::FindNextItem repeatedly until *pfExist is FALSE, which indicates that there are no more objects.
  5. The service manager calls IReplStore::FindItemClose to free any resources that were used during enumeration.

Note If an enumeration takes more than a few seconds to complete, you can use IReplNotify::SetStatusText to have the desktop provider module display text that notifies the user how much time remains until completion.

The following code example shows how to implement IReplStore::FindFirstItem.

STDMETHODIMP CStore::FindFirstItem
(
    HREPLFLD hFolder,    // Handle to a folder
    HREPLITEM *phItem,    // Output pointer to the handle of the first
                        // item in the folder
    BOOL *pfExist        // Output pointer to a Boolean value that is set
                        // to TRUE if there is an object in the folder
)
{
    CFolder *pFolder = (CFolder *)hFolder;

    // Find the first item. pItem used in the following code points to
    // the first item.

// ...

    // Implementation should retrieve a unique identifier from
    // the first item.
    // pItem->m_uid = retrieved_unique_ID;

    // Implementation also should retrieve whatever it is using to         // verify if the first item is changed; for example, a time stamp.
    // pItem->m_ftModified = retrieved_time_stamp;

    *phItem = (HREPLITEM)pItem;

    if ( pfExist )
        *pfExist = TRUE;

    return NOERROR;
}

You can use the desktop provider module to filter synchronization objects during enumeration by returning to the service manager only those objects that meet your filtering criteria. For example, you may want to synchronize only appointments that fall within the next three days. The service manager accesses the filtering criteria—which is stored with the folder handle—by calling IReplStore::IsItemReplicated.

Note If a filter is implemented incorrectly, any object that fails the filter criteria appears as a deleted object on the desktop. If the desktop enumeration does not return the object, the service manager assumes that the object is deleted and deletes the corresponding device object. To avoid this, be sure that the desktop provider module returns every object in the store during enumeration.

You can reapply a filter as certain conditions change. For example, you may want to synchronize the appointments falling within the next three days, everyday. This would mean that the date criteria for the filter would change as the days pass. In order to determine exactly what date range to filter for, the service manager calls IReplStore::ReportStatus with RSC_DATE_CHANGED to initiate the synchronization process.


Detecting Desktop Object Changes

The service manager automatically detects object changes and deletions by comparing the list of handles that are returned during enumeration with the handles that are saved in Repl.dat. Before starting an enumeration process, the service manager:

  1. Marks a bit for each handle that is stored in Repl.dat.
  2. Calls IReplStore::FindFirstItem and IReplStore::FindNextItem.
  3. Performs a binary search on the handles in Repl.dat each time one of these methods returns a new handle, in order to find a handle representing the same object.
    • If no matching handle is found, it creates a new object on the desktop store.
    • If a matching handle is found, it clears the bit from the handle in Repl.dat and calls IReplStore::IsItemChanged to see if the object has changed since the last synchronization.

      If the object has changed, it calls IReplStore::CopyObject to copy the data from the returned handle into the handle that is saved in Repl.dat.

  4. Calls IReplStore::IsItemReplicated to see if it should send the object to the device.

All handles in Repl.dat that remain marked once enumeration is complete represent deleted objects.

To hasten the enumeration process, the desktop provider module can detect and report desktop changes to the service manager in real time. To do this:

  1. The service manager calls IReplNotify::OnItemNotify to inform the desktop provider module of the status for an object.
  2. The desktop provider module passes RNC_MODIFIED for a modified object, RNC_CREATED for a created object, and RNC_DELETED for a deleted object.
  3. The desktop provider module passes a handle to the object so that the service manager can search Repl.dat for the corresponding device object.

The following illustration shows the sequence of real-time notification calls.

The desktop provider module also can call IReplNotify::OnItemNotify with RNC_SHUTDOWN if it detects that the desktop application has closed. The service manager responds by unloading the desktop provider module and updating the status display.

The following code examples show how to implement IReplStore::IsItemChanged and IReplStore::IsItemReplicated.

STDMETHODIMP_(BOOL) CStore::IsItemChanged
(
    HREPLFLD    hFolder,    // Handle of the folder or the container
                            // that stores the object
    HREPLITEM   hItem,      // Handle of the object
    HREPLITEM   hItemComp   // Handle of the object that is used
                            // for comparison
)
{
    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
    {
        FILETIME ft;

        // Read the modification time stamp from the object into ft.
        // Compare it with the time stamp given in the object.
        fChanged = CompareFileTime( &pItem->m_ftModified, &ft );
    }
    return fChanged;
}

STDMETHODIMP_(BOOL) CStore::IsItemReplicated
(
    HREPLFLD    hFolder,    // Handle of the folder or the container
                            // that stores the object
    HREPLITEM   hItem       // Handle of the object
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem *pItem = (CItem *)hItem;

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

    // Search for the item; return FALSE if the item is not found.

    // Check if pItem should be replicated by using information stored
    //both in pFolder & pItem. If so, return TRUE.

    return FALSE;
}


Sending and Receiving Objects

You use IReplObjHandler to serialize or convert an object to a series of bytes. It also deserializes an object or converts the bytes back to an object to transmit data to a device or desktop. There is no limitation or specification on how to serialize an object. A desktop provider module can serialize the object into any number of bytes, and it can group these bytes into any number of packets.

Whenever an object requires serialization, the service manager calls the IReplObjHandler methods in the following order:

  1. IReplObjHandler::Setup to tell the desktop provider module what object to serialize and to enable the desktop provider module to allocate any resources that are required for serialization.
  2. IReplObjHandler::GetPacket to let the desktop provider module create one or more packets of bytes of any size.

    The service manager calls this method repeatedly until RWRN_LAST_PACKET is returned.

  3. IReplObjHandler::Reset to let the desktop provider module free any used resources.

    The service manager calls this method when serialization is completed.

The service manager does not recognize byte format during serialization. The service manager simply sends the packets in the same byte number and sequence as they were originally received.

The following illustration shows the sequence of calls necessary to send and receive data.

The following code example shows how to implement IReplObjHandler::Setup and IReplObjHandler::GetPacket. It also shows the definition for CDataHandler, which is a COM class whose base is IReplStore.

class CDataHandler : public IReplObjHandler
{
public:
    CDataHandler( CStore *pStore );
    ~CDataHandler();

    // ******** IUnknown methods **************
    // AddRef, Release, QueryInterface

    // ******** IReplObjHandler methods **************
    // Setup, Reset, GetPacket, SetPacket, DeleteObj

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

STDMETHODIMP CDataHandler::Setup
(
    PREPLSETUP pSetup    // Points a REPLSETUP structure, which contains // information about the object to be serialized // or deserialized.
)
{
    // Could be reading and writing at the same time, so it is necessary     // to save the pointer to set up the structure differently.
    if ( pSetup->fRead )
        m_pReadSetup = pSetup;
    else
        m_pWriteSetup = pSetup;

    return NOERROR;
}

STDMETHODIMP CDataHandler::GetPacket
(
    LPBYTE  *lppbPacket,        // Pointer to the pointer of the outgoing
                            // packet
    DWORD   *pcbPacket,        // Pointer to a DWORD for the packet size
    DWORD   cbRecommend         // Recommended maximum size of the packet
)
{
     if ( m_pReadSetup->hItem == NULL )
        return E_UNEXPECTED;

     // Initialize the packet.

// ...

    // Fill up the packet.

// ...

    // Assign the size of the packet to *pcbPacket and the packet
    // to *lppbPacket.
    //*pcbPacket = sizeof( packet );
    //*lppbPacket = (LPBYTE)&packet;

    return NOERROR;
    //Return RWRN_LAST_PACKET if it is the final packet.
}

During synchronization, the service manager creates an instance of IReplObjHandler for each object type. The desktop provider module recognizes when IReplObjHandler::SetPacket is sending information about a new object by checking for RSF_NEW_OBJECT in REPLSETUP::dwFlags. The service manager passes REPLSETUP::dwFlags in the IReplObjHandler::Setup call. The structure REPLSETUP is passed in IReplObjHandler::Setup.

The following table shows the REPLSETUP members that are used by the desktop provider module. All other members are internal to the service manager and should not be changed.

REPLSETUP member
Description
fRead Set to TRUE for reading an object from the desktop store.

Set to FALSE for writing an object to the desktop store.

dwFlags | A collection of bit flags related to serialization and deserialization.
Hfolder | A handle to the folder.
HItem | A handle to the object to be serialized. The desktop provider module uses the information in this handle to identify and convert the object into packets of bytes.

The process of receiving an object from the device is similar to sending an object to the device. After the packets of data arrive from the device, the service manager calls the IReplObjHandler interface methods, which enable the desktop provider module to convert those packets back into an object. The desktop provider module must take the data packets and create an object. It then must create a new HREPLITEM that represents the object in REPLSETUP::hItem.

The IReplObjhandler methods are always called in the following sequence:

  1. IReplObjHandler::Setup tells the desktop provider module what object to deserialize and enables the desktop provider module to allocate any resource needed for deserialization.
  2. IReplObjHandler::SetPacket sends packets to the desktop provider module so that it can recreate the object.

    Packets are sent in the exact same number, size, and sequence. The service manager calls this method repeatedly until the last packet is received from the device.

  3. IReplObjHandler::Reset enables the desktop provider module to free any used resources.

    The service manager calls this method when deserialization is completed.

When the service manager writes an object to the desktop store, it calls IReplStore::UpdateItem. This prompts the desktop provider module to open the object and update the HREPLITEM handle with a time stamp or version number, thus ensuring that the object is not marked as changed on the desktop.

The following code example shows how to implement IReplStore::UpdateItem.

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

    if ( pItemSrc )
    {
        pItemDst->m_ftModified = pItemSrc->m_ftModified;
    }
    else
    {
        // Implementation should update whatever it has used to validate
        // object changes, such as a time stamp, by reading it from the         // object.

        // Update the time stamp.
        // pItemDst->m_ftModified = time stamp read from object.
    }
}

Depending on the type of application, a desktop provider module may synchronize objects coming from a device as deletions. For example, when a user deletes an e-mail message on a device, the message is moved to the Deleted Item folder and marked as changed. The desktop provider module receives the changed object and automatically attempts to delete the message on the desktop. In response, IReplObjHandler::SetPacket returns one of the following errors:

  • RERR_DISCARD when the desktop provider module tries to delete the device object immediately after the change is synchronized. The service manager sends a command to the device to delete the corresponding object.
  • RERR_DISCARD_LOCAL when the desktop provider module tries to delete the desktop object immediately after the change is synchronized. The service manager calls IReplObjHandler::DeleteObject to delete the existing desktop object.


Handling Conflicts

A conflict occurs when an object has changed on both the device and the desktop since the last synchronization. To resolve a conflict, the service manager calls IReplObjHandler::GetPacket on the device to send the object to the desktop. The service manager then calls IReplObjHandler::SetPacket on the desktop to create a temporary object. During both the device and the desktop call, the service manager passes RSF_CONFLICT_OBJECT in REPLSETUP::dwFlags.

The following illustration shows the call sequence for conflict resolution.

After the service manager receives the data from the device, it calls IReplStore::GetConflictInfo and passes a handle to both the original desktop object and the temporary device object. Next, the desktop provider module must fill in the CONFINFO structure to customize the description text displayed in a standard Conflict Resolution dialog box, which is supplied by the service manager.

The following code example shows how to implement IReplStore::GetConflictInfo.

STDMETHODIMP CStore::GetConflictInfo
(
    PCONFINFO pConfInfo         // Oointer to a CONFINFO structure
)
{
    // Verify that you have the right version of OBJUIDATA.
    if ( pConfInfo->cbStruct != sizeof( CONFINFO ) )
        return E_INVALIDARG;

    // Copy “Stock” to szLocalName and szRemoteName. You can use your
    // own local and remote object names to replace “Stock”.
    lstrcpy( pConfInfo->szLocalName, "Stock" );
    lstrcpy( pConfInfo->szRemoteName, "Stock" );

    CItem   *pLocalItem = (CItem *)pConfInfo->hLocalItem;
    CItem   *pRemoteItem = (CItem *)pConfInfo->hRemoteItem;

    // Find the local object, and then the remote object.
    // pLocalObject points to the local object.
    // pRemoteObject points to the remote object.

    // If both the local and remote objects are found
    if (pLocalObject && pRemoteObject )
    {
        // Compare the local and remote objects.
        // If the local and remote objects are identical
        return RERR_IGNORE;
    }

    // If a local object is found
    if ( pLocalObject )
        // Store information for the local object
        // in pConfInfo->szLocalDesc.

    // If a remote object is found
    if ( pRemoteObject )
        // Store information for the remote object in
        // pConfInfo->szRemoteDesc.

    return NOERROR;
}

If the desktop provider module cannot write a temporary object on the desktop, it can save the packets into memory and return HREPLITEM containing a pointer to the memory location. In this case, the desktop provider module must implement this handle in all methods in IReplStore that accept an HREPLITEM handle, such as CopyObject or FreeObject. When the service manager calls IReplStore::GetConflictInfo, the handle becomes CONFINFO::hRemoteItem. The desktop provider module then can extract descriptive text from the handle and save it into CONFINFO.

The Conflict Resolution dialog box enables the user to:

  • Discard the original desktop object and keep the newly written device object.

    If the user chooses to keep the newly written device object, the service manager marks the desktop object as up to date and the device object as changed. This ensures that the device object is transferred to the desktop during the next synchronization.

  • Keep the original desktop object and discard the device object.

    If the user chooses to keep the desktop object, the service manager marks the device object as up-to-date.

In either case, the service manager calls IReplObjHandler::DeleteObject to delete the temporary object.

Conflict situations do not always require a Conflict Resolution dialog box. The following table describes special error values from IReplStore::GetConflictInfo that resolve the conflict automatically.

Error value
Action
RERR_IGNOR | The desktop provider module compares two handles in CONFINFO, determines that they are identical, and takes no action.
RERR_DISCARD | The service manager detects that the desktop object represented by a handle is already deleted and deletes the device object accordingly.
RERR_DISCARD_LOCAL | The service manager resolves a conflict by deleting a desktop object instead of allowing the desktop provider module to delete the object.


Setting Synchronization Options

For a user to change synchronization options, the desktop provider module must supply a UI that enables a user to select options. There is no limitation or specification for the UI.

A user can gain access to the UI in two ways:

  • If a device is connected to the desktop, a user selects a service provider from the desktop.
  • If the device is not connected to the desktop, a user selects a service provider from the Mobile Device folder.

Once a user selects a service provider, the service manager calls IReplStore::ActivateDialog, which displays the UI for the selected service provider. If the desktop provider module does not support a synchronization options dialog box, the call to IReplStore::ActiveDialog must return E_NOTIMPL. To get a handle to a parent window to display a dialog box or message box, have the desktop provider module call IReplNotify::GetWindow.

If a user chooses to remove a service provider, IReplStore::ActivateDialog returns RERR_UNLOAD. If a user cancels the synchronization options dialog box, IReplStore::ActivateDialog returns RERR_CANCEL.

You can save synchronization options for the desktop provider module either in the registry or in the HREPLFLD handle. If you save synchronization options in a HREPLFLD handle, the desktop provider module sets default option values in IReplStore::GetFolderInfo. If the pointer to which phFolder points is NULL, the desktop provider module must create a new HREPLFLD. If the pointer is not NULL, the desktop provider module loads the options from Repl.dat.


Developing the Device Provider Module

The service manager interfaces with the device provider module to access data. To facilitate this interaction, a device provider module must implement the same IReplObjHandler interface that you use for the desktop provider module. It also must implement the InitObjType, GetObjTypeInfo, ObjectNotify functions. The device provider module can also implement the ReportStatus function, which is optional.

To implement the device provider module

  1. Create one GUID for the store, by using the Visual C++ GUID generator tool, Guidgen.exe.
  2. Initialize the device store.
  3. Enumerate device objects.
  4. Detect device object changes and send and receive device data.

Initializing the Device Store

To initialize the device provider module, call InitObjType. If the device provider module supports synchronization of multiple object types, it calls InitObjType for each object type whose lpszObjType is not NULL. When an ActiveSync service terminates, the lpszObjType of its objects are set to NULL, thereby allowing the device provider module to free any resources it may have allocated.

The following code example shows how to implement InitObjType.

EXTERN_C BOOL InitObjType
(
    LPWSTR lpszObjType,
    IReplObjHandler **ppObjHandler,
    UINT uPartnerBit
)
{
    if ( lpszObjType == NULL )
    {
        // Terminates the device provider module and frees all
        // allocated resources.
        // ...

        return TRUE;
    }

    // Allocates a new IReplObjHandler.
    *ppObjHandler = new CDataHandler;

    // Saves the uPartnerBit so that you can use it later in
    // ObjectNotify.

    // Initializing the module.
    // ...

    return TRUE;
}

ActiveSync supports the synchronization of two desktop computers with a Windows CE–based device. To differentiate between two computers, the service manager passes a partner bit into InitObjType when initializing the device provider module. This bit is set to 1 if the connected desktop computer is the first partner and 2 if it is the second partner. The device provider module must use this partner bit when setting and resetting dirty bits of an object.


Enumerating Device Objects

Enumerating objects on a device differs from enumerating objects on a desktop. In contrast to desktop enumeration, the service manager enumerates each object and calls the ObjectNotify function of each device provider module when a user connects a device. If an object on a device needs synchronizing, the device provider module must inform the service manager.


Detecting Device Object Changes

Changes on the device are handled through the ObjectNotify function. The service manager calls ObjectNotify in the following cases:

  • Immediately after connection.
  • When an object changes and a user connects the device.
  • After an acknowledgement is received from the desktop that the object has been synchronized successfully.
  • Two seconds after the device provider module sets ONF_CALLING_BACK.

ObjectNotify checks the flags and the file system identifier information in the OBJNOTIFY structure for a changed object.

The following table shows the values for the OBJNOTIFY::uFlags member.

Uflags member
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 0.
ONF_CLEAR_CHANGE | The desktop provider module should mark the object as up to date. In this case, OBJNOTIFY::oidObject is the synchronized object identifier, and not the Windows CE object identifier.
ONF_CALL_BACK | Set by the device provider module to ask the service manager to call back.
ONF_CALLING_BACK | The service manager sets this flag and calls ObjectNotify.

The following code example shows how to implement ObjectNotify.

EXTERN_C BOOL ObjectNotify( POBJNOTIFY pNotify )
{
    // Check to see if the structure size is the same.
    if ( pNotify->cbStruct != sizeof( OBJNOTIFY ) )
         return FALSE;

    // Check ONF_* flags to see if the notification is
    // relevant.
    if ( !( pNotify->uFlags & ONF_DELETED ) )
    {
        // Make sure that you are dealing with the records in your
        // database.
        // The object (a record or a file) must exist.
    }

    if ( pNotify->uFlags & ONF_CLEAR_CHANGE )
    {
        // Check whether the object was changed again
        // during synchronization.
        // If so, return TRUE; if not, return FALSE.
    }

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

    // Determine what object identifier to return. Consider
    // uPartnerBit in your decision.

    // If you store one object per file and/or record, you simply
    // need to return the file system object identifier. Otherwise,
    // you need to read the file system object and determine the list of
    // object identifiers that have changed.

    return TRUE;
 }

To get information from a database with the oidDataBase object identifier, the service manager calls GetObjTypeInfo.

The following code example shows how to implement GetObjTypeInfo.

EXTERN_C BOOL GetObjTypeInfo
(
POBJTYPEINFO pInfo        // Pointer to the OBJTYPEINFO structure
)
{
CEOIDINFO   oidInfo;
if ( pInfo->cbStruct != sizeof( OBJTYPEINFO ) )
return FALSE;
    // Clear the structure.
    memset( &(oidInfo), 0, sizeof(oidInfo));

    // Retrieve information about the object in the object store.
CeOidGetInfo( oidDataBase, &oidInfo );

    // Store the database information into the OBJTYPEINFO structure.
wcscpy( pInfo->szName, oidInfo.infDatabase.szDbaseName );
     pInfo->cObjects = oidInfo.infDatabase.wNumRecords;
pInfo->cbAllObj = oidInfo.infDatabase.dwSize;
pInfo->ftLastModified = oidInfo.infDatabase.ftLastModified;

     return TRUE;
}


Registering the Service Provider Module

For Windows CE Services to recognize a service provider, you must create valid registry entries in the registry on both the Windows-based desktop computer and on the Windows CE–based device. You also must register the object types.

Note The CEUTIL utility DLL functions are especially helpful when adding desktop registry entries for Windows CE. For more information, see “Installing and Managing Applications.”

To register a service provider on the Windows-based desktop computer

  1. Provide a programmatic identifier (ProgID) for the desktop provider module.

    The ProgID must be a unique name.

  2. Generate a GUID (Class ID) for the service provider.
  3. Create the following keys in the Windows registry:

    HKEY_CLASSES_ROOT\CLSID\Class ID\InProcServer32

    HKEY_CLASSES_ROOT\CLSID\Class ID\ProgID

    HKEY_CLASSES_ROOT\ProgID\CLSID

The default value of the InProcServer32 key is the full path of the 32-bit DLL that implements the IReplStore interface (for example, for Microsoft Outlook the path is Outstore.dll); the default value of the ProgID key is MS.WinCE.Outlook; and the default value of the CLSID key is the GUID for the store.

After registering the service provider, you must register each object type that the service provider synchronizes. Register the object types in a subdirectory under HKEY_LOCAL_MACHINE. The following screen shot illustrates the desktop registry location for the appointment, contact, and task object types.

Each object type name is a key. Under each key, you must define the following five values:

  • Default

    A description of the object type; for example, Outlook Appointment Object.

  • Display Name

    The name of the object that you want to display; for example, Appointment.

  • Plural Name

    The plural name of the object; for example, Appointments.

  • Store

    The OLE programmatic identifier, ProgID, of the store that implements the IReplStore and IReplObjHandler interfaces; for example, MS.WinCE.Outlook.

  • Disabled

    A value indicating whether the service provider is displayed as disabled or enabled in Windows CE Services. A nonzero value indicates that the service provider is disabled.

The following screen shot illustrates the values defined under the Appointment object type.

Whenever a user connects a new device to the desktop and a new device profile is added to Mobile Device folder, the registry keys for the synchronization objects under HKEY_LOCAL_MACHINE are automatically copied to HKEY_CURRENT_USER.

To register a service provider on the Windows CE–based device

  • Register the device provider module under HKEY_LOCAL_MACHINE\Windows CE Services\Synchronization\Objects.

On the device, each object type name is a key, but you only define two values: Store and Display Name. Store refers to the module that exports the functions for this object type, and Display Name refers to the name of the object type.

The following registry data shows the device registration for the Appointment object type that was explained above.

Store    “pegobj.dll”
Display Name    “Appointment”


Working with RAPI

This section discusses how to use remote application programming interface (RAPI), which you can use to port your knowledge of standard Microsoft Win32 APIs into the Microsoft Windows CE programming environment. The main difference is that with RAPI, you can write data to or read data from a Windows CE–based device remotely with a desktop computer.


Invoking Functions from a Desktop Computer

RAPI gives an application running on a desktop computer the ability to invoke function calls on a Windows CE–based platform. The desktop computer is the RAPI client and the Windows CE–based platform is the RAPI server. The communication uses Windows Sockets API (Winsock) and can take place over a serial link, a modem connection, or a network connection.

The function calls behave much like the equivalent Windows CE functions. For the most part, RAPI functions have the same syntax, parameters, and return values as the corresponding Windows CE versions. The RAPI functions contain a Ce prefix to differentiate them from the Windows CE functions. Any other differences are noted in the reference documentation for the RAPI functions.

Note String and character parameters must be in Unicode format. Use the appropriate conversion routines, if necessary.


Initializing and Terminating Remote Applications

Before making RAPI calls, you must call the CeRapiInit or CeRapiInitEx function. These functions perform routine initialization and set up the communications link between the desktop computer and the Windows CE platform.

The CeRapiInit call is a synchronous operation. It does not return control to the application until a connection is made or an error occurs. In contrast, the CeRapiInitEx call is an asynchronous operation and it returns immediately. CeRapiInitEx continues the initialization until a connection is made, an error occurs, or there is a call to CeRapiUninit. Although CeRapiInitEx avoids blocking any threads, it is a more complicated method of initialization.

To initialize RAPI by using CeRapiInitEx

  1. Call CeRapiInitEx.

    If an error is returned, exit.

  2. If successful, call WaitForSingleObject or WaitForMultipleObjects to wait on the event handle passed back in the heRapiInit member of RAPIINIT.
  3. When heRapiInit is signaled, check for a successful connection or an error value.

Check the hrRapiInit member of the RAPIINIT structure for the final return value.

When you are finished with RAPI, call CeRapiUninit to terminate the connection and perform any necessary cleanup. Because creating and terminating connections are fairly expensive operations, establish and terminate the link only once per session, and not on a per-call basis.

The following code example shows how to use the CeRapiInitEx function. Following the CeRapiInitEx call, the MsgWaitForMultipleObjects function is used to wait on one of two events. The first event is when the event handle is passed back through the heRapiInit member of the RAPIINIT structure. The second event is when a user terminates a connection.

#define ARRAYSIZE(hArray)   sizeof (hArray) / sizeof (HANDLE)

enum
{
  WAD_ALLINPUT    = 0x0000,
  WAD_SENDMESSAGE = 0x0001,
};

DWORD WaitAndDispatch (DWORD nCount, HANDLE *phWait, DWORD dwTimeout,
                       UINT uFlags)
{
  DWORD dwObj;
  DWORD dwStart = GetTickCount ();
  DWORD dwTimeLeft = dwTimeout;

  for (;;)
  {
    dwObj= MsgWaitForMultipleObjects (nCount, phWait, FALSE, dwTimeLeft,
             (uFlags & WAD_SENDMESSAGE) ? QS_SENDMESSAGE : QS_ALLINPUT);

    if (dwObj == (DWORD)-1)
    {
      dwObj = WaitForMultipleObjects (nCount, phWait, FALSE, 100);

      if (dwObj == (DWORD)-1)
        break;
    }
    else
    {
      if (dwObj == WAIT_TIMEOUT)
        break;
    }

    if ((UINT)(dwObj - WAIT_OBJECT_0) < nCount)
      break;

    MSG msg;

    if (uFlags & WAD_SENDMESSAGE)
      PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE);
    else
    {
      while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        DispatchMessage (&msg);
    }

    if (INFINITE != dwTimeout)
    {
      dwTimeLeft = dwTimeout - (GetTickCount () - dwStart);
      if ((int)dwTimeLeft < 0)
        break;
    }
  }

  return dwObj;
}

HRESULT InitRapi (HANDLE hExit)
{
  RAPIINIT rapiinit = {sizeof (RAPIINIT)};
  HRESULT hResult = CeRapiInitEx (&rapiinit);

  if (FAILED(hResult))
    return hResult;

  HANDLE hWait[] = {hExit, rapiinit.heRapiInit};
  enum {WAIT_EXIT=WAIT_OBJECT_0, WAIT_INIT};

  DWORD dwObj = WaitAndDispatch (ARRAYSIZE(hWait), hWait, 1000, 1);

  // Event signaled by RAPI
  if (WAIT_INIT == dwObj)
  {
    // If the connection failed, uninitialize the
    // Windows CE RAPI.
    if (FAILED(rapiinit.hrRapiInit))
      CeRapiUninit ();

    return rapiinit.hrRapiInit;
  }

  // Either event signaled by user or a time-out occurred.
  CeRapiUninit ();

  if (WAIT_EXIT == dwObj)
    return HRESULT_FROM_WIN32(ERROR_CANCELLED);

  return E_FAIL;
}


Predefined RAPI Functions

RAPI includes a group of predefined functions that duplicate Windows CE functions on the desktop computer side of the connection. The following sections group RAPI functions by use. Because the actions of the functions are mirror-images of their Windows CE–based counterparts, details of each function are not provided. For information on a particular function, see the Windows CE Platform SDK Reference.


System Information Functions

The following table shows the RAPI system information functions. Most will be familiar to those of you working with Windows CE APIs, with the possible exception of CeGetPassword and CeGetDesktopDeviceCaps. The CeGetPassword function compares a string to the current system password. If the strings match, the function returns TRUE. Note that the comparison is case-specific. The CeGetDesktopDeviceCaps function is identical to the Windows CE counterpart, which is GetDeviceCaps.

System information functions
CeGetVersion | CeGetDesktopDeviceCaps
CeGlobalMemoryStatus | CeGetSystemInfo
CeGetSystemPowerStatusEx | CeGetPassword
CeGetStoreInformation | CeCheckPassword
CeGetSystemMetrics | CeCreateProcess


Database Functions

The following table shows the RAPI database functions. Many of them are supported in Windows CE 2.1 or later. Note that the RAPI functions that support the extended database API of Windows CE 2.1 and later are not exported by previous RAPI DLLs. Your application will not load if the desktop computer has a previous version of Rapi.dll and has attempted to implicitly load one of these functions.

Database management functions
CeCreateDatabase | CeOpenDatabaseEx
CeCreateDatabaseEx | CeReadRecordProps
CeDeleteDatabase | CeReadRecordPropsEx
CeDeleteDatabaseEx | CeSeekDatabase
CeDeleteRecord | CeSetDatabaseInfo
CeFindFirstDatabase | CeSetDatabaseInfoEx
CeFindFirstDatabaseEx | CeWriteRecordProps
CeFindNextDatabase | CeMountDBVol
CeFindNextDatabaseEx | CeUnmountDBVol
CeOidGetInfoEx | CeEnumDBVolumes
CeOpenDatabase | CeFindAllDatabases

Call the CeFindAllDatabases function to get information about all databases of a specified type. The information is returned in an array of CE_FIND_DATA structures.

You must free the memory allocated by the CeFindAllDatabases or CeReadRecordProps function by calling the CeRapiFreeBuffer function.


File and Directory Management Functions

The following table shows the RAPI file and directory management functions.

File and directory management functions
CeFindAllFiles | CeSetFilePointer
CeFindFirstFile | CeSetEndOfFile
CeFindNextFile | CeCreateDirectory
CeFindClose | CeRemoveDirectory
CeGetFileAttributes | CeMoveFile
CeSetFileAttributes | CeCopyFile
CeCreateFile | CeDeleteFile
CeReadFile | CeGetFileSize
CeWriteFile | CeGetFileTime
CeCloseHandle | CeSetFileTime

To retrieve path information

  • Call the CeGetTempPath function to get the path of the directory that is designated for temporary files.

    – Or –

    Call the CeGetSpecialFolderPath function to get the path of a specific desktop folder, which depends on the input parameter. The possibilities include the Recycle Bin, Start menu directory, document template directory, network directory, and folders for fonts or installed printers.

To retrieve other information

  • Call the CeFindAllFiles function to get information about all files and directories in a specified directory of the Windows CE object store.

    As with the CeFindAllDatabases function, the information is returned in an array of CE_FIND_DATA structures.

Also, you must free the memory allocated by the CeFindAllFiles function by calling the CeRapiFreeBuffer function.


Registry Management Functions

The following table shows the RAPI registry management functions.

Registry management functions
CeRegOpen KeyEx | CeRegEnumValue
CeRegEnumKeyText | CeRegDeleteValue
CeRegCreateKeyEx | CeRegQueryInfoKey
CeRegCloseKey | CeRegQueryValueEx
CeRegDeleteKey | CeRegSetValueEx


Shell Management Functions

The following table shows the RAPI shell management functions.

Shell management functions
CeSHCreateShortcut | CeGetTempPath
CeSHGetShortcutTarget | CeGetSpecialFolderPath


Window Management Functions

The following list shows the RAPI window management functions.

Window management functions
CeGetWindow | CeGetWindowText
CeGetWindowLong | CeGetClassName


Invoking Functions and Applications

Among all RAPI functions, there are two functions that invoke functions and applications residing on the Windows CE platform:

  • CeCreateProcess

    This function creates a new process that runs a specified executable file residing on the Windows CE platform.

  • CeRapiInvoke

    This function remotely executes a function residing on the Windows CE platform and provides for both input parameters and output data. It operates in either of two modes:

    • Block, which is known as synchronous. In block mode, the caller passes both input parameters and output data in a single buffer. Because this is a synchronous call, all input data must be present in memory at the time of the call and all output data must be present before the function finishes.
    • Stream, which is known as asynchronous. In stream mode, an Istream-type interface is used to exchange arbitrarily-sized data in any order and direction. The caller can pass input data in a single buffer, but from that point on all data should be exchanged through the stream. Because the data can be read, written, and stored in chunks, stream code is significantly faster than block mode. The interface used is based on Istream, but has two additional methods to allow you to do time-outs.

Note LocalAlloc allocates the memory passed for both the pInput and ppOutput parameters of CeRapiInvoke. The called function frees the input memory allocation, and the calling application frees the output memory allocation.


Handling RAPI Errors

In addition to errors associated with their non-RAPI counterparts, RAPI functions can fail because of RAPI-related errors. Network errors, for example, will need to be communicated back to the calling application.

RAPI functions that fail due to a RAPI-related error will return the error value defined for their Win32-based counterpart. To distinguish between RAPI and non-RAPI errors, use either the CeRapiGetError function or the CeGetLastError function. To determine if a function failed because of RAPI errors, call CeRapiGetError. To determine if a function failed because of non-RAPI errors, call CeGetLastError, which works the same as the GetLastError function does on Windows-based platforms.


Sample RAPI Application

The following code example shows how to initialize the RAPI client, make calls, and handle errors.

#include <stdio.h>
#include <tchar.h>
#include <rapi.h>

void PrintDirectory (LPWSTR lpszPath, UINT Indent)
{
  DWORD dwFoundCount;
  LPCE_FIND_DATA findDataArray;
  WCHAR szSearchPath[MAX_PATH];

  wcscpy (szSearchPath, lpszPath);
  wcscat (szSearchPath, L"*");

  if(!CeFindAllFiles (szSearchPath,
                      FAF_ATTRIBUTES | FAF_NAME,
                      &dwFoundCount,
                      &findDataArray))
  {
    _tprintf (TEXT("*** CeFindAllFiles failed. ***\n"));
    return;
  }

  if (!dwFoundCount)
    return;

  for (UINT i = 0; i < dwFoundCount; i++)
  {
    for (UINT indCount = 0; indCount < Indent; indCount++)
      _tprintf( TEXT("  "));

    if (findDataArray[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      wprintf (TEXT("[%s]\n"), findDataArray[i].cFileName);

      WCHAR szNewPath[MAX_PATH];

      wcscpy (szNewPath, lpszPath);
      wcscat (szNewPath, findDataArray[i].cFileName);
      wcscat (szNewPath, L"\\");

      PrintDirectory (szNewPath, Indent + 1);
    }
    else
      wprintf (TEXT("%s\n"), findDataArray[i].cFileName);
  }

  if (findDataArray)
    RapiFreeBuffer (findDataArray);
}

void main()
{
  HRESULT hRapiResult;

  _tprintf (TEXT("Connecting to Windows CE..."));

  hRapiResult = CeRapiInit ();

  if (FAILED(hRapiResult))
  {
    _tprintf (TEXT("Failed\n"));
    return;
  }

  _tprintf (TEXT("Success\n"));

  PrintDirectory (L"\\", 0);

  CeRapiUninit ();
}


Managing the Connection Partnership

This section provides information on methods and tools that you can use to manage aspects of a connection partnership between a Microsoft Windows CE–based device and its companion Windows-based desktop computer. This section includes:

  • “Receiving Connection Notification,” which explains how to launch applications automatically when a device is connected to or disconnected from the desktop computer by using either registry-based or COM interface–based notification.
  • “Transferring Files,” which describes how to register file types; how to register, implement, and use a file filter; and how to implement a dummy file filter for transferring and converting data.
  • “Using the CEUTIL Helper DLL for Windows CE Services,” which explains how to use the CEUTIL utility to manage registry entries for Windows CE Services and to gain access to device partnerships.


Receiving Connection Notification

Connection notification is the method by which applications on a desktop computer are notified when a Windows CE–based device is either connected to or disconnected from the desktop computer. To launch applications automatically, an application must be registered in the system registry on the desktop computer for each connection event. Once the application is registered, the Windows CE Services connection manager starts the application whenever the specified event occurs.

The following applications are registered in the system registry on the desktop computer:

  • Windows CE Explorer.
  • Remote tools (for example, the debugger), which are provided with Windows CE Platform Builder.
  • Connection notification client sample code, which provides a basic template for receiving connection notifications. For a description of this sample, see the CD-ROM that accompanies this documentation.

There are two methods to register the desktop application:

  • Registry-based notification, by using command lines that are registered in the registry on the desktop computer.
  • COM interface–based notification, by using two Component Object Model (COM) interfaces—one that is implemented by the connection manager and the other by the application—to perform the registration.


Registry-based Notification

In registry-based notification, an application places a command line in the desktop system registry in one of two keys:

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnConnect
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnDisconnect

When the event specified by the key occurs, the command line is executed. That is, when a Windows CE–based device is connected to a desktop computer, the command line under AutoStartOnConnect is executed; likewise, when the device is disconnected, the command line under AutoStartOnDisconnect is executed.

Registry-based notification is appropriate for applications that need neither some control of the connection manager nor the ability to register and deregister for connection notifications.

To register an application for automatic execution

  1. Construct a named value that uniquely identifies the application.

    It should include a company and product name — for example, MicrosoftHPCExplorerAutoConnect. Enter the named value under the appropriate key, either AutoStartOnConnect or AutoStartOnDisconnect.

  2. Define the named value as the application that is to be executed and include command line arguments. The application file path must be wrapped with double quotes if arguments are provided.

The following registry editor (.reg) file shows how to register a command line for both AutoStartOnConnect and AutoStartOnDisconnect. In this example, when the device is connected, Notepad.exe is started with a command line argument of c:\config.sys. When the device is disconnected, Notepad.exe is started with a command line argument of c:\autoexec.bat.

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE
Services\AutoStartOnConnect]
"MicrosoftAutoConnectSample"="\"notepad"\ c:\\config.sys"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE
Services\AutoStartOnDisconnect]
"MicrosoftAutoDisconnectSample"="\"notepad"\ c:\\autoexec.bat"


COM Interface-based Notification

COM Interface–based Notification

The second method you can use to register a desktop application is COM interface–based notification. In this method, you use two COM interfaces to register an application in the desktop registry:

  • IDccMan, which is implemented by the connection manager.
  • IDccManSink, which is implemented by the application.

Although the COM interface–based notification method is more complex than registry-based notification, by using it you get some control of the connection manager and the ability to register and deregister for connection notifications.

The connection manager, which resides on the desktop, displays an icon next to the clock in the taskbar when the Windows CE–based device is connected or is waiting to be connected to the desktop computer. This icon—two terminals with a connecting cable—indicates the connection status. By right-clicking the icon, you can start Windows CE Explorer.


Notifying and Deregistering Procedures

The notification process follows the same basic steps for connection and disconnection of the Windows CE–based device. The following procedure assumes that the IDccMan and IDccManSink interfaces have been implemented.

To register an application by using COM interface–based notification

  1. Initialize the COM library and register the application for the appropriate event.
  2. Connect or disconnect the device.
  3. Perform the application processing.

The Connection Notification Client sample application, provided on the CD-ROM that accompanies this documentation, shows several connection notification scenarios, including a new remote connect, a disconnect, and a reconnect. To see the actual sequence of interface method calls for any of these scenarios, build and run the application. Then, in the Connection Notification Test dialog box, view the Notification Messages list.

To receive notification upon connection of the Windows CE–based device to a desktop computer

  1. Initialize the COM library and register with the connection manager.
    • Call the COM function CoInitialize to initialize the Component Object library.
    • Call the COM function CoCreateInstance with the DccMan class identifier (CLSID_DccMan) and IDccMan interface identifier (IID_IDccMan), and receive a pointer to the IDccMan interface.

      For more information on CLSID_DccMan and IID_IDccMan, see “Registering the IDccMan Class Identifiers.”

    • Call the IDccMan::Advise method, which provides the connection manager with a pointer to the IDccManSink interface that you implemented and registers the application with the connection manager.

      The connection manager calls the IDccManSink::OnLogInactive method, notifying the application that there is no connection between the desktop computer and the device.

  2. Establish the connection between the desktop computer and the device.
    • The connection manager calls the IDccManSink::OnLogListen method.

      The connection manager waits for the remote connection services for both the desktop computer and the device to respond. Until they are both running, the connection manager will not proceed.

    • For Windows 95–based systems only, the connection manager calls the IDccManSink::OnLogAnswered method when the connection manager has detected the communications interface.
    • The connection manager calls the IDccManSink::OnLogActive method when the connection is established between the device and connection manager.
    • The connection manager calls the IDccManSink::OnLogIpAddr method, providing the Internet Protocol (IP) address that it obtained for the communications socket.

    Note When the IDccManSink::OnLogIpAddr notification occurs, the connection is completely established.

  3. Perform your processing in the application.

    This can include processing on the desktop computer, remote processing on the device by using remote application programming interface (RAPI), or calling the IDccMan methods. However, the application should wait to use CeRapiInit to initialize RAPI until the IDccManSink::OnLogActive notification is received. This ensures that a connection is established between the desktop computer and the device.

To receive notification upon disconnection of the Windows CE–based device from the desktop computer

  1. Initialize the COM library and register the application.
  2. Disconnect the device from the desktop computer.

    Windows CE Services notifies the application when the desktop computer and device are disconnected by calling the IDccManSink::OnLogDisconnection method.

  3. Perform your processing in the application.

    Because there is no connection to the device, this processing can only take place on the desktop computer.

To receive notification when reestablishing a remote connection

The Connection Notification Client source code uses the IDccMan interface and implements the IDccManSink interface.

  • If a connection was established, but then was disconnected by the desktop computer or the device, the IDccManSink::OnLogActive notification occurs when the connection is reestablished.

    When an application calls the IDccMan::ShowCommSettings function and the OK button is clicked in the Communications Properties dialog box, the following notification sequence occurs:

    • IDccManSink::OnLogListen
    • IDccManSink::OnLogDisconnection
    • IDccManSink::OnLogInactive
    • IDccManSink::OnLogListen

    If the Cancel button is clicked instead, no notification is sent and a Listen state is maintained.

To deregister an application from being notified

One of the advantages to using the COM interface–based notification process is that it allows an application to deregister itself from being notified. This might be helpful when an application needs to run only once.

  1. Call IDccMan::Unadvise, which releases the memory associated with the IDccManSink interface.
  2. Call IDccMan::Release, which releases the IDccMan object.
  3. Call CoUninitialize to perform any OLE cleanup.

    Note that a call to CoUninitialize is required for each successful call to CoInitialize.


Registering the IDccMan Class Identifiers

Both the DccMan class identifier, CLSID_DccMan, and the IDccMan interface identifier, IID_IDccMan, are passed in the call to CoCreateInstance. Because the Windows CE Services Setup application registers CLSID_DccMan, you only need to register IID_IDccMan in your application.

The following code example shows how to initialize the IDccMan interface in the registry.

REGEDIT4
[HKEY_CLASSES_ROOT\CLSID\499c0c20-A766-11cf-8011-00A0c90A8F78]
@="Connection Manager"

[HKEY_CLASSES_ROOT\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78\InprocServer32]
@="C:\\Windows\\System\\Rapi.dll"
   "ThreadingModel"="Apartment"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78]
@="Connection Manager"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78\InprocServer32]
@="C:\\Windows\\System\\Rapi.dll"
   "ThreadingModel" = "Apartment"


Windows CE-based Device Notification

Windows CE–based Device Notification

In addition to supporting notification for the desktop computer–based application, Windows CE supports Notification API, which allows applications on the mobile device to receive connection notification.

The CeRunAppAtEvent function provides Windows CE–based applications with the ability to be notified when a connection or other event occurs. When a specified event occurs, applications registered by CeRunAppAtEvent are launched. For example, events include when a device is connected to a desktop computer, when an operating system is restored from a backup, or when the system time is changed.

The function is prototyped as:

BOOL CeRunAppAtEvent (TCHAR *pwszAppName, LONG lWhichEvent);

The name of the application to be launched when the event occurs is indicated by the first parameter. The second parameter is a set of bit flags that indicate which events you want to track.


Transferring Files

A file filter is a dynamic-link library (DLL) that controls the transfer of data between the desktop computer and the Windows CE–based device. File filters are used by the Windows CE Services application on the desktop computer to automatically convert files as they are transferred.

File formats used by the Windows CE operating system and Windows CE–based applications are generally different from those of the corresponding Windows-based applications. For example, Microsoft Pocket Word does not support OLE compound files. Windows CE Services automatically adjusts file formats as files are transferred between the desktop computer and the device.

Windows CE Services includes the following filters:

  • Pocket Word (.pwd) to Microsoft Word (.doc)
  • Word (.doc) to Pocket Word (.pwd)
  • Microsoft Pocket Excel (.pxl) to Microsoft Excel 5.0 (.xls)
  • Excel (.xls) to Pocket Excel (.pxl)
  • Windows bitmap (.bmp) to Windows CE 4-color bitmap (.2bp)

You can extend the file-filtering capability of Windows CE Services by defining your own application-specific filters. This section describes file filters and the interfaces that you use to create them.

Implementing a file filter is similar for both importing and exporting files. The only differences are in the registry settings and in how the body of the file filter—the converter function—changes data. The examples in this section demonstrate the procedure for importing files, but typically you would write a converter function that handles both importing and exporting, by using dual registry settings that indicate both the import and export functionality.

Note The words “importing” and “exporting” in this section are from the perspective of the device. Thus, importing a file with a file filter transfers a file from the desktop computer to the device, whereas exporting a file with a file filter transfers a file from the device to the desktop computer.


Registering File Types and File Filters

Windows CE Services uses the registry entries to determine which conversions are available for a given file type and how to invoke the filter that supports the conversion. For this reason, you must register each file type and file filter properly by using the following procedure.

To register file types and their filters

  1. Register the file extension type.
  2. Generate a class identifier (CLSID) for the file filter.
  3. Register the file filter.

The following sections describe each step in detail and provide a sample file filter registry entry.

Note CEUTIL, a utility DLL, has functions that are especially helpful when you are dealing with the desktop registry entries for Windows CE Services. For information about CEUTIL, see “Using the CEUTIL Helper DLL for Windows CE Services.”


Registering a File Extension Type

Windows CE Explorer, like Windows Explorer, allows you to customize a type name, as displayed in the details view of Windows Explorer, and an icon for any file extension (for example, .pwd). You must register file filters under HKEY_CLASSES_ROOT.

The following is the structure of HKEY_CLASSES_ROOT.

HKEY_CLASSES_ROOT\.<file extension>
   \(Default) = <class name>
HKEY_CLASSES_ROOT\<class name>
   \(Default) = <name to be displayed in the “Type” column of Explorer>
   \DefaultIcon = <file name or index of the icon for this type>


Generating a Class Identifier

You must give every file filter a unique class identifier (CLSID), which identifies class objects to OLE. CLSIDs are universally unique identifiers (UUIDs), also called globally unique identifiers (GUIDs). You must include the CLSID for the file filter in your application and you must register it with the operating system when your application is installed.

If the file filter supports both importing and exporting, a unique CLSID must be associated with each file filter that is to be registered for the respective import and export registry setting.

The GUID Generator tool lets you generate a GUID that you can use to identify your file filter. A GUID Generator application, named Guidgen.exe, is provided with Microsoft Visual C++ development system. The GUID Generator calls the CoCreateGuid function to generate a new GUID. It also lets you copy the GUID to the clipboard so that you can insert the GUID into the source code for your application by using one of the following formats:

  • IMPLEMENT_OLECREATE macro format

    Defined in an IMPLEMENT_OLECREATE macro, which allows instances of a CCmdTarget-derived class to be created by Automation clients. For example:

    // {CA761230-ED42-11CE-BACD-00AA0057B223}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0xca761230, 0xed42, 0x11ce, 0xba, 0xcd, 0x0, 0xaa,
0x0, 0x57, 0xb2, 0x23);
  • DEFINE_GUID macro format

    Defined in an IMPLEMENT_OLECREATE macro, which is included with Visual C++ in the file Afxdisip.h. It allows instances of a CCmdTarget-derived class to be created by Automation clients. For example:

    // {CA761230-ED42-11CE-BACD-00AA0057B223}
DEFINE_GUID(<<name>>,
0xca761230, 0xed42, 0x11ce, 0xba, 0xcd, 0x0, 0xaa, 0x0, 0x57, 0xb2,
0x23);
  • Statically allocated structure format

    Declared as a statically allocated structure. For example:

    // {CA761232-ED42-11CE-BACD-00AA0057B223}
static const GUID <<name>> = { 0xca761232, 0xed42, 0x11ce,
{ 0xba, 0xcd, 0x0, 0xaa, 0x0, 0x57, 0xb2, 0x23 } };
  • Registry entry

    Specified in a form suitable for registry entries or registry editor scripts. For example:

    {CA761233-ED42-11CE-BACD-00AA0057B223}


Registering a File Filter

An application registers a file filter by placing its CLSID in the following registry key locations:

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.<file extension>\InstalledFilters. This registration associates the file filter with the file type that it converts.
  • HKEY_CLASSES_ROOT\CLSID. This registration provides information on the file filter’s capabilities and its DLL.

Registering a file filter in two locations guarantees that your file filter is available for any new device partnership. Windows CE Services uses device partnerships to allow multiple Windows CE–based devices to be connected to the same desktop computer. If device partnerships exist when an application registers a file filter, an application must create a file filter extension key for each existing device partnership. Connecting a device to a desktop computer for the first time establishes a new device partnership with a unique identifier. Devices use the partnership identifier to store unique settings for synchronization, file conversions, and backup-and-restore information.

All filters registered under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters key are copied to the HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows CE Services\Partners\<partner identifier> key, where <partner identifier> is the identifier of the new device partnership.

A file type can have more than one file filter associated with it. The Windows CE Services user interface (UI) lists the registered filters as filter options. A file filter can be registered under the file type’s InstalledFilters key in the DefaultImport or DefaultExport subkeys. As their names imply, these subkeys define the default file filters for the file type. The file filter specified under the extension key as the DefaultImport or DefaultExport value will be shown as the default.

Note Any filter defined as the DefaultImport or DefaultExport value must be an InstalledFilters value, also.

A file filter can also be registered for a particular device. For example, for the Palm-size PC device category, the file filter extension subkey needs to be created under the HKEY_LOCAL_MACHINE\SOFTWARE\Windows CE Services\SpecialDefaults\Palm PC\Filters key.

The following is the structure of the Filters key and the InstalledFilters subkey.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters

   \.<file extension>
      [DefaultImport = <default import filter CLSID>]
      [DefaultExport = <default export filter CLSID>]
      \InstalledFilters
         [<clsid1>]
         . . .
         [More CLSIDs for this extension.]
    . . .
    [More extensions.]

The HKEY_CLASSES_ROOT\CLSID key provides basic information about file filters. Each file filter that has been identified in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters key must be registered in this key. The following is the structure for this key and its subkeys.

HKEY_CLASSES_ROOT\CLSID

   \<clsid>
      \(Default) = <description in "Edit Conversion Settings" list>
      \DefaultIcon = <file name,index for the icon for this type>
      \InProcServer32 = <file name of the DLL that handles this type>
         ThreadingModel = Apartment
      \PegasusFilter
         [Import]
         [HasOptions]
         Description = <string to display in the conversion dialog box>
         NewExtension = <extension of the converted file>
   \. . . [More CLSIDs for filters.]

The <clsid> key is a named value that is the CLSID of the registered file filter. This key contains the following subkeys:

  • DefaultIcon, which defines the icon name string or icon resource identifier for the icon associated with the file filter DLL.
  • InProcServer32, which identifies the file filter DLL using the default value, and defines the apartment model capabilities of the file filter in the ThreadingModel named value.
  • PegasusFilter, which provides information on the specific capabilities of the file filer.

    The following table shows possible named values for the PegasusFilter subkey.

Named value
Description
Import | If this named value exists, the conversion type is for importing files from the desktop computer to the device. Otherwise, the conversion type is for exporting files from the desktop computer to the device.
HasOptions | If this named value exists, the file filter supports the ICeFileFilter::FilterOptions method.
Description The data for this named value is a string that describes the conversion. Windows CE Services displays this text on the property sheets that are displayed by selecting the DeviceDesktop or DesktopDevice tab control selections in the File Conversion Properties dialog box, and then choosing Edit to display the Edit Conversion Settings dialog box.

For example, if the Import named value exists, then, on the DesktopDevice property sheet, the data value defined by the Description named value is displayed under the file conversions details Convert to HPC files of the type.

NewExtension | Defines the extension of the file that will be created on the destination device.


Sample File Filter Registry Entry

The following is a sample registry editor (.reg) file that is used to register the Bitmap Image file filter converter. This converter is to be used when a bitmap file is imported from a desktop computer to a Windows CE–based device. The sample file can be used to convert a bitmap file with a .bmp format to a bitmap file with the .2bp format used by Windows CE. The last three entries register the .2bp file extension to be displayed with a specific icon and name.

Note The 2bp.dll file converter is registered and installed when Windows CE Services is installed on the desktop computer.

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.bmp]
"DefaultImport"="{DA01ED80-97E8-11cf-8011-00A0C90A8F78}"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE
Services\Filters\.bmp\InstalledFilters]
"{DA01ED80-97E8-11cf-8011-00A0C90A8F78}"=""

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-00A0C90A8F78}]
@="Bitmap Image"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-
00A0C90A8F78}\DefaultIcon]
@="c:\Program Files\Windows CE Services\2bp.dll,-1000"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-
00A0C90A8F78}\InProcServer32]
@="2bp.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-
00A0C90A8F78}\PegasusFilter]
"Import"=""
"Description"="Bitmap Image."
"NewExtension"="2bp"

[HKEY_CLASSES_ROOT\.2bp]
@="2bpfile"

[HKEY_CLASSES_ROOT\2bpfile]
@="Bitmap Image"

[HKEY_CLASSES_ROOT\2bpfile\DefaultIcon]
@="c:\Program Files\Windows CE Services\minshell.dll,-2025"


Implementing and Using a File Filter

The CD-ROM that accompanies this documentation includes a sample file filter named Copyfilt that imports a binary file (.bin) from a desktop computer to a binary file (.pbn) on a Windows CE–based device. The Copyfilt sample file filter demonstrates basic operations for implementing a file filter, which are described in the following procedure.

To implement a file filter

  1. Register the file filter DLL.

    For a description of how to register a file filter, see “Registering a File Filter.”

  2. Implement the ICeFileFilter interface and methods.
  3. Windows CE Services calls the QueryInterface method for the file filter’s ICeFileFilterOptions interface.
    • If this interface is available, Windows CE then calls the ICeFileFilterOptions::SetFilterOptions method with a correctly initialized CFF_CONVERTOPTIONS structure. The bNoModalUI member specifies whether the converter is allowed to bring up modal UI while performing the conversion.

      For a file filter that includes selectable conversion options, implement the ICeFileFilter::FilterOptions method to allow users to select among the conversion options supported by the file filter.

To use a file filter

  1. The user transfers a file by dragging it between Windows Explorer on the desktop computer and Windows CE Explorer on the device.
  2. Windows CE Services prompts the user for a conversion type, using the File Conversion Properties dialog box.
  3. Windows CE Services calls the ICeFileFilter::NextConvertFile method for the file filter to perform the custom file conversion.
  4. Information about the file conversion and about the source and destination files is passed by pointers to the CFF_CONVERTINFO, CFF_DESTINATIONFILE, and CFF_SOURCEFILE structures.

Within the ICeFileFilter::NextConvertFile method:

  1. Call ICeFileFilterSite::OpenSourceFile to open the source file.
  2. Call ICeFileFilterSite::OpenDestinationFile to open the destination file.
  3. Use the OpenSourceFile method to read data from the stream file that was opened.
  4. Convert the data.

    This can include independent software vendor (ISV)-developed code and RAPI calls.

  5. Check the status of the NextConvertFile pbCancel parameter occasionally to ensure that the user has not stopped the conversion process.

    If the conversion has been stopped, perform all cleanup operations, and then exit.

  6. Use the OpenDestinationFile method to write the converted data to the stream file that was opened.
  7. Call the ICeFileFilterSite::ReportProgress method occasionally to report the progress of the file conversion.

    Windows CE Services uses this information to update a status bar that shows the percentage of the conversion that is complete. You should limit your use of this method because it can add substantially to the conversion time.

  8. Call the ICeFileFilterSite::ReportLoss method to report data that is intentionally discarded during conversion.

    Windows CE Services displays a message with this information when the file conversion is complete. Depending on the error format passed in the call, Windows CE Services may call the ICeFileFilter::FormatMessage method for the file filter, in order to properly format the message.

  9. Use the ICeFileFilterSite::CloseSourceFile method to close the source file, and then use the ICeFileFilterSite::CloseDestinationFile method to close the destination file.


Using RAPI Calls in a File Filter

You can use RAPI calls in a file filter. This allows use of any RAPI functions that are appropriate to your application, such as registry or file functions.

Do not initialize RAPI in the file filter DLL by using CeRapiInit. Rather, the NextConvertFile method should have already performed the RAPI initialization and established a connection between the desktop computer and the Windows CE–based device. If a RAPI call fails because there is no connection established, the file converter should perform some type of default action rather than just failing. For example, this default action could involve querying the user to select from various options.

To determine if a call failed due to a failure in the RAPI, use CeRapiGetError. To diagnose non-RAPI related errors, use CeGetLastError.

For more information on RAPI, see “Working with RAPI.”


Implementing a Dummy File Filter

A dummy file filter gives the appearance that files are being converted without actually implementing a file filter or performing a filter conversion. Instead, the file is passed without any conversion.

You may want to implement a dummy file filter for a file that has a unique file type or one that has not been registered already in the desktop registry. A dummy file filter can also be useful for a file that does not need any conversion when it is transferred between Windows Explorer on the desktop computer and Windows CE Explorer for the Windows CE–based device.

Usually, if a file with a deregistered file type is copied to the device, the device displays the warning “No Converter Selected.” This warns the user that the file will be transferred without conversion. In this situation, you could implement a dummy file filter to avoid alarming the user with the file conversion warning.

Note The “No Converter Selected” warning is displayed only if the File Conversions Properties for the device is set to enable file conversion. If the Enable File Conversion check box is cleared, the “No Converter Selected” warning is not displayed.

To register a dummy file filter

  1. In the desktop registry, modify the HKLM\Software\Microsoft\Windows CE Services\Filters key by adding a subkey.

    This subkey should name the file extension for the type of files that should be converted with the NULL file conversion. For example, if you are converting files with extension .abc, then you must add an .abc subkey.

  2. Under the .abc subkey, create a string value named DefaultImport that is set to Binary Copy.

    This string value identifies the conversion for files with .abc extensions that are imported from the desktop computer to the device.

  3. Under the .abc subkey, create a string value named DefaultExport that is set to Binary Copy.

    This string value identifies the conversion for files with .abc extensions that are exported from the device to the desktop computer.

The following registry editor (.reg) file shows how to register the example .abc dummy file filter.

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.abc]
"DefaultImport"="Binary Copy"
"DefaultExport"="Binary Copy"

In this example, when a file with an .abc extension is copied between the desktop computer and the device, it will seem as though a conversion process is taking place because you do not receive the “No Converter Selected” warning from Windows CE Services. However, no filter actually is being used, because an InstalledFilters subkey has not been added under the .abc key.


Using the CEUTIL Helper DLL for Windows CE Services

You can use the CEUTIL DLL to manage desktop registry entries for Windows CE Services. CEUTIL encapsulates the registry top-level locations to ensure forward-compatibility for applications. It also provides helper functions for browsing device partnerships and querying the currently connected, or selected, device settings. In general, this DLL is a replacement for and compatible with the Microsoft Win32 registry API that is used when referring to any subkeys under the Windows CE Services root.

Use CEUTIL to do the following tasks:

  • Register desktop file filters
  • Register desktop synchronization services
  • Access device partnership settings that are used for both file filters and synchronization services
  • Add custom menu items


Desktop Registry Structure

The following list describes the desktop registry structure that is used by Windows CE Services and the corresponding identifiers that are used in CEUTIL to refer to particular keys in the structure:

  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows CE Services, hereafter referred to as machine_root, stores general information.
  • HKEY_CURRENT_USER\Software\Microsoft\Windows CE Services, hereafter referred to as local_root, stores device partnership information.

The first time a Windows CE–based device is connected to a desktop computer and a device partnership is created, the various synchronization and filter settings are copied from the machine_root to the partnership subkey under local_root.


Examples of CEUTIL Functions

The following code example shows how to enumerate device partnerships and get the path for the file synchronization folder.

#include "ceutil.h"

void Example1 (void)
{
  // Note that this code runs on the desktop (or host) computer,
  // and not on the Windows CE–based device, so set
  // your project settings accordingly.

  HCESVC   hsvc            = NULL;
  HCESVC   hsvcSync        = NULL;
  HCESVC   hsvcProfile     = NULL;
  DWORD    cProfilesEnum   = 0;
  DWORD    nProfileID      = 0;

  while (SUCCEEDED (CeSvcEnumProfiles (&hsvc,
                                       cProfilesEnum,
                                       &nProfileID)))
  {
    if (nProfileID != (DWORD)-1)
    {
      if (SUCCEEDED (CeSvcOpenEx (hsvcProfile,
                                  TEXT("Services\\Synchronization"),
                                  FALSE, &hsvcSync)))
      {
        TCHAR szPath[MAX_PATH];

        if (SUCCEEDED (CeSvcGetString (hsvcSync,
                                       TEXT("Briefcase Path"),
                                       szPath,
                                       sizeof(szPath)/sizeof(TCHAR))))
        {
          // Found a path to use.

          // Your code to complete tasks goes here.
        }

        CeSvcClose (hsvcSync);
      }

      CeSvcClose (hsvcProfile);
    }
  }

  cProfilesEnum++;
}

The following code example shows how to add a custom menu.

{
  HCESVC hsvcMyMenu = NULL;

  if (SUCCEEDED (CeSvcOpen (CESVC_CUSTOM_MENUS,
                            TEXT("MyApp"), TRUE, &hsvcMyMenu)))
  {
    CeSvcSetString (hsvcMyMenu, TEXT("DisplayName"),
                    TEXT("&My Calculator"));

    CeSvcSetString (hsvcMyMenu, TEXT("Command"), TEXT("calc.exe"));

    CeSvcSetString (hsvcMyMenu, TEXT("StatusHelp"),
                    TEXT("Displays calculator"));

    CeSvcSetDword (hsvcMyMenu, TEXT("Version"), 0x00020000);

    CeSvcClose (hsvcMyMenu);
  }
}


Installing Applications

Developers of Windows CE–based platforms have created additional capabilities for use on the Handheld PC and Palm-size PC devices. This section describes how to install applications on and remove applications from a Windows CE–based device. This includes:

  • Using the CAB Wizard to create a Windows CE cabinet (.cab) file to install applications on a Windows CE–based device.
  • Using the Application Manager application, CeAppMgr.exe, to install applications on and remove applications from a Windows CE–based device, as well as toremove the application’s files from the desktop computer.

This section also explains how to add additional menu items to the Tools menu in the Windows CE Explorer window by using code to directly place values in the proper registry locations. You can also use the CEUTIL utility dynamic-link library (DLL), which is described in “Using the CEUTIL Helper DLL for Windows CE Services,” to create custom menus and perform other tasks.


Using the CAB Wizard

The Windows CE operating system (OS) uses a .cab file to install an application on a Windows CE–based device. A .cab file is composed of multiple files that have been compressed into one file. Compressing multiple files into one file provides the following benefits:

  • All of the application’s files are present.
  • You can prevent a partial installation.
  • You can install your application from several sources, such as a desktop computer or a Web site.

Use the CAB Wizard application (Cabwiz.exe) to generate a .cab file for your application.

To create a device-specific .cab file for an application

  1. Create an .inf file with Windows CE–specific modifications.
  2. Optionally, create a Setup.dll file to provide custom control of the installation process.
  3. Use the CAB Wizard to create the .cab file, using the .inf file, the Setup.dll file, and the device-specific application files as parameters.


Creating an .inf File for the CAB Wizard

An .inf file specifies information about an application for the CAB Wizard. The following table shows the sections of an .inf file.

Section
Required
Describes
Version | Yes | The application’s creator and version
CEStrings | Yes | String substitutions for application and directory names
Strings | No | String definitions for one or more strings
CEDevice | Yes | The device platform for which the application is targeted
DefaultInstall | Yes | The default installation of the application
SourceDiskNames | Yes | The name and path of the disk on which the application resides
SourceDiskFiles | Yes | The name and path of the files in which the application resides
DestinationDirs | Yes | The names and paths of the destination directories for the application on the target device
CopyFiles | Yes | Default files to copy to the target device
AddReg | Yes | Keys and values that the .cab file will add to the registry on the device
CEShortCuts | No | Shortcuts that the installation application creates on the device


Version

The [Version] section is required and specifies the creator of the file and other relevant information.

[Version]
Signature = "signature_name"
Provider = "INF_creator"
CESignature = "$Windows CE$"
signature_name
Must be “$Windows NT$” or “$Windows 95$”.
INF_creator
Company name of the application. For example:
Provider = "Microsoft"

The following code example shows a typical [Version] section.

[Version]
Signature = "$Windows NT$"
Provider = "Microsoft"
CESignature = "$Windows CE$"


CEStrings

The [CEStrings] section is required and specifies string substitutions for the application name and the default installation directory.

[CEStrings]
AppName = app_name
InstallDir = default_install_dir
app_name
Name of the application. Other instances of %AppName% in the .inf file will be replaced with this string value.
default_install_dir
Default installation directory on the device. Other instances of %InstallDir% in the .inf file will be replaced with this string value.

The following code example shows a typical [CEStrings] section.

[CEStrings]
AppName="Game Pack"
InstallDir=%CE1%\%AppName%


Strings

The [Strings] section is optional and defines one or more string keys. A string key represents a string of printable characters.

[Strings]
string_key = value
[string_key = value]
value
String consisting of letters, digits, or other printable characters. Enclose value in double quotation marks ““ ““if the corresponding string key is used in an item that requires double quotation marks.

The following code example shows a typical [Strings] section.

[Strings]
reg_path = Software\Microsoft\My Test App


CEDevice

The [CEDevice] section is optional and describes the platform for which your application is targeted. All keys in this section are optional. If a key is nonexistent, Windows CE does not perform any checking. Windows CE also does not perform any checking if a key has no data. The exception is UnsupportedPlatforms; if this key exists but there is no data, the previous value is not overridden.

[CEDevice]
[ProcessorType =[processor_type]]
[UnsupportedPlatforms = platform_family_name[,platform_family_name]]
[VersionMin = [major_version.minor_version]]
[VersionMax = [major_version.minor_version]]
[BuildMin = [build_number]]
[BuildMax = [build_number]]
processor_type
Value that is returned by SYSTEMINFO.dwProcessorType. For example, the value for the SH3 CPU is 10003 and the MIPS CPU is 4000.
platform_family_name
List of platform family names that are known to be unsupported. If the name specified in the [CEDevice.xxx] section is different from that in the [CEDevice] section, both platform_family_name values are unsupported for the microprocessor that is specified by xxx. That is, the list of specific unsupported platform family names is appended to the previous list of unsupported platform family names. Application Manager will not display the application for an unsupported platform. Also, a user will be warned during the setup process if the .cab file is copied to an unsupported device. For example:
[CEDevice]
UnsupportedPlatforms = pltfrm1      ; pltfrm1 is unsupported
[CEDevice.SH3]
UnsupportedPlatforms =              ; pltfrm1 is still unsupported
major_version and minor_version
Numeric value that is returned by OSVERSIONINFO.dwVersionMinor and OSVERSIONINFO.dwVersionMajor. The .cab file is valid for the currently connected device if the version of the currently connected device is less than or equal to VersionMax and also greater than or equal to VersionMin. For Windows CE Japanese-language devices, set VersionMin and VersionMax to 2.01.

Note The supported Windows CE OS versions include 1.0, 1.01, 2.0, 2.01, and 2.10. When you use these numbers, be sure to include all significant digits.

build_number
Numeric value returned by OSVERSIONINFO.dwBuildNumber. The .cab file is valid for the currently connected device if the version of the currently connected device is less than or equal to BuildMax and also greater than or equal to BuildMin.

The following code example shows three [CEDevice] sections: one that gives basic information for any CPU and two that are specific to the SH3 and the MIPS microprocessors.

[CEDevice]                           ; A "template" for all platforms
UnsupportedPlatforms = pltfrm1       ; Does not support pltfrm1
; The following specifies version 1.0 devices only.
VersionMin = 1.0
VersionMax = 1.0

[CEDevice.SH3]                  ; Inherits all [CEDevice] settings
; This will create a .cab file specific to SH3 devices.
ProcessorType = 10003           ; The SH3 .cab file is only valid
                                ; for the SH3 microprocessors.
UnsupportedPlatforms =          ; pltfrm1 is still unsupported
; The following overrides the version settings so that no version
; checking is performed.
VersionMin =
VersionMax =

[CEDevice.MIPS]                  ; Inherits all [CEDevice] settings
; This will create a .cab file specific to "MIPS" devices.
ProcessorType = 4000             ; The MIPS .cab file is only valid
                                 ; for the MIPS microprocessor.
UnsupportedPlatforms =pltfrm2    ; pltfrm1 and pltfrm2 are
                                 ; unsupported for the “MIPs" .cab file.

Note To create the two CPU-specific .cab files for the setup .inf file in the previous example, you must run the CAB Wizard with the /cpu sh3 mips parameter.


DefaultInstall

The [DefaultInstall] section is required and describes the default installation of your application.

[DefaultInstall]
Copyfiles=copyfile_list_section[,copyfile_list_section]
AddReg=add_registry_section[,add_registry_section]
[CEShortcuts=shortcut_list_section[,shortcut_list_section]]   ; new key
[CESetupDLL=setup_DLL]                                        ; new key
[CESelfRegister=self_reg_DLL_filename[,self_reg_DLL_filename] ; new key
shortcut_list_section
String that identifies one more section that defines shortcuts to a file, as defined in the [CEShortcuts] section.
setup_DLL
Optimal string that specifies a Setup.dll. It is written by the independent software vendor (ISV) and contains customized functions for operations during installation and removal of the application. The file must be specified in the [SourceDisksFiles] section.
self_reg_DLL_filename
String that identifies files that self-register by exporting the DllRegisterServer and DllUnregisterServer Component Object Model (COM) functions. You must specify the files in the [SourceDiskFiles] section.

During installation, if installation on the device fails to call the file’s exported DllRegisterServer function, the file’s exported DllUnregisterServer function will not be called during removal.

The following code example shows a typical [DefaultInstall] section.

[DefaultInstall]
AddReg = RegSettings.All
CEShortcuts = Shortcuts.All


SourceDiskNames

The [SourceDiskNames] section is required and describes the name and path of the disk on which your application resides.

[SourceDisksNames]
disk_ordinal= ,disk_label,,path
[disk_ordinal= ,disk_label,,path]

The following code example shows a typical [SourceDiskNames] section.

[SourceDisksNames]                   ; Required section
1 = ,"Common files",,C:\app\common   ; Using an absolute path

[SourceDisksNames.SH3]
2 = ,"SH3 files",,sh3                ; Using a relative path

[SourceDisksNames.MIPS]
2 = ,"MIPS files",,mips              ; Using a relative path


SourceDiskFiles

The [SourceDiskFiles] section is required and describes the name and path of the files in which your application resides.

 [SourceDisksFiles]
filename=disk_number[,subdir]
[filename=disk_number[,subdir]]

The following code example shows a typical [SourceDiskFiles] section.

[SourceDisksFiles]         ; Required section
begin.wav = 1
end.wav = 1
sample.hlp = 1

[SourceDisksFiles.SH3]
sample.exe = 2             ; Uses the SourceDisksNames.SH3
                           ; identification of 2.

[SourceDisksFiles.MIPS]
sample.exe = 2             ; Uses the SourceDisksNames.MIPS
                           ; identification of 2.


DestinationDirs

The [DestinationDirs] section is required and describes the names and paths of the destination directories for your application on the target device.

[DestinationDirs]
file_list_section = 0,subdir
[file_list_section = 0,subdir]
[DefaultDestDir=0,subdir]

Note Windows CE does not support directory identifiers.

subdir
String that identifies the destination directory. The following table shows the string substitutions that are supported by Windows CE. These can be used only for the beginning of the path.
String
Replacement value
%CE1% | \Program Files
%CE2% | \Windows
%CE3% | \Windows\Desktop
%CE4% | \Windows\Startup
%CE5% | \My Documents
%CE6% | \Program Files\Accessories
%CE7% | \Program Files\Communication
%CE8% | \Program Files\Games
%CE9% | \Program Files\Pocket Outlook
%CE10% | \Program Files\Office
%CE11% | \Windows\Programs
%CE12% | \Windows\Programs\Accessories
%CE13% | \Windows\Programs\Communications
%CE14% | \Windows\Programs\Games
%CE15% | \Windows\Fonts
%CE16% | \Windows\Recent
%CE17% | \Windows\Favorites

The following code example shows a typical [DestinationDirs] section.

[DestinationDirs]
Files.Common   = 0,%CE1%\My Subdir   ;\Program Files\My Subdir
Files.Shared   = 0,%CE2%                 ;\Windows


CopyFiles

The [Copyfiles] section, under the [DefaultInstall] section, is required and describes the default files to copy to the target device.

[copyfile_list_section]
destination_filename,[source_filename],[,flags]
[destination_filename,[source_filename],[,flags]]

The source_filename parameter is optional if it is the same as destination_filename.

flags
Numeric value that specifies an action to be done while copying files. The following table shows the values that are supported by Windows CE.
Flag
Value
Description
COPYFLG_WARN_IF_SKIP | 0x00000001 | Warn a user if an attempt is made to skip a file after an error has occurred.
COPYFLG_NOSKIP | 0x00000002 | Do not allow a user to skip copying a file.
COPYFLG_NO_OVERWRITE | 0x00000010 | Do not overwrite an existing file in the destination directory.
COPYFLG_REPLACEONLY | 0x00000400 | Copy the source file to the destination directory only if the file is already in the destination directory.
CE_COPYFLG_NO_DATE_DIALOG | 0x20000000 | Do not copy files if the target file is newer.
CE_COPYFLG_NODATECHECK | 0x40000000 | Ignore date while overwriting the target file.
CE_COPYFLG_SHARED | 0x80000000 | Create a reference when a shared DLL is counted.

The following example is a typical [CopyFiles] section.

[DefaultInstall.SH3]
CopyFiles = Files.Common, Files.SH3

[DefaultInstall.MIPS]
CopyFiles = Files.Common, Files.MIPS


AddReg

The [AddReg] section, under the [DefaultInstall] section, is required and describes the keys and values that the .cab file adds to the device registry.

 [add_registry_section]
registry_root_string , subkey,[value_name], flags, value[,value]
[registry_root_string, subkey,[value_name], flags, value[,value]]
registry_root_strings
String that specifies the registry root location. The following table shows the values that are supported by Windows CE.
Root string
Description
HKCR | The same as HKEY_CLASSES_ROOT
HKCU | The same as HKEY_CURRENT_USER
HKLM | The same as HKEY_LOCAL_MACHINE

value_name
Registry value name. If empty, the “(default)” registry value name is used.
flags
Numeric value that specifies information about the registry key. The following table shows the values that are supported by Window CE.
Flag
Value
Description
FLG_ADDREG_NOCLOBBER | 0x00000002 | If the registry key exists, do not overwrite it. This flag can be used in combination with any of the other flags in this table.
FLG_ADDREG_TYPE_SZ | 0x00000000 | The REG_SZ registry data type.
FLG_ADDREG_TYPE_MULTI_SZ | 0x00010000 | The REG_MULTI_SZ registry data type. The value field that follows can be a list of strings separated by commas.
FLG_ADDREG_TYPE_BINARY | 0x00000001 | The REG_BINARY registry data type. The value field that follows must be a list of numeric values separated by commas, one byte per field, and must not use the 0x hexadecimal prefix.
FLG_ADDREG_TYPE_DWORD | 0x00010001 | The REG_DWORD data type. Only the non-compatible format in the Win32 Setup .inf documentation is supported.

The following code example shows a typical [AddReg] section.

AddReg = RegSettings.All

[RegSettings.All]
HKLM,%reg_path%,,0x00000000,alpha        ; <default> = "alpha"
HKLM,%reg_path%,test,0x00010001,3        ; Test = 3
HKLM,%reg_path%\new,another,0x00010001,6    ; New\another = 6


CEShortcuts

The [CEShortcuts] section, a Windows CE–specific section under the [DefaultInstall] section, is optional and describes the shortcuts that the installation application creates on the device.

[shortcut_list_section]
shortcut_filename,shortcut_type_flag,target_file/path[,standard_destination_path]
[shortcut_filename,shortcut_type_flag,target_file/path[,standard_destination_path]]
shortcut_filename
String that identifies the shortcut name. It does not require the .lnk extension.
shortcut_type_flag
Numeric value. Zero or empty represents a shortcut to a file; any nonzero numeric value represents a shortcut to a folder.
target_file/path
String value that specifies the destination location. For a file, use the target file name—for example, MyApp.exe—that must be defined in a file copy list. For a path, use a file_list_section name defined in the [DestinationDirs] section—for example, DefaultDestDir—or the %InstallDir% string.
standard_destination_path
Optional string value. A standard %CE x% path or %InstallDir%. If no value is specified, the shortcut_list_section name of the current section or the DefaultDestDir value from the [DestinationDirs] section is used.

The following code example shows a typical [CEShortcuts] section.

CEShortcuts = Shortcuts.All

[Shortcuts.All]
Sample App,0,sample.exe              ; Uses the path in DestinationDirs.
Sample App,0,sample.exe,%InstallDir% ; The path is explicitly specified.


Sample .inf File

The following code example shows a typical .inf file.

[Version]                  ; Required section
Signature = "$Windows NT$"
Provider = "Microsoft"
CESignature = "$Windows CE$"

[CEDevice.SH3]
ProcessorType = 10003      ; SH3 microprocessor

[CEDevice.MIPS]
ProcessorType = 4000       ; MIPS microprocessor

[DefaultInstall]           ; Required section
AddReg = RegSettings.All
CEShortcuts = Shortcuts.All

[DefaultInstall.SH3]
CopyFiles = Files.Common, Files.SH3

[DefaultInstall.MIPS]
CopyFiles = Files.Common, Files.MIPS

[SourceDisksNames]                   ; Required section
1 = ,"Common files",,C:\app\common   ; Using an absolute path

[SourceDisksNames.SH3]
2 = ,"SH3 files",,sh3      ; Using a relative path

[SourceDisksNames.MIPS]
2 = ,"MIPS files",,mips    ; Using a relative path

[SourceDisksFiles]         ; Required section
begin.wav = 1
end.wav = 1
sample.hlp = 1

[SourceDisksFiles.SH3]
sample.exe = 2             ; Uses the SourceDisksNames.SH3
                           ; identification of 2.

[SourceDisksFiles.MIPS]
sample.exe = 2             ; Uses the SourceDisksNames.MIPS
                           ; identification of 2.

[DestinationDirs]          ; Required section
Shortcuts.All = 0,%CE3%    ; \Windows\Desktop
Files.Common = 0,%CE2%     ; \Windows
Files.SH3 = 0,%InstallDir%
Files.MIPS = 0,%InstallDir%
DefaultDestDir = 0,%InstallDir%

[CEStrings]                ; Required section
AppName = My Test App
InstallDir = %CE1%\%AppName%

[Strings]                  ; Optional section
reg_path = Software\Microsoft\My Test App

[Shortcuts.All]
Sample App,0,sample.exe              ; Uses the path in DestinationDirs.
Sample App,0,sample.exe,%InstallDir% ; The path is explicitly specified.

[Files.Common]
begin.wav,,,0
end.wav,,,0
Sample Help File.hlp,sample.hlp,,0   ; Rename the destination file.

[Files.SH3]
sample.exe,,,0

[Files.MIPS]
sample.exe,,,0

[RegSettings.All]
HKLM,%reg_path%,,0x00000000,alpha    ; <default> = "alpha"
HKLM,%reg_path%,test,0x00010001,3    ; test = 3
HKLM,%reg_path%\new,another,0x00010001,6    ; new\another = 6


Using Installation Functions in Setup.dll

Setup.dll is an optional file that enables you to perform custom operations during installation and removal of your application. The following table shows the functions that are exported by Setup.dll.

Function
Description
Install_Init | Called before installation begins. Use this function to check the application version when reinstalling an application and to determine if a dependent application is present.
Install_Exit | Called after installation is complete. Use this function to handle errors that occur during application installation.
Uninstall_Init | Called before the removal process begins. Use this function to close the application, if the application is running.
Uninstall_Exit | Called after the removal process is complete. Use this function to save database information to a file and delete the database and to tell the user where the user data files are stored and how to reinstall the application.

Note Use the [CESelfRegister] section in the .inf file to point to Setup.dll.


Using CAB Wizard to Create a .cab File

After you create the .inf file and the optional Setup.dll file, use the CAB Wizard to create the .cab file. The command-line syntax for the CAB Wizard is as follows:

cabwiz.exe “inf_file” [/dest dest_directory] [/err error_file]
[/cpu cpu_type [cpu_type]]
inf_file
Setup .inf file path.
dest_directory
Destination directory for the .cab files. If no directory is specified, the .cab files are created in the inf_file directory.
error_file
File name for a log file that contains all warnings and errors that are encountered when the .cab files are compiled. If no file name is specified, errors are displayed in message boxes. If a file name is used, the CAB Wizard runs without the user interface (UI); this is useful for automated builds.
cpu_type
Creates a .cab file for each microprocessor tag that you specify. A microprocessor tag is a label that is used in the Win32 setup .inf file to differentiate between different microprocessor types. The /cpu parameter, followed by multiple cpu_type values, must be the last qualifier in the command line.

The following example creates .cab files for the SH3 and MIPS microprocessors, assuming that the Win32 setup .inf file contains the SH3 and MIPS tags:

cabwiz.exe  "c:\myfile.inf"  /err myfile.err  /cpu  sh3  mips

Note The following Windows CE files must be installed in the same directory on the desktop computer: Cabwiz.exe, Makecab.exe, and Cabwiz.ddf. Cabwiz.exe must be called with its full path in order to run correctly.


Troubleshooting the CAB Wizard

To identify and avoid problems that might occur when using the CAB Wizard, follow these guidelines:

  • Use %% for a percent sign (%) character when using this character in an .inf file string, as specified in the Win32 documentation. This will not work under the [Strings] section.
  • Do not use .inf or .cab files that were created for Windows CE to install applications on Windows-based desktop platforms.
  • Ensure that the Makecab.exe and Cabwiz.ddf files, included with Windows CE, are in the same directory as Cabwiz.exe.
  • Use the full path to call Cabwiz.exe.
  • Do not create a .cab file with the Makecab.exe file that is included with Windows CE. You must use Cabwiz.exe, which uses Makecab.exe to generate the .cab files for Windows CE.
  • Do not set the read-only attribute for .cab files.


Using the Application Manager

The Application Manager application, CeAppMgr.exe, resides on a user’s desktop computer. CeAppMgr adds and removes applications on a Windows CE–based device, and it deletes the application files from the desktop computer.

To install an application on a Windows CE–based device

  1. Create a single Application Manager initialization (.ini) file to provide the Application Manager with information about the application.
  2. Install the application automatically or manually:
    • To install the application automatically, create a desktop setup application with any available third-party desktop setup application.

      This application copies the multiple device-specific .cab files to the desktop computer. The application then launches the Application Manager, with the Application Manager .ini file as a parameter.

    –Or–

    • To install the application manually, register the application with the Application Manager.

      Registering the application manually requires that you copy the .cab file and the .ini file for the Application Manager to the desktop computer and run the Application Manager with the .ini file as the parameter.


Creating an .ini File for the Application Manager

The .ini file contains information that registers an application with the Application Manager. The .ini file has the following format:

[CEAppManager]
Version      = version_number
Component    = component_name

[component_name]
Description  = descriptive_name
[Uninstall   = uninstall_name]
[InstallDir  = install_directory]
[IconFile    = icon_filename]
[IconIndex   = icon_index]
[DeviceFile  = device_filename]
CabFiles         = cab_filename [,cab_filename]
version_number
Numeric version of the Application Manager, which is 1.0.
component_name
String that identifies the name of the section for the application.
descriptive_name
String that will appear in the Description field of the Application Manager when a user chooses the application.
uninstall_name
String that identifies the application’s Windows Uninstall registry key name. This name must match the application’s registered Windows Uninstall key name, which is found in the HKLM\Software\Microsoft\Windows
\CurrentVersion\Uninstall registry key. Providing this name enables the Application Manager to automatically remove the application from the desktop computer and the device when a user clicks the Remove button in the Application Manager UI.
install_directory
String that identifies the desktop installation directory containing the location of the .cab files. If this key is nonexistent, which is recommended, the path of the .inf file is used for the installation directory.
icon_filename
String that identifies the relative path from install_directory to the desktop icon file. This string is used to display the device_filename when the file name is viewed in Windows CE Services.
icon_index
Numeric index into icon_filename. The value is used to display the device_filename when it is viewed in Windows CE Services. If this key is nonexistent, the first icon in icon_filename is used.
device_filename
File name on the device that will display the icon specified by icon_filename and icon_index when the device_filename is viewed in Windows CE Services.
cab_filename
File name of the available .cab files, relative to install_directory. Use commas to separate multiple cab_filenames. Do not include unnecessary spaces in this list of file names.


‹^# $ K + ›

Sample .ini File

The following code example shows a typical.ini file.

[CEAppManager]
Version      = 1.0
Component    = Games

[Games]
Description  = Game Pack for your Windows CE–based device
Uninstall    = Game Pack

;Do not specify the "InstallDir" key so that CEAppMgr will use
;the directory of this .ini file as the installation directory.

IconFile     = gamepack.ico
IconIndex    = 0
DeviceFile   = gamepack.exe

;Because there are multiple .cab files specific to a CPU type,
;these files are relative to the installation directory.
CabFiles    = SH3\gamepack.cab,MIPS\gamepack.cab


Installing an Application Automatically

You can use a third-party desktop computer installation application to copy a file from the installation site to a user’s desktop computer. With this approach, the Application Manager automatically installs the application on the Windows CE–based device. If the Windows CE–based device is not connected, the Application Manager notes that the application has not been installed. When the device is subsequently connected, the Application Manager automatically completes the installation.

You can extract the full file name and path of the Application Manager from the default registry value of the HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\CEAppMgr.exe registry key. Because the returned value is the full file name and path of CEAppMgr.exe, you can remove the CEAppMgr.exe file name to get the desktop installation directory of Windows CE Services. You can use the desktop installation directory to copy files to the desktop computer. The location for your files will be the installation directory with your application’s subdirectory appended.


Installing and Removing an Application Manually

To install an application from a desktop computer to a Windows CE–based device, on the desktop computer, call the Application Manager with the application’s .ini file as a parameter. The command-line syntax for the Application Manager CeAppMgr.exe is as follows:

CEAppMgr.exe [/report]  “CEAppMgr_ini_filename”  [“CEAppMgr_ini_filename”]
CEAppMgr_ini_filename
Full file name and path of the CEAppMgr .ini file for a single application. If the application has multiple components, you can run the Application Manager once with multiple .ini files, one file for each component.
report
Optional parameter that you can use to test the installation process if problems occur. This parameter should not be included in the final setup application.

One you call CEAppMgr.exe, the Application Manager completes the installation process. To add additional capabilities to the installation process, use the Install_Init and Install_Exit functions. Because the installation procedure registers the application’s .cab files with the Application Manager, a user can reinstall the application on the device at a later time or install the application on another device.

The full application name displayed in the CEAppMgr dialog box is extracted from the CAB Wizard .inf file. The extracted name is "provider appname". The value for provider is from [Version] Provider, while appname is from the [CEStrngs] AppName.

To remove an application from a Windows CE–based device, the user calls the Application Manager from the desktop computer. The Application Manager is usually located in the Control Panel. The Application Manager uses the information registered from the uninstall_name parameter of the .ini file to delete the application from the desktop computer and the Windows CE–based device. To add additional capabilities to the removal process, use the Uninstall_Init and Uninstall_Exit functions.


Troubleshooting the Application Manager

To identify and avoid problems that might occur when you install or remove an application on a Windows CE–based device, follow these guidelines:

  • Use the full path for the location of the CeAppMgr .ini file when you call Ceappmgr.exe to register an application.
  • Use the /report parameter in debug versions to verify that CeAppMgr is using the correct information for the .cab files.
  • In the CeAppMgr .ini file, verify the following:
    • The string list in the CabFiles key contains no unnecessary spaces and matches the actual .cab file name and relative path.
    • The string value in the Component key exists elsewhere in the .ini file.
  • Verify that the desktop computer’s setup application is calling the correct CeAppMgr .ini file, using the full path.

There are various third-party desktop setup applications that do not correctly update the actual file sizes when overwriting existing files. Because the Application Manager verifies the actual file size with the embedded file size of the .cab file, be sure that the installed .cab file sizes are correct. To ensure that this happens for future upgrade scenarios, delete the known existing .cab files when you reinstall an application.


Adding Custom Menus to Windows CE Explorer

You can add additional menu items to the Tools menu in the Windows CE Explorer window in two different ways. The method described here uses code to directly place values in the proper registry locations. You can also use the CEUTIL utility DLL, described in “Using the CEUTIL Helper DLL for Windows CE Services,” to create custom menus and perform other tasks.

To add a custom menu, create a subkey and add several values under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\ CustomMenus as follows.

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WINDOWS CE Services\CustomMenus]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WINDOWS
CE Services\CustomMenus\subkey]
“DisplayName”=“displayName”
“Command”=“myApp.exe”
“StatusHelp”=“StatusHelpText”
“Version”=version_number
subkey
String that identifies the subkey to be created on the Tools menu.
displayName
String that identifies the display name of the menu item. An ampersand (&) specifies a hot key.
myApp.exe
String that identifies the command that will be executed by WinExec when a user chooses the menu item.
StatusHelpText
String that identifies the status and Help text that appears in the status bar when a user browses the menu item.
version_number
Application version. This value should be 0x00020000.

The following sample registry file adds a calculator menu item.

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WINDOWS CE Services\CustomMenus]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WINDOWS
CE Services\CustomMenus\MyApp]
"DisplayName"="&My Calculator"
"Command"="calc.exe"
"StatusHelp"="Brings up the calculator"
"Version"=dword:00020000