Understanding Mobile Data Synchronization: Utilizing MS ActiveSync Capabilities at a High Level

Wednesday Aug 17th 2005 by Alex Gusev

Have you ever experienced an adventure of data synchronization between your PDA and PC? For many applications, it might be more than a "nice-to-have" feature. Learn which way is more suitable for your requirements in case you really need it.

Windows CE Services

As you probably very well aware, a whole bunch of data synchronization methods and techniques are bound around one common basis: Windows CE Services. This term combines ActiveSync custom service providers, file filters, connection notifications, and the CeUtil API. An upcoming article will be dedicated to the last two topics. ActiveSync service provider's development will be described later. Most of the discussed topics below are related to the desktop side only.

So, you can start with CeUtil. This DLL exports about a dozen functions that allow you to manage ActiveSync's Registry values. Obviously enough, you can do the same job using regular RegXXX calls. A benefit you get with CeUtil API is that it provides an abstract layer over ActiveSync registry settings, so you don't need to bother with their exact names, which in turn may vary a bit between different OS versions and localizations. Thus far, here is what you can use:

HRESULT __stdcall CeSvcOpen( UINT uSvc, LPTSTR pszPath,
                             BOOL fCreate, PHCESVC phSvc );
HRESULT __stdcall CeSvcOpenEx( HCESVC hSvcRoot, LPTSTR pszPath,
                               BOOL fCreate, PHCESVC phSvc );
HRESULT __stdcall CeSvcClose( HCESVC hSvc );
HRESULT __stdcall CeSvcGetString( HCESVC hSvc, LPCTSTR pszValName,
                                  LPTSTR pszVal, DWORD cbVal );
HRESULT __stdcall CeSvcSetString( HCESVC hSvc, LPCTSTR pszValName,
                                  LPCTSTR pszVal );
HRESULT __stdcall CeSvcGetDword( HCESVC hSvc, LPCTSTR pszValName,
                                 LPDWORD pdwVal );
HRESULT __stdcall CeSvcSetDword( HCESVC hSvc, LPCTSTR pszValName,
                                 DWORD dwVal );
HRESULT __stdcall CeSvcGetBinary( HCESVC hSvc, LPCTSTR pszValName,
                                  LPBYTE pszVal, LPDWORD pcbVal );
HRESULT __stdcall CeSvcSetBinary( HCESVC hSvc, LPCTSTR pszValName,
                                  LPBYTE pszVal, DWORD cbVal );
HRESULT __stdcall CeSvcDeleteVal( HCESVC hSvc, LPCTSTR pszValName );
DEVICEID __stdcall CeGetDeviceId( void );
DEVICEID __stdcall CeGetSelectedDeviceId( void );
HRESULT __stdcall CeSvcEnumProfiles(PHCESVC phSvc,
                                    DWORD lProfileIndex,
                                    PDWORD plProfile);

As you see, there are three logical functional groups:

  • To access Registry keys
  • To manipulate values
  • To obtain device info

CeUtil offers you a lot of predefined keys touching different aspects of ActiveSync programming. You will find an excerpt from the SDK help in the following table. But prior to this, let me highlight the following point. CeUtil Registry keys are mirrored at several locations. There is "MACHINE_ROOT":

HKEY_LOCAL_MACHINESoftwareMicrosoftWindows CE Services—Used to address settings in general;

There is also "LOCAL_ROOT":

HKEY_CURRENT_USERSoftwareMicrosoftWindows CE Services—Addresses specific device configuration.

So, you have some freedom to configure Windows CE Services at different levels. Having said this all, here is a table:

Registry Key CEUTIL Constants Description
MACHINE_ROOTFilters CESVC_FILTERS The Filters Registry root. New filters should register themselves here
MACHINE_ROOTCustomMenus CESVC_CUSTOM_MENUS Custom menus Registry root
MACHINE_ROOTServices Synchronization CESVC_SYNC_COMMON Synchronization Registry root. New sync services should register here
LOCAL_ROOT CESVC_ROOT_USER Windows CE Services root hierarchy under HKEY_CURRENT_USER
LOCAL_ROOTPartners CESVC_DEVICES Individual device subkeys are created under this key
LOCAL_ROOTPartners<device id> CESVC_DEVICEX Particular device root subkey
LOCAL_ROOTPartners<selected device id> CESVC_DEVICE_SELECTED Root subkey for the currently selected device
LOCAL_ROOTPartners<device id>Services CESVC_SERVICES_USER Root Services subkey for a particular device
LOCAL_ROOTPartners<device id>ServicesSynchronization CESVC_SYNC Synchronization reg root for a particular device

