OBEX
The OBEX standard provides a vendor-independent standard for transferring files, business card information, and calendar information between devices. Windows CE supports OBEX over IrDA and Bluetooth. The embedded version of Windows CE supports a number of OBEX protocols, depending on how the system is configured. The Pocket PC supports the OBEX Push protocol although additional protocols may be optionally supported by OEMs. This section covers how to use OBEX to detect devices in range of either IrDA or Bluetooth as well as how to use OBEX to send files to another device.The OBEX support under Windows CE is provided through a series of COM interfaces. The primary interface is IObex or its modestly enhanced derivative, IObex2. This interface provides support for initialization and shutdown of OBEX support as well as device enumeration. Other interfaces used when working with OBEX include IObexDevice, for communication with a device; and IObexSink, which provides a callback interface in the application.
Initialization
To initialize the OBEX system, an application must first create an IObex or IObex2 object. The difference between the two interfaces is a single method, PauseDeviceEnum, which provides the handy feature of suspending the device enumeration and resuming instead of having to stop and restart it. Creating the object is easily accomplished using the COM function CoCreateInstance. Once the object is created, the OBEX system can be initialized with a call to the Initialize method. The following code shows this initialization process.
IObex *pObex = NULL;
HRESULT hr = CoCreateInstance (_uuidof(Obex), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IObex), (void **)&pObex);
if (FAILED(hr))
return hr;
if (pObex != NULL)
pObex->Initialize ();
else
return -1;
Application Callbacks
The best way to keep informed concerning the status of the OBEX system is for the application to support an IObexSink interface that the OBEX system can call in to with status messages. Application callbacks are optional; an application using OBEX isn't required to support them. However, the callback scheme enables the best method for device detection and also provides a way for the OBEX system to notify the application when a remote device requires a password for a connection.The IObexSink interface is a standard COM interface with a single additional method, Notify, prototyped as
HRESULT IObexSink::Notify (OBEX_EVENT Event,
IUnknown *pUnk1, IUnknown *pUnk2);
To tell the OBEX system about the callback interface, the application must ask the OBEX interface for its connection point container. Once found, the connection point container is queried for an IObexSink connection point. Once that's found, the Advise method can be called on the connection point pointing to the IObexSink interface in the application. The process is best illustrated in the simple code fragment shown here:
// Create my class the implements an IObexSink interface
MyObexSink *pSink;
pSink = new MyObexSink(hWnd);
if (!pSink)
return -1;
// Create connection point container
hr = pObex->QueryInterface (IID_IConnectionPointContainer,
(LPVOID *)&pContainer);
if (!SUCCEEDED(hr) || (pContainer == 0))
return -2;
hr = pContainer->FindConnectionPoint (IID_IObexSink, &pConPt);
if (!SUCCEEDED(hr) || (pConPt == 0)) {
pContainer->Release();
return -3;
}
// Ask for notifications
hr = pConPt->Advise((IUnknown *)pSink, &dwCookie);
When a callback occurs, the Event parameter provides the reason for the callback. In theory, the pUnk1 and pUnk2 parameters are pointers to COM objects that are relevant for each event. In practice, for the supported events, pUnk1 is a pointer to an IPropertyBag interface that contains information about the device.The events reported by the OBEX system are listed here:
OE_QUERY_PASSWORD The remote device needs a password to continue.
OE_DEVICE_ARRIVAL A new device has been detected in range.
OE_DEVICE_DEPARTURE A device has moved out of range.
OE_DEVICE_UPDATE New information is available on a device in range.
At the time the callback is made, the OBEX object is blocked from other work for the application, so the code in the callback interface must be executed quickly. Traditionally, the interface simply posts a message with the relevant details to a window where the details are examined asynchronously from the callback.
Device Discovery
One of the most convenient aspects of OBEX programming is that most functions the application uses apply to devices using both IrDA and Bluetooth. Device discovery is one of these common areas. Device discovery can be accomplished in a synchronous or an asynchronous manner. The synchronous method is simpler, but the asynchronous method is much more flexible.
Synchronous Device Detection
Synchronous device detection is accomplished by calling the IObex method StartDeviceEnum, prototyped as
HRESULT IObex::StartDeviceEnum (void);
StartDeviceEnum returns immediately, but the OBEX system starts monitoring for devices. The application must wait some amount of time for the OBEX system to gather information about the devices. Typically, the application should wait 5 or more seconds before calling back to get a list of the devices detected. The application doesn't have to be idle during this wait. The application can set a Windows timer, using SetTimer to have the operating system send a timer message back to the application 5 seconds after the initial call to StartDeviceEnum.Once the enumeration time has elapsed, the application can call StopDeviceEnum, prototyped as
HRESULT IObex::StopDeviceEnum (void);
This method doesn't have to be called before the EnumDevices method (discussed next), but it must not be called until the OBEX system has had time to enumerate the local devices.To query the devices the OBEX system has discovered, call EnumDevices, prototyped as
HRESULT IObex::EnumDevices (IDeviceEnum *ppDeviceEnum,
REFCLSID uuidTransport);
The first parameter is the address of a pointer to an IDeviceEnum interface. This pointer is set by the method to an IDeviceEnum object that can be used to enumerate the devices. The second parameter is the GUID of the transport—Bluetooth, IrDA, or others—that the application is interested in. If the application wants a list of all devices regardless of the transport they support, a NULL value can be passed in the second parameter.The IDeviceEnum interface, used to enumerate the devices, has four methods: Next, Reset, Skip, and Clone. To return a list of the devices, use the Next method:
HRESULT IDeviceEnum::Next (ULONG celt, IObexDevice **rgelt,
ULONG *pceltFetched);
The second parameter of the Next method points to an array of IObexDevice interface pointers. The first parameter, celt, should be set to the number of the entries in the IObexDevice pointer array. The third parameter is the address of a ULONG that receives the number of IObexDevice pointers returned in the array.The Next method can be used in two ways. In the first way, Next is called with the celt parameter set to 1 so that the call returns a single IObexDevice pointer. Using the method this way, Next will have to be repeatedly called to return a device pointer for each device found. The other way to use the Next method is to call it once but pass a large array of IObexDevice pointers, which will be filled with pointers for all the devices. Either way will return similar device information.The other methods of IDeviceEnum are shown here:
HRESULT IDeviceEnum::Skip (ULONG celt);
HRESULT IDeviceEnum::Reset (void);
HRESULT IDeviceEnum::Clone (IDeviceEnum *ppenum);
These relatively self-explanatory methods allow the application to skip a set number of devices in the enumeration, reset the enumeration back to the first device, and create a new copy of the IDeviceEnum interface.
Asynchronous Device Detection
Asynchronous device detection is accomplished by starting the detection with the same call to StartDeviceEnum as in the synchronous detection. The difference is that before the call to StartDeviceEnum is made, the application provides an IObexSink interface to be notified with an OE_DEVICE_ARRIVAL event when devices are detected. There is no need to call the IObex method EnumDevices because the notification process will provide the device information as the devices are discovered.One interesting aspect of asynchronous device detection is that the device information returned in the IObexSink callback is initially incomplete. The OBEX system will initially report that a device has been found but provides little information about the device. As more information about the device is gathered, the OBEX system provides additional callback events, this time with an OE_DEVICE_UPDATE notification. Because of this, the application needs to tolerate the incomplete information and parse the update notifications to complete the information about the discovered device.Information about the devices is passed in the notification callback through a PropertyBag object. This object has information such as the name of the device, its address, the transport supported, and services supported by the device. Because of the repeated updates on the same device, the application has to determine the device being updated and add the updated information to that device. The ObexSquirt example later in this chapter demonstrates how to keep track of devices through asynchronous detection.If the device supports Bluetooth, there is a good possibility that the device enumeration process will detect more than one device able to communicate. The application has to be written to provide the user the ability to select the target device.
OBEX Communication
Once a device has been chosen as a target, the application must connect with the device to initialize communication. The connection process starts with accessing the IObexDevice interface associated with the target device. If the device enumeration was accomplished with synchronous enumeration, the IDeviceEnum::Next method returns a list of IObexDevice interface pointers, one for each device found. For asynchronous enumeration, the only information the application has is a PropertyBag object for each device. To convert that to an IObexDevice interface, IObex provides the BindToDevice method, prototyped as
HRESULT IObex::BindToDevice (IPropertyBag* pPropertyBag,
IObexDevice** ppDevice);
The BindToDevice method returns the IObexDevice pointer that matches the device described in the PropertyBag object pointed to by the first parameter.The IObexDevice interface provides the basic set of methods used to communicate with a device. The first method of interest is the Connect method, prototyped as
HRESULT IObexDevice::Connect (LPCWSTR pszPassword, DWORD dwCapability,
IHeaderCollection* pHeaders);
The first parameter is the password to the remote device. If this parameter isn't specified and the remote device needs a password, the OBEX system will call back to the application to request the password. The dwCapability parameter is reserved and should be set to 0. The final parameter is a pointer to a header collection object. This object must be created before the call to Connect and contains a list of specifications for the connection.A header collection object is a COM object that's used to describe the OBEX operation being attempted. For connections, the header collection can optionally contain a target service to connect to on the remote device.The target is added to the header collection object using the AddTarget method, shown here:
HRESULT IHeaderCollection::AddTarget (unsigned long ulSize, byte * pData);
The two parameters are the length of the target data being set and the target data itself.If no target service is specified, a connection is attempted to the default OBEX service, Object Push, which is fielded on Windows CE devices by Pocket InBox. Other OBEX services can be supported by devices. The two services of most interest to Windows CE developers are the file browsing service, or ObexFTP, and the Sync service. To connect to the FTP service, the target service should be set to the File Browsing GUID:
{F9ec7bc4-953c-11d2-984e-525400dc9e09}
Note that the GUID needs to be specified in network byte order instead of the Windows-standard little-endian byte order. The following code takes a PropertyBag object that describes a device and connects to the device's FTP service.
// {F9ec7bc4-953c-11d2-984e-525400dc9e09}
GUID CLSID_FTP_NetOrder={0xc47becf9, 0x3c95, 0xd211,
{0x98, 0x4e, 0x52, 0x54, 0x00, 0xdc, 0x9e, 0x09}};
//
// Start here with a pointer to a IPropertyBag describing the device
//
HRESULT hr = pObex->BindToDevice (pDevPropBag, &pDevice);
IHeaderCollection *pHC = 0;
hr = CoCreateInstance(__uuidof(HeaderCollection), NULL,
CLSCTX_INPROC_SERVER, __uuidof(IHeaderCollection),
(void **)&pHC);
if (FAILED(hr)) {
CloseHandle (hFile);
return -2;
}
pHC->AddTarget(sizeof (CLSID_FTP_NetOrder),(UCHAR *)&CLSID_FTP_NetOrder);
// Connect to device
hr = pDevice->Connect (NULL, 0, pHC);
if (FAILED(hr)) {
printf ("Connect fail %x %d", hr, GetLastError());
pHC->Release();
CloseHandle (hFile);
return -3;
}
printf ("Connected...");
Once connected, the application can exchange objects using the Get and Put methods, prototyped as follows:
HRESULT Get (IHeaderCollection* pHeaders, IStream** ppStream);
HRESULT Put (IHeaderCollection* pHeaders, IStream** ppStream);
For both these methods, a header collection object must be created first to describe the object being sent or requested. The object name is specified using the AddName method of the header collection object, defined as
HRESULT IHeaderCollection::AddName (LPCWSTR pszName);
The only parameter is the string containing the name of the object.When Put or Get is called, it returns a pointer to an IStream interface. The IStream interface provides the basic read and write methods for reading and writing to the other device. The following code demonstrates how to push a file from the application to another connected device:
//get a header collection
IHeaderCollection *pFileHC = 0;
hr = CoCreateInstance(__uuidof(HeaderCollection), NULL,
CLSCTX_INPROC_SERVER, __uuidof(IHeaderCollection),
(void **)&pFileHC);
if (FAILED(hr)) {
return -2;
}
// Add file name to header
hr = pFileHC->AddName(pszFileName);
if (FAILED(hr)) {
pFileHC->Release();
return -3;
}
// Send header
IStream *stOut = 0;
hr = pDevice->Put(pFileHC, &stOut);
if (FAILED(hr)) {
pFileHC->Release();
return -4;
}
// Send the data
nTotal = nFileSize;
while (nFileSize) {
// Send up to the block size
nCnt = min (BUFFSIZE, nFileSize);
if (!ReadFile (hFile, pBuff, nCnt, &dwBytesWritten, FALSE)) {
Add2List (hWnd, TEXT("ReadFile error %d "), GetLastError());
break;
}
nCnt = (int)dwBytesWritten;
hr = stOut->Write (pBuff, nCnt, &dwBytesWritten);
if(FAILED(hr)) {
break;
}
nFileSize -= (int)dwBytesWritten;
}
printf ("Done");
When the application has completed its transfer and needs to disconnect, a call to the Disconnect method of IObexDevice should be made. The method is defined as
HRESULT IObexDevice::Disconnect (IHeaderCollection* pHeaders);
The method requires a header collection object. If the application is disconnecting from the default Object Push service, the header simply needs to be created. For disconnecting from other services, the target service should be set to match the service originally connected to.The IObexDevice interface supports a handful of other methods, shown here:
HRESULT IObexDevice::SetPath (LPCWSTR pszName, DWORD dwFlags,
IHeaderCollection* pHeaders);
HRESULT IObexDevice::EnumProperties (REFIID riid, void** ppv);
HRESULT IObexDevice::SetPassword (LPCWSTR pszPassword);
HRESULT IObexDevice::Abort (IHeaderCollection* pHeaders);
The SetPath method is used in the FTP service to specify the target directory on the remote device. The security settings of the other device typically restrict the path to acceptable directories.
The EnumProperties method returns the PropertyBag object associated with the device. This function is essentially the inverse of the IObex::BindToDevice method. The SetPassword method provides another way to set the password for remote device access. Finally, the Abort method provides a way for the application to halt a task.
The ObexSquirt Example Program
The ObexSquirt example demonstrates the use of the OBEX service to connect and send a file to another device. Since the example uses OBEX, the transfer can take place over IrDA or Bluetooth because the OBEX system handles all the grisly transport details.Figure 14-5 shows the ObexSquirt program running on an embedded Windows CE device. Notice that the upper list box contains the list of a number of devices in range of this system. The example works by using asynchronous device enumeration to query the area for devices. As information is returned about each device, the text in the device list is updated to provide the name of the device and the supported protocol. Devices are listed twice in the list if they support both Object Push and the FTP OBEX service.

Figure 14-5: The ObexSquirt program, showing the devices in range of the system
The source code for ObexSquirt is shown in Listing 14-3. Unlike both the MySquirt and BtHello examples, ObexSquirt is a single-threaded application. The OBEX service provides some threading because it provides asynchronous device search. The example exposes an IObexSink interface that's called when devices are found. When a notification is received, the application simply posts a message to its main window, where the notification is actually processed.Listing 14-3: The ObexSquirt source code
ObexSquirt.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2001 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
// Windows CE Specific defines
#define LPCMDLINE LPWSTR
//----------------------------------------------------------------------
// 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.
};
//----------------------------------------------------------------------
// Defines used by application
#define ID_ICON 1
#define IDD_INTEXT 10 // Control IDs
#define IDD_SENDFILE 11
#define IDD_OUTTEXT 12
#define IDD_SCAN 13
#define IDD_DEVICES 14
#define MYMSG_OBEXEVENT (WM_USER+1000)
#define MYMSG_PRINTF (WM_USER+1001)
ObexSquirt.h
//======================================================================
// Header file
//
// Written for the book Programming Windows CE
// Copyright (C) 2001 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0]))
// Windows CE Specific defines
#define LPCMDLINE LPWSTR
//----------------------------------------------------------------------
// 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.
};
//----------------------------------------------------------------------
// Defines used by application
#define ID_ICON 1
#define IDD_INTEXT 10 // Control IDs
#define IDD_SENDFILE 11
#define IDD_OUTTEXT 12
#define IDD_SCAN 13
#define IDD_DEVICES 14
#define MYMSG_OBEXEVENT (WM_USER+1000)
#define MYMSG_PRINTF (WM_USER+1001)
***
#define DEV_FLAG_ADDRESS 0x00000001
#define DEV_FLAG_NAME 0x00000002
#define DEV_FLAG_TRANSPORT 0x00000004
#define DEV_FLAG_PORT 0x00000008
#define DEV_FLAG_UUID 0x00000010
#define DEV_FLAG_DEVBOUND 0x00000100
#define DEV_TRANS_IRDA 0x00010000
#define DEV_TRANS_BTOOTH 0x00020000
#define DEV_SERVICE_OBJPUSH 0x01000000
#define DEV_SERVICE_FTP 0x02000000
#define DEV_SERVICE_IRMCSYNC 0x04000000
typedef struct {
DWORD dwFlags;
TCHAR szName[256];
TCHAR szAddr[32];
DWORD dwTransport;
DWORD dwPort;
GUID guidService;
IPropertyBag* pDevBag;
} MYOBEXDEVICEINFO, *PMYOBEXDEVICEINFO;
#define MAX_DEVS 16
#define BUFFSIZE 8192
//----------------------------------------------------------------------
// Function prototypes
//
HWND InitInstance (HINSTANCE, LPCMDLINE, int);
int TermInstance (HINSTANCE, int);
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...);
int InitObex (HWND hWnd);
int SendFile (HWND hWnd, IObexDevice *pDevice, LPTSTR pszFileName,
DWORD dwFlags);
BOOL MyYield ();
// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoSizeMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPocketPCShell (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoObexEventMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoPrintfNotifyMain (HWND, UINT, WPARAM, LPARAM);
// Command functions
LPARAM DoMainCommandSend (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandDevList (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
IObexSink.cpp
//======================================================================
// MyObexSink - A lightweight COM class to receive obex notifications.
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
class MyObexSink : public IObexSink {
private:
HWND m_hWnd;
int m_lRef;
public:
// Constructor
MyObexSink(HWND hWnd) {
m_hWnd = hWnd;
m_lRef = 0;
}
// Destructor
~MyObexSink() {}
//
// Notify - Callback from Obex code. Must return quick so no
// real work done here.
//
HRESULT STDMETHODCALLTYPE Notify (OBEX_EVENT Event, IUnknown *pUnk1,
IUnknown *pUnk2)
{
if (IsWindow (m_hWnd)) {
// Inc the cnt of unk1 so it'll stay around.
pUnk1->AddRef();
PostMessage (m_hWnd, MYMSG_OBEXEVENT, (WPARAM)Event,
(LPARAM)pUnk1);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE QueryInterface (REFIID riid, LPVOID *ppv)
{
// If caller wants our IUnknown or IClassFactory object,
// return a pointer to the object.
if (IsEqualIID (riid, IID_IUnknown) ||
IsEqualIID (riid, IID_IObexSink)) {
// Return pointer to object.
*ppv = (IConnectionPoint *)this;
AddRef(); // Increment ref
return NOERROR;
}
*ppv = NULL;
return (E_NOINTERFACE);
}
ULONG STDMETHODCALLTYPE AddRef () {
ULONG cnt = (ULONG)InterlockedIncrement ((long *)&m_lRef);
return cnt;
}
ULONG STDMETHODCALLTYPE Release () {
ULONG cnt;
cnt = (ULONG)InterlockedDecrement ((long *)&m_lRef);
if (cnt == 0) {
delete this;
return 0;
}
return cnt;
}
};
ObexSquirt.cpp
//======================================================================
// ObexSquirt - A simple Obex 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 <obex.h>
#include <Msgqueue.h>
#if defined(WIN32_PLATFORM_PSPC)
#include <aygshell.h> // Add Pocket PC includes
#pragma comment( lib, "aygshell" ) // Link Pocket PC lib for menubar
#endif
#include "obexsquirt.h" // Program-specific stuff
#include "MyObexSink.cpp" // IObexSink class
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("obexsquirt");
const TCHAR szTitleText[] = TEXT ("OBEX Squirt");
TCHAR szTitle[128];
HINSTANCE hInst; // Program instance handle
HWND hMain; // Main window handle
BOOL fFirstSize = TRUE; // First WM_SIZE flag
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
SHACTIVATEINFO sai; // Needed for PPC helper funcs
#endif
// The GUID strings below are defined numerically in bt_sdp.h
const TCHAR g_szIrMCSyncGuid[] =
TEXT("{00001104-0000-1000-8000-00805f9b34fb}");
const TCHAR g_szObjPushGuid[] =
TEXT("{00001105-0000-1000-8000-00805f9b34fb}");
const TCHAR g_szFtpGuid[] =
TEXT("{00001106-0000-1000-8000-00805f9b34fb}");
const TCHAR g_szTransIrDA[] =
TEXT("{30a7bc02-59b6-40bb-aa2b-89eb49ef274e}");
const TCHAR g_szTransBth[] =
TEXT("{30a7bc03-59b6-40bb-aa2b-89eb49ef274e}");
// {F9ec7bc4-953c-11d2-984e-525400dc9e09}
GUID CLSID_FileExchange_NetOrder = {0xc47becf9, 0x3c95, 0xd211,
{0x98, 0x4e, 0x52, 0x54, 0x00, 0xdc, 0x9e, 0x09}};
HANDLE hQRead = 0; // Msg queues are used to sync
HANDLE hQWrite = 0; // output to the listbox
CRITICAL_SECTION csPrintf;
CRITICAL_SECTION csLock;
IObex *pObex = NULL; // Obex main interface ptr
BOOL fObex2IF = FALSE;
IConnectionPointContainer *pContainer = NULL;
IConnectionPoint *pConPt = NULL;
DWORD dwCookie;
MYOBEXDEVICEINFO obDevs[MAX_DEVS];
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateMain,
WM_SIZE, DoSizeMain,
WM_COMMAND, DoCommandMain,
MYMSG_OBEXEVENT, DoObexEventMain,
MYMSG_PRINTF, DoPrintfNotifyMain,
WM_SETTINGCHANGE, DoPocketPCShell,
WM_ACTIVATE, DoPocketPCShell,
WM_DESTROY, DoDestroyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
IDOK, DoMainCommandExit,
#else
IDOK, DoMainCommandSend,
#endif
IDCANCEL, DoMainCommandExit,
IDD_SENDFILE, DoMainCommandSend,
IDD_DEVICES, DoMainCommandDevList,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPCMDLINE lpCmdLine, int nCmdShow) {
MSG msg;
int rc = 0;
// Initialize this instance.
hMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
if (hMain) {
// Application message loop
while (GetMessage (&msg, NULL, 0, 0)) {
if ((hMain == 0) || !IsDialogMessage (hMain, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
}
// Instance cleanup
return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPCMDLINE lpCmdLine,
int nCmdShow){
WNDCLASS wc;
HWND hWnd;
hInst = hInstance; // Save program instance handle.
// For all systems, if previous instance, activate it instead of us.
hWnd = FindWindow (szAppName, NULL);
if (hWnd) {
SetForegroundWindow ((HWND)((DWORD)hWnd | 0x01));
return 0;
}
// Init COM
CoInitializeEx (NULL, COINIT_MULTITHREADED);
// Init device structure
memset (&obDevs, 0, sizeof (obDevs));
// Create message queues for async string out to listbox
MSGQUEUEOPTIONS mqo;
mqo.dwSize = sizeof (mqo);
mqo.dwFlags = MSGQUEUE_ALLOW_BROKEN;
mqo.dwMaxMessages = 16;
mqo.cbMaxMessage = 512;
mqo.bReadAccess = TRUE;
hQRead = CreateMsgQueue (TEXT ("MSGQUEUE\\ThTead"), &mqo);
mqo.bReadAccess = FALSE;
hQWrite = CreateMsgQueue (TEXT ("MSGQUEUE\\ThTead"), &mqo);
// 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 = LoadCursor (NULL, IDC_ARROW);// Default cursor
wc.hbrBackground = (HBRUSH) GetStockObject (LTGRAY_BRUSH);
wc.lpszMenuName = NULL; // Menu name
wc.lpszClassName = szAppName; // Window class name
if (RegisterClass (&wc) == 0) return (HWND)0;
// Create main window.
hWnd = CreateDialog (hInst, szAppName, NULL, NULL);
// Return fail code if window not created.
if (!IsWindow (hWnd)) return 0;
// Init obex
if (!InitObex (hWnd)) {
return 0;
}
ShowWindow (hWnd, nCmdShow); // Standard show and update calls
UpdateWindow (hWnd);
SetFocus (GetDlgItem (hWnd, IDD_OUTTEXT));
return hWnd;
}
//----------------------------------------------------------------------
// ResetDevList - Clean up the list of devices
//
int ResetDevList (void) {
int i;
// Clean up property bags
for (i = 0; i < dim (obDevs); i++) {
if (obDevs[i].pDevBag) {
obDevs[i].pDevBag->Release();
obDevs[i].pDevBag = 0;
}
}
return 0;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
if (pObex) {
pObex->StopDeviceEnum();
ResetDevList ();
if (pConPt) {
pConPt->Unadvise(dwCookie);
Sleep(100);
pConPt->Release();
}
if (pContainer) {
pContainer->Release();
}
pObex->Shutdown();
pObex->Release();
}
CoUninitialize();
return nDefRC;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
INT i;
//
// Search message list to see if we need to handle this
// message. If in list, call procedure.
//
for (i = 0; i < dim(MainMessages); i++) {
if (wMsg == MainMessages[i].Code)
return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
}
return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCreateMain - Process WM_CREATE message for window.
//
LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
SHINITDLGINFO shidi;
SHMENUBARINFO mbi; // For Pocket PC, create
memset(&mbi, 0, sizeof(SHMENUBARINFO)); // menu bar so that we
mbi.cbSize = sizeof(SHMENUBARINFO); // have a sip button
mbi.dwFlags = SHCMBF_EMPTYBAR;
mbi.hwndParent = hWnd;
SHCreateMenuBar(&mbi);
SendMessage(mbi.hwndMB, SHCMBM_GETSUBMENU, 0, 100);
// For Pocket PC, make dialog box full screen with PPC
// specific call.
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLG | SHIDIF_SIPDOWN;
shidi.hDlg = hWnd;
SHInitDialog(&shidi);
sai.cbSize = sizeof (sai);
SHHandleWMSettingChange(hWnd, wParam, lParam, &sai);
#endif
GetWindowText (hWnd, szTitle, dim (szTitle));
return 0;
}
//----------------------------------------------------------------------
// DoSizeMain - Process WM_SIZE message for window.
//
LRESULT DoSizeMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
static RECT rectListbox;
RECT rect;
GetClientRect (hWnd, &rect);
if (fFirstSize) {
// First time through, get the position of the listbox for
// resizing later. Store the distance from the sides of
// the listbox control to the side of the parent window
if (IsWindow (GetDlgItem (hWnd, IDD_INTEXT))) {
GetWindowRect (GetDlgItem (hWnd, IDD_INTEXT), &rectListbox);
MapWindowPoints (HWND_DESKTOP, hWnd, (LPPOINT)&rectListbox, 2);
rectListbox.right = rect.right - rectListbox.right;
rectListbox.bottom = rect.bottom - rectListbox.bottom;
SetWindowPos (GetDlgItem (hWnd, IDD_INTEXT), 0, rect.left+5,
rectListbox.top, rect.right-10,
rect.bottom - rectListbox.top - 5,
SWP_NOZORDER);
}
}
#endif
if (fFirstSize) {
EnableWindow (GetDlgItem (hWnd, IDD_SENDFILE), FALSE);
int i = 40;
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_SETTABSTOPS, 1,
(LPARAM)&i);
fFirstSize = FALSE;
}
return 0;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
WORD idItem, wNotifyCode;
HWND hwndCtl;
INT i;
// Parse the parameters.
idItem = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam);
hwndCtl = (HWND) lParam;
// Call routine to handle control message.
for (i = 0; i < dim(MainCommandItems); i++) {
if (idItem == MainCommandItems[i].Code)
return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
}
return 0;
}
//----------------------------------------------------------------------
// FindDevInfo - Loop through array looking for a device
//
PMYOBEXDEVICEINFO FindDevInfo (IPropertyBag* pDevBag) {
int i, j = -1;
for (i = 0; i < dim (obDevs); i++) {
// See if device data matches search
if (obDevs[i].pDevBag == pDevBag)
return &obDevs[i];
// Find first free index
if ((j == -1) && (obDevs[i].pDevBag == 0)) {
j = i;
}
}
return &obDevs[j];
}
//----------------------------------------------------------------------
// FindDevInList - Search listbox for a device.
//
int FindDevInList (HWND hWnd, PMYOBEXDEVICEINFO pDev) {
int i, nCnt;
LRESULT lr;
// Get the number of items in the listbox
nCnt = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_GETCOUNT, 0, 0);
for (i = 0; i < nCnt; i++) {
lr = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_GETITEMDATA,i,0);
if (lr == (int)pDev)
return i;
}
return LB_ERR;
}
//----------------------------------------------------------------------
// DoObexEventMain - Handles notifications of obex events
//
LRESULT DoObexEventMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
int i;
HRESULT hr;
TCHAR szDevStr[128];
PMYOBEXDEVICEINFO pFoundDev;
MYOBEXDEVICEINFO diDev;
memset (&diDev, 0, sizeof (diDev));
IPropertyBag *pDeviceBag = (IPropertyBag *)lParam;
if (wParam == OE_QUERY_PASSWORD) {
MessageBox (hWnd, TEXT("Query Password"), TEXT("App"), MB_OK);
return 0;
}
// Parse name
VARIANT var;
VariantInit (&var);
hr = pDeviceBag->Read (TEXT("Name"), &var, NULL);
if (SUCCEEDED(hr)) {
diDev.dwFlags |= DEV_FLAG_NAME;
lstrcpy (diDev.szName, var.bstrVal);
}
VariantClear(&var);
// Parse address
hr = pDeviceBag->Read (TEXT("Address"), &var, NULL);
if (SUCCEEDED(hr)) {
diDev.dwFlags |= DEV_FLAG_ADDRESS;
if (var.vt == VT_BSTR)
lstrcpy (diDev.szAddr, var.bstrVal);
else if (var.vt == VT_I4)
wsprintf (diDev.szAddr, TEXT("%08x"), var.ulVal);
else
diDev.dwFlags &= ~DEV_FLAG_ADDRESS;
}
VariantClear(&var);
// Parse port
hr = pDeviceBag->Read (TEXT("Port"), &var, NULL);
if (SUCCEEDED(hr)) {
diDev.dwFlags |= DEV_FLAG_PORT;
if (var.vt == VT_BSTR)
lstrcpy (diDev.szAddr, var.bstrVal);
else if (var.vt == VT_I4)
wsprintf (diDev.szAddr, TEXT("%08x"), var.ulVal);
else
diDev.dwFlags &= ~DEV_FLAG_PORT;
}
VariantClear(&var);
// Parse IrDA information
hr = pDeviceBag->Read (TEXT("IrDA"), &var, NULL);
VariantClear(&var);
hr = pDeviceBag->Read (TEXT("Transport"), &var, NULL);
if (SUCCEEDED(hr)) {
if (var.vt == VT_BSTR) {
WCHAR szTran[40];
memset (szTran, 0, sizeof (szTran));
wcsncpy (szTran, var.bstrVal, 38);
wcslwr (szTran);
if (wcscmp (g_szTransIrDA, szTran) == 0)
diDev.dwFlags |= DEV_TRANS_IRDA;
else if (wcscmp (g_szTransBth, szTran) == 0)
diDev.dwFlags |= DEV_TRANS_BTOOTH;
}
}
VariantClear(&var);
hr = pDeviceBag->Read (TEXT("OBEX:IrXfer"), &var, NULL);
if (SUCCEEDED(hr))
Add2List (hWnd, TEXT("OBEX:IrXfer"));
VariantClear(&var);
// Parse service UUID
hr = pDeviceBag->Read (TEXT("ServiceUUID"), &var, NULL);
if (SUCCEEDED(hr)) {
if (var.vt == VT_BSTR) {
// Compare the guid service string to ones we know about
if (wcsncmp (g_szObjPushGuid, var.bstrVal, 38) == 0)
diDev.dwFlags |= (DEV_FLAG_UUID | DEV_SERVICE_OBJPUSH);
else if (wcsncmp (g_szFtpGuid, var.bstrVal, 38) == 0)
diDev.dwFlags |= (DEV_FLAG_UUID | DEV_SERVICE_FTP);
else if (wcsncmp (g_szIrMCSyncGuid, var.bstrVal, 38) == 0)
diDev.dwFlags |= (DEV_FLAG_UUID | DEV_SERVICE_IRMCSYNC);
}
}
VariantClear(&var);
diDev.pDevBag = pDeviceBag;
// Tell the user what protocols the device supports.
lstrcpy (szDevStr, diDev.szName);
lstrcat (szDevStr, TEXT(" "));
if (diDev.dwFlags & DEV_SERVICE_OBJPUSH)
lstrcat (szDevStr, TEXT("Object Push"));
else if (diDev.dwFlags & DEV_SERVICE_FTP)
lstrcat (szDevStr, TEXT("FTP"));
else if (diDev.dwFlags & DEV_SERVICE_IRMCSYNC)
lstrcat (szDevStr, TEXT("IrMC Sync"));
else if (diDev.dwFlags & DEV_TRANS_IRDA)
lstrcat (szDevStr, TEXT("IrDA"));
// See if device already recorded
pFoundDev = FindDevInfo (pDeviceBag);
// React depending on the notice
switch ((int)wParam) {
case OE_DEVICE_ARRIVAL:
// See if device already found
if (pFoundDev->pDevBag)
break;
memcpy (pFoundDev, &diDev, sizeof (diDev));
i = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_ADDSTRING, 0,
(LPARAM)szDevStr);
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_SETITEMDATA, i,
(LPARAM)pFoundDev);
break;
case OE_DEVICE_UPDATE:
i = LB_ERR;
memcpy (pFoundDev, &diDev, sizeof (diDev));
// Find device entry in list box
if (pFoundDev->pDevBag) {
i = FindDevInList (hWnd, pFoundDev);
// Release because we already hold the propbag
pFoundDev->pDevBag->Release();
}
if (LB_ERR != i)
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_DELETESTRING,i,0);
i = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_INSERTSTRING, i,
(LPARAM)szDevStr);
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_SETITEMDATA, i,
(LPARAM)pFoundDev);
break;
case OE_DISCONNECT:
case OE_DEVICE_DEPARTURE:
// See if device not in device array, ignore disconnect
if (pFoundDev->pDevBag == 0)
break;
// Find device in list box and delete
i = FindDevInList (hWnd, pFoundDev);
if (LB_ERR != i)
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_DELETESTRING,i,0);
// Clear entry in device array
pFoundDev->pDevBag->Release();
pFoundDev->pDevBag = 0;
break;
case OE_QUERY_PASSWORD:
break;
}
return 0;
}
//----------------------------------------------------------------------
// DoPrintfNotifyMain - Process printf notify message
//
LRESULT DoPrintfNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
int rc;
TCHAR szBuffer[512];
DWORD dwLen = 0;
DWORD dwFlags = 0;
memset (szBuffer, 0, sizeof (szBuffer));
rc = ReadMsgQueue (hQRead, (LPBYTE)szBuffer, sizeof (szBuffer),
&dwLen, 0, &dwFlags);
if (rc) {
if (dwFlags & MSGQUEUE_MSGALERT)
SetWindowText (hWnd, szBuffer);
else {
rc = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0,
(LPARAM)(LPCTSTR)szBuffer);
if (rc != LB_ERR)
SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX, rc,
(LPARAM)(LPCTSTR)szBuffer);
}
}
return 0;
}
//----------------------------------------------------------------------
// DoPocketPCShell - Process Pocket PC required messages
//
LRESULT DoPocketPCShell (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
#if defined(WIN32_PLATFORM_PSPC) && (_WIN32_WCE >= 300)
if (wMsg == WM_SETTINGCHANGE)
return SHHandleWMSettingChange(hWnd, wParam, lParam, &sai);
if (wMsg == WM_ACTIVATE)
return SHHandleWMActivate(hWnd, wParam, lParam, &sai, 0);
#endif
return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
Sleep (0); // Pass on timeslice.
PostQuitMessage (0);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
SendMessage (hWnd, WM_CLOSE, 0, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandSend - Process Program Send File command.
//
LPARAM DoMainCommandSend (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int i;
HRESULT hr;
PMYOBEXDEVICEINFO pDev;
TCHAR szName[MAX_PATH];
IObexDevice *pDevice = 0;
GetDlgItemText (hWnd, IDD_OUTTEXT, szName, dim(szName));
if (lstrlen (szName) == 0) {
MessageBox (hWnd, TEXT("File name needed"), TEXT("Error"),
MB_OK);
return 0;
}
// Get the selected device
i = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_GETCURSEL, 0, 0);
if (i != LB_ERR) {
pDev = (PMYOBEXDEVICEINFO)SendDlgItemMessage (hWnd,
IDD_DEVICES, LB_GETITEMDATA,i,0);
// Enumeration must be stopped during transfer
if (fObex2IF)
((IObex2 *)pObex)->PauseDeviceEnum (TRUE);
else
pObex->StopDeviceEnum();
// Bind to the device
MyYield();
hr = 0;
if ((pDev->dwFlags & DEV_FLAG_DEVBOUND) == 0) {
hr = pObex->BindToDevice (pDev->pDevBag, &pDevice);
if (SUCCEEDED (hr))
pDev->dwFlags |= DEV_FLAG_DEVBOUND;
else
Add2List (hWnd, TEXT("BindToDevice failed %x %d"), hr,
GetLastError());
if (SUCCEEDED (hr)) {
i = SendFile (hWnd, pDevice, szName, pDev->dwFlags);
Add2List (hWnd, TEXT("SendFile returned %d"), i);
}
}
// Restart Enumeration after transfer
if (fObex2IF)
((IObex2 *)pObex)->PauseDeviceEnum (FALSE);
else {
pObex->StartDeviceEnum();
ResetDevList ();
SendDlgItemMessage (hWnd, IDD_DEVICES, LB_RESETCONTENT, 0, 0);
}
}
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandDevList - Process Device list box commands.
//
LPARAM DoMainCommandDevList (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int i;
PMYOBEXDEVICEINFO pDev;
if (wNotifyCode == LBN_SELCHANGE) {
i = SendDlgItemMessage (hWnd, IDD_DEVICES, LB_GETCURSEL, 0, 0);
if (i != LB_ERR) {
pDev = (PMYOBEXDEVICEINFO)SendDlgItemMessage (hWnd,
IDD_DEVICES, LB_GETITEMDATA,i,0);
EnableWindow (GetDlgItem (hWnd, IDD_SENDFILE), TRUE);
}
}
return 0;
}
//----------------------------------------------------------------------
// Add2List - Add string to the report list box.
//
void Add2List (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf, nLen;
TCHAR szBuffer[512];
va_list args;
if (hWnd == 0)
hWnd = hMain;
EnterCriticalSection (&csPrintf);
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
va_end(args);
nLen = (lstrlen (szBuffer)+1) * sizeof (TCHAR);
WriteMsgQueue (hQWrite, (LPBYTE)szBuffer, nLen, 0, 0);
PostMessage (hWnd, MYMSG_PRINTF, 0, 0);
LeaveCriticalSection (&csPrintf);
}
//----------------------------------------------------------------------
// MySetWindowText - Set Window title to passed printf style string.
//
void MySetWindowText (HWND hWnd, LPTSTR lpszFormat, ...) {
int nBuf, nLen;
TCHAR szBuffer[512];
va_list args;
EnterCriticalSection (&csPrintf);
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
va_end(args);
nLen = (lstrlen (szBuffer)+1) * sizeof (TCHAR);
WriteMsgQueue (hQWrite, (LPBYTE)szBuffer, nLen, 0, MSGQUEUE_MSGALERT);
PostMessage (hWnd, MYMSG_PRINTF, 0, 0);
LeaveCriticalSection (&csPrintf);
}
//----------------------------------------------------------------------
// MyYield - Flushes the message queue during long operations
//
BOOL MyYield () {
MSG msg;
int rc = 0;
while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) {
if (msg.message == WM_QUIT)
return FALSE;
GetMessage (&msg, NULL, 0, 0);
if ((hMain == 0) || !IsDialogMessage (hMain, &msg)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
return TRUE;
}
//----------------------------------------------------------------------
// InitObex - Initialize the Obex subsystem.
//
int InitObex (HWND hWnd) {
HRESULT hr;
hr = CoCreateInstance (__uuidof(Obex), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IObex2), (void **)&pObex);
if(FAILED(hr)) {
hr = CoCreateInstance (__uuidof(Obex), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IObex), (void **)&pObex);
} else
fObex2IF = TRUE;
if(FAILED(hr)) {
Add2List (hWnd, TEXT("Obex initialization failed! %d %x\n"),
hr, GetLastError());
return 0;
}
if (pObex != NULL)
pObex->Initialize ();
else
return 0;
//set device caps
IObexCaps *pObexCaps = NULL;
hr = pObex->QueryInterface(IID_IObexCaps, (LPVOID *)&pObexCaps);
if(SUCCEEDED(hr)) {
pObexCaps->SetCaps(SEND_DEVICE_UPDATES);
pObexCaps->Release();
}
InitializeCriticalSection(&csLock);
EnterCriticalSection(&csLock);
MyObexSink *pSink;
pSink = new MyObexSink(hWnd);
if (!pSink) {
LeaveCriticalSection(&csLock);
return 0;
}
// Create connection point container
hr = pObex->QueryInterface (IID_IConnectionPointContainer,
(LPVOID *)&pContainer);
if (!SUCCEEDED(hr) || (pContainer == 0)) {
LeaveCriticalSection(&csLock);
return 0;
}
hr = pContainer->FindConnectionPoint (IID_IObexSink, &pConPt);
if (!SUCCEEDED(hr) || (pConPt == 0)) {
pContainer->Release();
LeaveCriticalSection(&csLock);
return 0;
}
// Ask for notifications
hr = pConPt->Advise((IUnknown *)pSink, &dwCookie);
LeaveCriticalSection(&csLock);
// Start device enumeration
if (ERROR_SUCCESS != pObex->StartDeviceEnum())
return 0;
IDeviceEnum *pDeviceEnum = 0;
hr = pObex->EnumDevices(&pDeviceEnum, CLSID_NULL);
if(!SUCCEEDED(hr) || (pDeviceEnum == 0))
return NULL;
Add2List (hWnd, TEXT("EnumDevices succeeded"));
pDeviceEnum->Release ();
return 1;
}
//----------------------------------------------------------------------
// SendFile - Sends a file to another obex device
//
int SendFile (HWND hWnd, IObexDevice *pDevice, LPTSTR pszFileName,
DWORD dwFlags) {
LPTSTR pszName;
DWORD dwBytesWritten;
int nCnt, nFileSize, nTotal;
HRESULT hr;
HANDLE hFile;
PBYTE pBuff;
Add2List (hWnd, TEXT("Sending file %s"), pszFileName);
pBuff = (PBYTE)LocalAlloc (LPTR, BUFFSIZE);
if (pBuff == 0) return 0;
// prune the path from the file name
pszName = wcsrchr (pszFileName, '\\');
if (pszName == 0)
pszName = pszFileName;
else
pszName++;
// Open the file
hFile = CreateFile (pszFileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
Add2List (hWnd, TEXT("file opened failed. rc %d"),
GetLastError());
return -1;
}
// Get file size
nFileSize = GetFileSize (hFile, NULL);
if (!MyYield ()) return 0;
IHeaderCollection *pHC = 0;
hr = CoCreateInstance(__uuidof(HeaderCollection), NULL,
CLSCTX_INPROC_SERVER, __uuidof(IHeaderCollection),
(void **)&pHC);
if (!MyYield () || FAILED(hr)) {
CloseHandle (hFile);
return -2;
}
if (dwFlags & DEV_SERVICE_FTP)
pHC->AddTarget (sizeof (CLSID_FileExchange_NetOrder),
(UCHAR *)&CLSID_FileExchange_NetOrder);
// Connect to device
hr = pDevice->Connect (NULL, 0, pHC);
if (!MyYield () || FAILED(hr)) {
Add2List (hWnd, TEXT("Connect fail %x %d"), hr, GetLastError());
pHC->Release();
CloseHandle (hFile);
return -3;
}
Add2List (hWnd, TEXT("Connected..."));
//get a header collection
IHeaderCollection *pFileHC = 0;
hr = CoCreateInstance(__uuidof(HeaderCollection), NULL,
CLSCTX_INPROC_SERVER, __uuidof(IHeaderCollection),
(void **)&pFileHC);
if (!MyYield () || FAILED(hr)) {
pHC->Release();
pDevice->Disconnect (pHC);
CloseHandle (hFile);
return -2;
}
// Add file name to header
hr = pFileHC->AddName(pszName);
if (!MyYield () || FAILED(hr)) {
pHC->Release();
pFileHC->Release();
CloseHandle (hFile);
return -3;
}
// Send header
IStream *stOut = 0;
hr = pDevice->Put(pFileHC, &stOut);
if (!MyYield () || FAILED(hr)) {
pDevice->Disconnect (pHC);
pHC->Release();
pFileHC->Release();
CloseHandle (hFile);
return -4;
}
// Send the data
nTotal = nFileSize;
while (nFileSize) {
if (!MyYield ()) break;
MySetWindowText (hWnd, TEXT ("%02d%% sent"),
(nTotal-nFileSize)*100/nTotal);
// Send up to the block size
nCnt = min (BUFFSIZE, nFileSize);
if (!ReadFile (hFile, pBuff, nCnt, &dwBytesWritten, FALSE)) {
Add2List (hWnd, TEXT("ReadFile error %d "), GetLastError());
break;
}
nCnt = (int)dwBytesWritten;
Add2List (hWnd, TEXT("sending %d bytes"), nCnt);
if (!MyYield ()) break;
hr = stOut->Write (pBuff, nCnt, &dwBytesWritten);
if(FAILED(hr)) {
Add2List (hWnd, TEXT("send error %x %d"), hr, GetLastError());
break;
}
nFileSize -= (int)dwBytesWritten;
}
MySetWindowText (hWnd, (LPTSTR)szTitleText);
MyYield ();
stOut->Commit (STGC_DEFAULT);
// Clean up
stOut->Release();
pDevice->Disconnect (pHC);
if(pHC)
pHC->Release();
CloseHandle(hFile);
if(pFileHC)
pFileHC->Release();
return 0;
}
This chapter has given you a basic introduction to some of the ways Windows CE devices can communicate with other devices. Next on our plate is networking from a different angle. In Chapter 15, we look at the Windows CE device from the perspective of its companion PC. The link between the Windows CE device and a PC is based on some of the same networking infrastructure that we touched upon here. Let's take a look.