Connection Notification
ActiveSync gives you two ways of notifying PC-based applications when a connection is made with a Windows CE device. The first method is to simply launch all the applications listed under a given registry key. When the connection is broken, all applications listed under another key are launched. This method has the advantage of simplicity at the cost of having the application not know why it was launched.The second method of notification is a COM-interface method. This notification method involves two interfaces: IDccMan, provided by RAPI.dll, and IDccManSink, which must be implemented by the application that wants to be notified. This method has the advantage of providing much more information to the application as to what is actually happening at the cost of having to implement a COM-style interface.
Registry Method
To have your PC application launched when a connection is made to a Windows CE device, simply add a value to the PC registry under the following key:
[HKEY_LOCAL_MACHINE]
\Software\Microsoft\Windows CE Services\AutoStartOnConnect
I'll show you shortly how to access this key using CeSvcOpen so that the precise name of the key can be abstracted. The name of the value under AutoStartOnConnect can be anything, but it must be something unique. The best way to ensure this is to include your company name and product name plus its version in the value name. The actual data for the value should be a string that contains the fully specified path for the application you want to launch. The string can only be the filename; appending a command line string causes an error when the program is launched. For example, to launch a myapp program that's loaded in the directory c:\windowsce\tools\syncstuff, the value and data might be
MyCorpThisApp c:\windowsce\tools\syncstuff\myapp.exe
To have a command line passed to your application, you can have the entry in the registry point to a shortcut that will launch your application. The entry in the registry can't pass a command line, but shortcuts don't have that limitation.You can have an application launched when the connection is broken between the PC and the Windows CE device by placing a value under the following key:
[HKEY_LOCAL_MACHINE]
\Software\Microsoft\Windows CE Services\AutoStartOnDisconnect
The format for the value name and the data is the same as the format used in the AutoStartOnConnect key.
A routine to set these values is simple to write. The example routine below uses the CeSvcOpen and CeSvcSetString functions to write the name of the module to the registry. Remember that since this routine runs on a PC, and therefore perhaps under Windows XP, you'll need administrator access for this routine to have write access to the registry.
//
// RegStartOnConnect – Have module started when connect occurs.
//
LPARAM RegStartOnConnect (HINSTANCE hInst) {
TCHAR szName[MAX_PATH];
HCESVC hSvc;
HRESULT rc;
// Get the name of the module.
GetModuleFileName (hInst, szName, dim(szName));
// Open the AutoStartOnConnect key.
rc = CeSvcOpen (CESVC_ROOT_MACHINE, "AutoStartOnConnect",
TRUE, &hSvc);
if (rc == NOERROR) {
// Write the module name into the registry.
CeSvcSetString (hSvc, TEXT ("MyCompanyMyApp"), szName);
CeSvcClose (hSvc);
}
return rc;
}
The preceding routine doesn't have to know the absolute location of the ActiveSync keys in the registry, only that the AutoStart key is under CESVC_ROOT_MACHINE. You can modify this routine to have your application started when a connection is broken by substituting AutoStartOnConnect with AutoStartOnDisconnect in the call to CeSvcOpen.
COM Method
As I mentioned before, the COM method of connection notification is implemented using two COM interfaces—IDccMan and IDccManSink. The system implements IDccMan, while you are responsible for implementing IDccManSink. The IDccMan interface gives you a set of methods that allow you to control the link between the PC and the Windows CE device. Unfortunately, most of the methods in IDccMan aren't implemented. The IDccManSink interface is a series of methods that are called by the connection manager to notify you that a connection event has occurred. Implementing each of the methods in IDccManSink is trivial because you don't need to take any action to acknowledge the notification.
The process of connection notification is simple. You request an IDccMan interface. You call a method in IDccMan to pass a pointer to your IDccManSink interface. ActiveSync calls the methods in IDccManSink to notify you of events as they occur. In this section, I'll talk about the unique methods in IDccManSink and IDccMan, but I'll skip over the IUnknown methods that are part of every COM interface[1].
The IDccMan Interface
To gain access to the IDccMan interface, you need to call the COM library function CoInitialize to initialize the COM library. Then you make a call to CoCreateInstance to retrieve a pointer to the IDccMan interface. Once you have this interface pointer, you call the method IDccMan::Advise to notify the connection manager that you want to be notified about connection events. This method is prototyped as
HRESULT IDccMan::Advise (IDccManSink *pDccSink,
DWORD *pdwContext);
The first parameter is a pointer to an IDccManSink interface that you must have previously created. I'll talk about IDccManSink shortly. The second parameter is a pointer to a DWORD that receives a context value that you pass to another IDccMan method when you request that you no longer be advised of events.You can display the communications configuration dialog of ActiveSync by calling this method:
HRESULT IDccMan::ShowCommSettings (void);
This method has no parameters; it simply displays the communications dialog box. The user is responsible for making any changes to the configuration and for dismissing the dialog box.When you no longer need connection notifications, you call the Unadvise method, prototyped as
HRESULT IDccMan::Unadvise (DWORD dwContext);
The only parameter is the context value that was returned by the Advise method. After you have called Unadvise, you no longer need to maintain the IDccManSink interface.
The IDccManSink Interface
You are responsible for creating and maintaining the IDccManSink interface for as long as you want notifications from the connection manager. The interface methods are simple to implement—you simply provide a set of methods that are called by the connection manager when a set of events occurs. Following are the prototypes for the methods of IDccManSink:
HRESULT IDccManSink::OnLogListen (void);
HRESULT IDccManSink::OnLogAnswered (void);
HRESULT IDccManSink::OnLogIpAddr (DWORD dwIpAddr);
HRESULT IDccManSink::OnLogActive (void);
HRESULT IDccManSink::OnLogTerminated (void);
HRESULT IDccManSink::OnLogInactive (void);
HRESULT IDccManSink::OnLogDisconnection (void);
HRESULT IDccManSink::OnLogError (void);
Although the documentation describes a step-by-step notification by the connection manager, calling each of the methods of IDccManSink as the events occur, I've found that only a few of the methods are actually called with any consistency.When you call CoCreateInstance to get a pointer to the IDccManSink interface, the connection manager is loaded into memory. When you call Advise, the connection manager responds with a call to OnLogListen, indicating that the connection manager is listening for a connection. When a connection is established, the connection manager calls OnLogIpAddr to notify you of the IP address of the connected device. OnLogIpAddr is the only method in IDccManSink that has a parameter. This parameter is the IP address of the device being connected. This address is handy if you want to establish a socket connection to the device, bypassing the extensive support of the connection manager and RAPI. This IP address can change between different devices and even when connecting the same device if one connection is made using the serial link and a later connection is made across a LAN. The connection manager then calls OnLogActive to indicate that the connection between the PC and the device is up and fully operational.When the connection between the PC and the Windows CE device is dropped, the connection manager calls the OnLogDisconnection method. This disconnection notification can take up to a few seconds before it's sent after the connection has actually been dropped. The connection manager then calls the OnLogListen method to indicate that it is in the listen state, ready to initiate another connection.
Some of the other methods are called under Windows Me. Those methods simply refine the state of the connection even further. Since your application has to operate as well under Windows XP as it does under Windows Me, you'll need to be able to operate properly using only the notifications I've just described.
The CnctNote Example Program
The CnctNote program is a simple dialog box–based application that uses the COM-based method for monitoring the PC–to–Windows CE device connection state. The example doesn't act on the notifications—it simply displays them in a list box. The CnctNote window is shown in Figure 15-3.

Figure 15-3: The CnctNote window shows two consecutive connections from different devices.
The source code for CnctNote is shown in Listing 15-5.Listing 15-5: CnctNote source code
CnctNote.rc
//====================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
#include "windows.h"
#include "CnctNote.h" // Program-specific stuff
//-----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON "CnctNote.ico" // Program icon
//-----------------------------------------------------------------------
CnctNote DIALOG discardable 10, 10, 220, 160
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU |
DS_CENTER | DS_MODALFRAME
CAPTION "CnctNote"
CLASS "CnctNote"
BEGIN
LISTBOX IDC_RPTLIST, 2, 10, 216, 140,
WS_TABSTOP | WS_VSCROLL
END
CnctNote.h
//====================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
//-----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT { // Structure associates
UINT Code; // messages
// with a function.
LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};
//-----------------------------------------------------------------------
// Generic defines used by application
#define ID_ICON 1
#define IDC_RPTLIST 10 // Control IDs
//-----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPSTR, int);
int TermInstance (HINSTANCE, int);
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...);
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
//********************************************************************
// MyDccSink
//
class MyDccSink : public IDccManSink {
public:
MyDccSink (HWND hWnd, IDccMan *pDccMan);
~MyDccSink ();
// *** IUnknown methods ***
STDMETHODIMP QueryInterface (THIS_ REFIID riid, LPVOID * ppvObj);
// Note: No reference counting is actually maintained on this object.
STDMETHODIMP_(ULONG) AddRef (THIS);
STDMETHODIMP_(ULONG) Release (THIS);
// These methods correspond to GW_LOG messages generated by the Win95
// DccMan application. (On NT/XP, the GW_LOG messages are simulated.)
STDMETHODIMP OnLogIpAddr (THIS_ DWORD dwIpAddr);
STDMETHODIMP OnLogTerminated (THIS);
STDMETHODIMP OnLogActive (THIS);
STDMETHODIMP OnLogInactive (THIS);
STDMETHODIMP OnLogAnswered (THIS);
STDMETHODIMP OnLogListen (THIS);
STDMETHODIMP OnLogDisconnection (THIS);
STDMETHODIMP OnLogError (THIS);
private:
long m_lRef;
HWND hWnd;
IDccMan *m_pDccMan;
};
CnctNote.cpp
//====================================================================
// CnctNote - A simple application for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//====================================================================
#include <windows.h> // For all that Windows stuff
#include <stdio.h>
#include <initguid.h>
#include <dccole.h>
#include "CnctNote.h" // Program-specific stuff
//-----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("CnctNote");
HINSTANCE hInst; // Program instance handle
BOOL fFirst = TRUE;
IDccMan *pDccMan;
MyDccSink *pMySink; // Notification interface
DWORD g_Context; // Context variable
//====================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
MSG msg;
HWND hwndMain;
// Initialize application.
hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hwndMain == 0)
return TermInstance (hInstance, 0x10);
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
if ((hwndMain == 0) || !IsDialogMessage (hwndMain, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//-----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow){
WNDCLASS wc;
HWND hWnd;
HRESULT hr;
// Save program instance handle in global variable.
hInst = hInstance;
// Initialize COM.
hr = CoInitialize(NULL);
if (FAILED(hr)) {
MessageBox (NULL, "CoInitialize failed.", szAppName, MB_OK);
return 0;
}
// Register application main window class.
wc.style = 0; // Window style
wc.lpfnWndProc = MainWndProc; // Callback function
wc.cbClsExtra = 0; // Extra class data
wc.cbWndExtra = DLGWINDOWEXTRA; // Extra window data
wc.hInstance = hInstance; // Owner handle
wc.hIcon = NULL, // Application icon
wc.hCursor = NULL; // Default cursor
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return 0;
// Create main window.
hWnd = CreateDialog (hInst, szAppName, NULL, NULL);
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// Standard show and update calls
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
return hWnd;
}
//-----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
// Release COM.
CoUninitialize();
return nDefRC;
}
//====================================================================
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
switch (wMsg) {
case WM_SIZE:
if (fFirst) {
HRESULT hr;
IDccManSink *pdms;
fFirst = FALSE;
// Get a pointer to the IDccMan COM interface.
hr = CoCreateInstance (CLSID_DccMan, NULL, CLSCTX_SERVER,
IID_IDccMan, (LPVOID*)&pDccMan);
if (FAILED(hr)) {
Add2List (hWnd, "CoCreateInstance failed");
break;
}
// Create new notification object.
pMySink = new MyDccSink(hWnd, pDccMan);
pMySink->QueryInterface (IID_IDccManSink, (void **)&pdms);
// Ask to be advised of connect state changes.
pDccMan->Advise (pdms, &g_Context);
}
break;
case WM_COMMAND:
switch (LOWORD (wParam)) {
case IDOK:
case IDCANCEL:
SendMessage (hWnd, WM_CLOSE, 0, 0);
break;
}
break;
case WM_DESTROY:
// Stop receiving notifications.
pDccMan->Unadvise (g_Context);
// Release the DccMan object.
pDccMan->Release();
PostQuitMessage (0);
break;
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//-----------------------------------------------------------------------
// Add2List - Add string to the report list box.
//
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf, i;
TCHAR szBuffer[512];
va_list args;
va_start(args, lpszFormat);
nBuf = vsprintf(szBuffer, lpszFormat, args);
i = SendDlgItemMessage (hWnd, IDC_RPTLIST, LB_ADDSTRING, 0,
(LPARAM)(LPCTSTR)szBuffer);
if (i != LB_ERR)
SendDlgItemMessage (hWnd, IDC_RPTLIST, LB_SETTOPINDEX, i,
(LPARAM)(LPCTSTR)szBuffer);
va_end(args);
}
//********************************************************************
// Constructor
MyDccSink::MyDccSink (HWND hwndMain, IDccMan *pDccMan) {
m_pDccMan = pDccMan;
hWnd = hwndMain;
m_pDccMan->AddRef();
return;
}
//-----------------------------------------------------------------------
// Destructor
MyDccSink::~MyDccSink () {
m_pDccMan->Release();
return;
}
//-----------------------------------------------------------------------
// AddRef - Increment object ref count.
STDMETHODIMP_(ULONG) MyDccSink::AddRef (THIS) {
return (ULONG)InterlockedIncrement (&m_lRef);
}
//-----------------------------------------------------------------------
// Release - Decrement object ref count.
STDMETHODIMP_(ULONG) MyDccSink::Release (THIS) {
ULONG cnt;
cnt = (ULONG)InterlockedDecrement (&m_lRef);
if (cnt == 0) {
delete this;
return 0;
}
return cnt;
}
//-----------------------------------------------------------------------
// QueryInterface – Return a pointer to interface.
STDMETHODIMP MyDccSink::QueryInterface (REFIID riid, LPVOID * ppvObj) {
if (IID_IUnknown==riid || IID_IDccManSink==riid)
*ppvObj = (IDccManSink*)this;
else {
*ppvObj = NULL;
return E_NOINTERFACE;
}
AddRef();
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogIpAddr (DWORD dwIpAddr) {
Add2List (hWnd, TEXT ("OnLogIpAddr: %02d.%02d.%02d.%02d"),
(dwIpAddr & 0x000000ff), (dwIpAddr & 0x0000ff00)>>8,
(dwIpAddr & 0x00ff0000)>>16, dwIpAddr>>24);
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogTerminated () {
Add2List (hWnd, TEXT ("OnLogTerminated "));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogActive () {
Add2List (hWnd, TEXT ("OnLogActive "));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogInactive () {
Add2List (hWnd, TEXT ("OnLogInactive "));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogAnswered () {
Add2List (hWnd, TEXT ("OnLogAnswered"));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogListen () {
Add2List (hWnd, TEXT ("OnLogListen "));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogDisconnection () {
Add2List (hWnd, TEXT ("OnLogDisconnection "));
return NO_ERROR;
}
//-----------------------------------------------------------------------
//
STDMETHODIMP MyDccSink::OnLogError () {
Add2List (hWnd, TEXT ("OnLogError "));
return NO_ERROR;
}
The meat of CnctNote is in the WM_SIZE handler of the window procedure. Here, CoCreateInstance is called to get a pointer to the IDccMan interface. If this is successful, an object is created that implements an IDccManSink interface. The Advise method is then called to register the IDccManSink object. The sole job of the methods in IDccManSink is to report when they're called by posting a message in the list box, which is the only control on the dialog box.
Connection Detection on the Windows CE Side
Chapter 11.[1] Many books have been written about COM, but only one, Mr. Bunny’s Guide to ActiveX, captures the essence of COM. Check it out if you get the opportunity.