Thus far, you're getting access to desired Registry key by calling CeSvcOpen. A deeper hierarchy is achieved by subsequent CeSvcOpenEx calls. By playing with their parameters, you can control key access and creation policy.

I will skip a second group of functions dealing with key values themselves due to their simplicity and move forward to a device-related one. It isn't complicated in usage but nevertheless may be pretty helpfull. A code snippet below shows just one simple case of it:

DWORD dwProfile = 0;
DWORD dwProfileInx = 0;
TCHAR szDevName[255] = { 0 } , szDevType[255] = { 0 };
HCESVC hDevice;
   hr = CeSvcEnumProfiles(&hSvc,dwProfileInx,&dwProfile);
   if ( hr == ERROR_NO_MORE_ITEMS )
   if ( hr != S_OK )
      // handle an error as needed
   if ( S_OK == CeSvcOpen(CESVC_DEVICEX, (LPTSTR)dwProfile,
        FALSE, &hDevice) )
      // do whatever you want with this info
while ( hr == S_OK );

This is actually all the story about CeUtil. Use it wisely, and leave it in peace for now.

Getting Your Application AutoStarted

If all you need is to launch some application when a PDA is connected or disconnected, this is really a piece-of-cake task. ActiveSync uses two keys in the Registry to keep a list of all programs to run upon the above events:


So, to execute your own application, simply add a value as in the following sample:

; Here, put the full path to your app, possibly with a command
; line to designate that it was launched on a device connection

In this sample, notepad.exe will be called when the PDA connects successfully, either through its cradle or remotely. In your real environment, you then may recognize such a device by its ID and handle it somehow specifically.

Using Notifications to Control the Device's Connection

For more complicated cases, you have another opportunity—connection notifications. Windows helps you here and implements IDccMan, one of two interfaces responsible for this task. You in turn have to implement one of additional interfaces, IDccManSink or IDccManSink2. They are identical, but IDccManSink2 also supports the IPv6 protocol.

Take a look at the usual working flow. It is really simple:

  • Create an instance of IDccMan interface
  • Call IDccMan::Advise method to get notified about connections
  • Call IDccMan::Unadvise method when you no longer need notifications

Now let's take a closer look at IDccManSink declarations:

    STDMETHOD(OnLogIpAddr)        (THIS_ DWORD dwIpAddr) PURE;
    STDMETHOD(OnLogTerminated)    (THIS) PURE;
    STDMETHOD(OnLogActive)        (THIS) PURE;
    STDMETHOD(OnLogInactive)      (THIS) PURE;
    STDMETHOD(OnLogAnswered)      (THIS) PURE;
    STDMETHOD(OnLogListen)        (THIS) PURE;
    STDMETHOD(OnLogDisconnection) (THIS) PURE;
    STDMETHOD(OnLogError)         (THIS) PURE;
                              pIpAddr) PURE;

You will find a short description of above methods in the following table:

Method Description
OnLogListen Indicates that the connection manager listens for incoming connections
OnLogTerminated Indicates that the connection manager has been shut down
OnLogAnswered Indicates that the connection manager has detected the communications interface
OnLogActive Indicates that a connection is established and fully operational
OnLogInactive Indicates a disconnection, or disconnected state
OnLogDisconnection Indicates that the connection manager has terminated the connection. It may be sent several seconds after actual disconnection
OnLogError Indicates that the connection manager failed to start communications
OnLogIpAddr Indicates that an IP address has been established for communications
OnLogIpAddrEx Indicates that an IP address has been established for communications, IDccManSink2 only

Now we can code our IDccManSink implementation. I left it pretty simple just to shape the idea. Full source you will find in article's sample project. Here I want to place only main loop:

IDccMan *pIDccMan = NULL;
if ( SUCCEEDED(hr) )
   CDevComDccSync DccSync;
   DWORD dwContext = 0;
   SetConsoleTitle("Developer.com IDccManSink Sample");
   while (!kbhit());
   cout << "Failed to create IDccMan object: " << std::hex
        << hr << std::dec << endl;

Our implementation of IDccManSink interface simply fakes a regular COM object. If you need to develop separate component which will be used by other applications, then you will have to add all standard COM stuff like ClassFactory and so forth. The sample's output is presented in Figure 1:

As you see, the connection manager informs your application about various phases of connection process. If you are going to use IP-based communications, you have PDA's address handy. Moreover, if WinCE device is connected via a cradle, the IP will indicate "localhost;" in other words,, so you will always know how device is connected.


This article has discussed simple yet useful techniques of managing a connectivity between Windows CE device and a desktop computer. I still haven't touched data synchronization itself, but anyway even now your application can use; for example, RAPI or WinSock to pass data back and forth. I'll describe more complicated data exchange and synchronization methods in coming articles.


Download the accompanying code's zip file here

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved