Programming Microsoft Windows Ce Net 3Rd [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Microsoft Windows Ce Net 3Rd [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








Communication



The Smartphone would be nothing but a small PDA with a limited interface if it weren't for its communication features. An intelligent, mobile device, in a pocket and always connected to the Internet, is a powerful platform. The communication features discussed in this section are also implemented on the Pocket PC Phone edition, which is quite helpful for the learning curve of developers, who can apply their knowledge both on the Pocket PC and the Smartphone.


Communication services cover a number of areas, from dialing voice calls to sending messages to other phones to connecting to the Internet through either a wired or wireless connection. For the most part, these services are accessed through easy-to-use shell APIs that wrap the traditional but more complex Windows APIs such as TAPI, the Telephone API.


The folks that create communication functions design the most flexible and therefore most complex functions possible. The functions I describe in this section have what at times seems like dozens of parameters, each with a dozen options. Complete books can be, and have been, written about many of the following topics, so I can't cover completely each of the topics in a single part of a chapter. Still, this discussion will provide a good introduction to these functions and should give you a good start on adding communication features in your applications.



Phone API




The phone API provides a basic set of functions to make calls and query the call log. The phone API is convenient because it avoids the need for most applications to dive directly into the TAPI and make calls with that rather involved interface. TAPI is supported on the Smartphone and Pocket PC Phone edition, but you need not use it unless you're creating the most manipulative of applications.


Dialing the Phone




The first communication feature likely to be tried by a programmer new to the Smartphone or Pocket PC Phone edition is to dial the phone. The system provides a simple and effective function for this, PhoneMakeCall, prototyped as


LONG PhoneMakeCall (PHONEMAKECALLINFO *ppmci);


The PHONEMAKECALLINFO structure is defined as


typedef struct tagPHONEMAKECALLINFO{
DWORD cbSize;
DWORD dwFlags;
PCWSTR pszDestAddress;
PCWSTR pszAppName;
PCWSTR pszCalledParty;
PCWSTR pszComment;
} PHONEMAKECALLINFO, * PHONEMAKECALLINFO;


The first field is the standard size field that needs to be filled with the size of the structure. The dwFlags field can contain one of two flags: PMCF_PROMPTBEFORECALLING, which tells the system to prompt the user before initiating the call, or PMCF_DEFAULT, which tells the system to make the call without asking the user whether the call should be made. Even with the PMCF_DEFAULT flag, the system will display a notification that the call is being made. The pszDestAddress field should point to a string containing the phone number to call. The string can contain the standard phone number separator characters such as dashes, spaces, and parentheses. The pszCalledParty field should point to a string that identifies the called party. This string is displayed, along with the number, on the call-in-progress notification that is displayed when the call is made. The pszAppName and pszComment fields should be set to 0.


Viewing the Call Log




The system maintains a log of all calls made to and from the phone. The call log is a simple database that keeps information such as the time of the call, its duration, and the number of the other phone, as well as details such as whether the call was incoming, the phone is roaming, and so on. The call log can be accessed with a few simple functions. To open the call log, call the aptly named PhoneOpenCallLog, prototyped as


HRESULT PhoneOpenCallLog (HANDLE * ph);


The function returns S_OK if successful and ERROR_FAIL otherwise. An extended error code can be retrieved with GetLastError. Some Smartphone systems don't allow the call log to be opened. There might or might not be a system reason for getting this error, but many phones don't allow this function to succeed. There isn't an issue with Pocket PC phone edition systems. Also, opening the call log isn't necessary for calling PhoneMakeCall. If PhoneOpenCallLog is successful, a handle will be placed in the value pointed to by the parameter ph, and the seek pointer of the call log will be set to point to the first entry in the log.


Once opened, entries in the call log can be queried with the function PhoneGetCallLogEntry, prototyped as


HRESULT PhoneGetCallLogEntry (HANDLE h, PCALLLOGENTRY pentry);


The handle is the one received from the call to PhoneOpenCallLog. The dbSize field of the CALLLOGENTRY structure needs to be initialized with the size of the structure before the function is called.


If the function returns without error, pentry points to a structure that contains data about the call, and the seek pointer of the call log is moved to the next entry. Repeated calls to PhoneGetCallLogEntry will enumerate the entire call log. When no more entries are in the log, the function will fail with the extended error code 259 indicating no more entries are available.


The CALLLOGENTRY structure is defined as


typedef struct {
DWORD cbSize;
FILETIME ftStartTime;
FILETIME ftEndTime;
IOM iom;
BOOL fOutgoing:1;
BOOL fConnected:1;
BOOL fEnded:1;
BOOL fRoam:1;
CALLERIDTYPE cidt;
PTSTR pszNumber;
PTSTR pszName;
PTSTR pszNameType;
PTSTR pszNote;
} CALLLOGENTRY, * PCALLLOGENTRY;


The ftStartTime and ftEndTime fields are FILETIME structures that provide the start and end times of the call. The iom field contains an enumeration indicating if the call was incoming, was outgoing, or was missed. The next four fields are Booleans detailing the conditions of the call: fOutgoing is TRUE if the call was made from the device; fConnected is set if the call actually made a connection; fEnded is TRUE if the call was terminated by the callers and FALSE if the call was dropped; and fRoam is set if the call was made from outside the phone's home area. The cidt field is an enumeration indicating if the caller ID for the call was available, blocked, or unavailable.


The pszNumber field points to a string indicating the number of the phone number of the calling phone or the phone being called. The pszName field identifies the name associated with the number. The pszNameType field points to a string that indicates which number—home, work, or mobile—was associated with the contact. The string is in the form of a character, typically either h, w, or m for home, work, or mobile, respectively. The pszNote field is a string that is supposed to point to a string containing the name of a notes file for the call. This field isn't always filled in by the system.


The seek pointer of the call log can be moved with


HRESULT PhoneSeekCallLog (HANDLE h, CALLLOGSEEK seek, DWORD iRecord,
LPDWORD piRecord);


The handle value is the handle returned by PhoneOpenCallLog. The seek value can be set to either CALLLOGSEEK_BEGINNING or CALLLOGSEEK_END, depending on whether the passed offset is based from the beginning or end of the log. The parameter iRecord is the zero-based offset from the beginning or end of the log. The piRecord parameter points to a DWORD that receives the index, from the beginning of the log, of the resulting record. Once the seek pointer is moved to a specific record, the record can then be read with PhoneGetCallLogEntry.


The phone log should be closed with a call to PhoneCloseCallLog, prototyped as


HRESULT PhoneCloseCallLog (HANDLE h);


The single parameter is the handle returned from PhoneOpenCallLog.



The Connection Manager




The connection manager is a centralized location to request a connection to external data networks regardless of the connection method. The connection manager presents both a single point of connection configuration to the user and a single place where applications can go to programmatically connect to the network. The connection manager also frees the application from having to know what connections are the best to use given the different costs and speeds of the various connections. For example, the connection manager knows it's better to access the Internet via ActiveSync if possible instead of connecting via a cellular connection.


Connecting




An application can connect to a network in three ways using the connection manager. An application can request a connection synchronously, request a connection asynchronously, or schedule a time for a connection to be made. Typically, an application will call ConnMgrEstablishConnection to request a connection be made asynchronously and then be notified when the connection is made. The prototype is


HRESULT ConnMgrEstablishConnection (CONNMGR_CONNECTIONINFO * pConnInfo,
HANDLE * phConnection);


The function returns an HRESULT and, if successful, a handle to the connection in the variable pointed to by phConnection. The other parameter of the call is a pointer to a CONNMGR_CONNECTIONNFO structure, defined as


typedef struct _CONNMGR_CONNECTIONINFO {
DWORD cbSize;
DWORD dwParams;
DWORD dwFlags;
DWORD dwPriority;
BOOL bExclusive;
BOOL bDisabled;
GUID guidDestNet;
HWND hWnd;
UINT uMsg;
LPARAM lParam;
ULONG ulMaxCost;
ULONG ulMinRcvBw;
ULONG ulMaxConnLatency;
} CONNMGR_CONNECTIONINFO;


The traditional size field should be set to the size of the structure. The dwParams field indicates which optional fields in the structure are filled with valid data. I'll mention the flags in this field as I discuss the optional fields. The dwFlags field indicates the proxies supported by the application. The supported flags include proxy flags for HTTP, Wireless Application Protocol (WAP), SOCKS4 and SOCKS5. If no flags are specified, only a direct Internet connection is attempted.


The dwPriority field indicates how important the connection is to the application. The priority ranges from CONNMGR_PRIORITY_VOICE for a voice connection, the highest priority, to CONNMGR_PRIORITY_LOWBKGND, which indicates a connection will only be made if another connection is currently active that satisfies the request. The bExclusive field should be set to TRUE if the connection should not be shared among the other applications in the system. If bDisabled is TRUE, ConnMgrEstablishConnection will check the connection to see whether it can be made, but the connection won't actually be made. The guidDestNet field indicates the network to connect to. The GUID of the various network connections can be determined using ConnMgrEnumDestinations discussed later. This field will be used only if the dwParams field contains the CONNMGR_PARAM_DESTNETID flag.


The next three fields, hWnd, wMsg, and lParam, are used to provide feedback to the application about the connection. The connection manager sends message values of wMsg indicating progress of the connection to the window indicated by hWnd. The lParam value is passed in the progress message in the lParam value. The wParam value of the message provides a connection status. The status values are defined in the ConnMgr.h include file. Additional connection status can be queried by calling ConnMgrConnectionStatus. This function, along with the various connection states, is discussed later.


The final three parameters, ulMaxCost, ulMinRcvBw, and ulMaxConnLatency, define additional conditions that the connection manager should use when choosing what path to use when making a connection. These fields are only used if the corresponding flag is set in the dwParams parameter.


To connect synchronously, an application can call ConnMgrEstablishConnectionSync, prototyped as


HRESULT ConnMgrEstablishConnectionSync (CONNMGR_CONNECTIONINFO *pConnInfo,
HANDLE *phConnection,
DWORD dwTimeout, DWORD *pdwStatus);


The parameters are similar to ConnMgrEstablishConnection with the addition of dwTimeout, which defines the time in milliseconds that the function should wait for a connection to be made, and pdwStatus, which points to a DWORD that is filled in with the resulting status of the connection.


The connection manager can also be requested to make a connection at a scheduled time using the function ConnMgrRegisterScheduledConnection. If the device is off when the scheduled time arrives, it will automatically turn on and attempt the connection. ConnMgrRegisterScheduledConnection is defined as


HRESULT ConnMgrRegisterScheduledConnection (SCHEDULEDCONNECTIONINFO *pSCI);


The single parameter is a pointer to a SCHEDULEDCONNECTIONINFO structure, defined as


typedef struct _SCHEDULEDCONNECTIONINFO {
GUID guidDest;
UINT64 uiStartTime;
UINT64 uiEndTime;
UINT64 uiPeriod;
TCHAR szAppName[MAX_PATH];
TCHAR szCmdLine[MAX_PATH];
TCHAR szToken[32];
BOOL bPiggyback;
} SCHEDULEDCONNECTIONINFO;


The guidDest field defines the network to connect to. The szToken field should be set to a string that uniquely identifies this scheduled connection. This string will be used if the connection needs to be canceled.


The uiStartTime and uiEndTime fields define the time that the requests should start and stop, and uiPeriod defines how often the requests should be repeated. These times are defined in the structure as 64-bit numbers that are 100 nanosecond intervals since January 1, 1601. Conveniently, this format is the same as the FILETIME structure. The uiPeriod value can be set to 0 to indicate that the system will never automatically attempt the connection.


When the connection is made, the application name pointed to by szAppName will be launched with the command line specified in szCmdLine. If the bPiggyback field is set to TRUE, the application will be launched whenever a connection is made to the network matching the guidDest field. Setting this field to TRUE and setting uiPeriod to 0 allows an application to monitor any connection to a specific network without scheduling a connection of its own.


The scheduled connection can be canceled with a call to ConnMgrUnregisterScheduledConnection, defined as


HRESULT ConnMgrUnregisterScheduledConnection (LPCWSTR pwszToken);


The single parameter is the 32-character token string passed in the szToken field of SCHEDULEDCONNECTIONINFO when the connection was scheduled.


Setting and Querying Status




Querying the status of a connection can be accomplished with a call to


HRESULT ConnMgrConnectionStatus (HANDLE hConnection, DWORD *pdwStatus);


The parameters are the handle to a current connection handle and a pointer to a DWORD that will receive the current status. The status flags are listed in the following table.






















































Status




Value




Description




Unknown




00




Status unknown.




Connected




0x10




The connection is up.




Disconnected




0x20




The connection is disconnected.




Connection failed




0x21




The connection attempt failed.




Connection canceled




0x22




The user aborted the connection.




Connection disabled




0x23




The connection is ready to connect but is disabled.




No path to destination




0x24




No network path could be found to destination.




Waiting for path




0x25




The connection is waiting for a path to the destination.




Waiting for phone




0x26




A voice call is in progress.




Waiting connection




0x40




Attempting to connect.




Waiting for resource




0x41




Resource is in use by another connection.




Waiting for network




0x42




No path could be found to destination.




Waiting disconnection




0x80




The connection is being brought down.




Waiting connection abort




0x81




The connection attempt is being aborted.




The various connections can be enumerated using the function ConnMgrEnumDestinations, prototyped as


HRESULT ConnMgrEnumDestinations (int nIndex, 
CONNMGR_DESTINATION_INFO *pDestInfo);


To use this function, an application calls ConnMgrEnumDestinations repeatedly, the first time with the nIndex set to 0 and then incrementing nIndex for each call. If the function is successful, data about the connection will be provided in the CONNMGR_DESTINATION_INFO structure pointed to by pDestInfo. The structure is defined as


typedef struct _CONNMGR_DESTINATION_INFO {
GUID guid;
TCHAR szDescription[CONNMGR_MAX_DESC];
} CONNMGR_DESTINATION_INFO;


The structure has two fields, the GUID of the specific network and a string describing the network. The GUID of the network can be used to specify the network when connecting.



SMS Messaging




The Short Message Service, or SMS, is a popular way, as the name implies, to exchange short text messages between cellular phones. By default, the InBox application on both the Smartphone and the Pocket PC Phone edition reads SMS messages. On the Smartphone, the Pocket InBox is also responsible for composing and sending SMS messages. On the Pocket PC, SMS messages can be composed and sent from a menu on the phone application. In addition to these applications, both systems expose a set of functions that allows third-party applications to send and receive SMS messages.


The process of sending and receiving SMS messages involves getting an SMS handle for sending and another for receiving. The message is composed, and the address is defined as the phone number of the receiving phone. Instead of sending the message directly to the phone, however, the message is sent to the SMS Service Center, which forwards the message on to the destination phone.


Receiving a message involves blocking on an event that is signaled when a message has been received. The message can then be read with a call to the SMS system. Because of the blocking nature of the reading process, this task is usually accomplished with a secondary thread.


The SMS system doesn't provide any way of saving the read messages. Instead, the application that is responsible for receiving the messages is responsible for saving the messages in a database if the user wants them saved. On the Pocket PC and Smartphone, Pocket Inbox saves the messages in the e-mail database.


Accessing the SMS System




The task of sending or receiving messages starts with accessing the SMS subsystem using SmsOpen, prototyped as


HRESULT SmsOpen (const LPCTSTR ptsMessageProtocol, 
const DWORD dwMessageModes, SMS_HANDLE* const psmshHandle,
HANDLE* const phMessageAvailableEvent);


The first parameter is a string that describes the type of message that the application is interested in sending or receiving. For sending and receiving basic text messages, the SMS_MSGTYPE_TEXT protocol should be used. The include file Sms.h defines a number of other protocols that can be used for broadcast, status, and a couple of control protocols. The dwMessageModes parameter should be set for either SMS_MODE_RECEIVE or SMS_MODE_SEND depending on if the open is to send or receive messages. The psmshHandle parameter points to an SMS_HANDLE value that will receive the SMS handle if the function is successful.


The final parameter, phMessageAvailableEvent, points to an event handle but is used only when opening to receive messages. When asking for a handle to send SMS messages, the parameter should be NULL. When asking for a handle to receive messages, this parameter must point to a standard Windows CE event handle that was previously created by the application. This event handle should be an auto-reset event, not initially signaled. The event will be signaled when a message has been received by the system. The application should not set the event or close the event handle. The event will be closed when the application calls SmsClose.


The return value of SmsOpen will be ERROR_SUCCESS if the call was successful. Otherwise, an error code will be returned.


Sending a Message




Sending a message is accomplished by calling the rather involved SmsSendMessage function, prototyped as


HRESULT SmsSendMessage (const SMS_HANDLE smshHandle, 
const SMS_ADDRESS * const psmsaSMSCAddress,
const SMS_ADDRESS * const psmsaDestinationAddress,
const SYSTEMTIME * const pstValidityPeriod,
const BYTE * const pbData, const DWORD dwDataSize,
const BYTE * const pbProviderSpecificData,
const DWORD dwProviderSpecificDataSize,
const SMS_DATA_ENCODING smsdeDataEncoding,
const DWORD dwOptions,
SMS_MESSAGE_ID * psmsmidMessageID);


The first parameter is an SMS handle that was opened for sending a message. The psmsaSMSCAddress parameter points to an SMS_ADDRESS structure that contains the phone number of the SMS service center. In most cases, this parameter can be NULL to indicate that the system should use the default SMS center address. The psmsaDestinationAddress parameter points to another SMS_ADDRESS structure that contains the destination address of the message. I'll discuss the format of the SMS_ADDRESS structure in a moment.


The fourth parameter of SmsSendMessage, pstValidityPeriod, sets the length of time the message can remain undelivered at the server before it is deleted. Contrary to the parameter's SYSTEMTIME type, the format of this field is not a SYSTEMTIME but a data type defined by the SMS specification. This parameter can be NULL.


The next two fields are the pointer to the message text and the length in bytes of the text. The maximum length of a single message is 140 bytes or 160 7-bit characters. The format of the data is defined in the smsdeDataEncoding parameter discussed later.


The pbProviderSpecificData parameter points to optional provider-specific data. The provider-specific data is a structure specialized to the message format being used when sending the data. The format of the TEXT_PROVIDER_SPECIFIC_DATA structure, used when sending standard text messages, is described later. The dwProviderSpecificDataSize parameter should be set to the size of the structure pointed to by pbProviderSpecificData. See the discussion of the TEXT_PROVIDER_SPECIFIC_DATA structure for special handling of this parameter.


The smsdeDataEncoding parameter describes how the message data is encoded. For most messages, the parameter should be set to SMSDE_OPTIMAL to tell the SMS code to define the optimal encoding. The other values are SMSDE_GSM to use the 7-bit GSM encoding and SMSDE_UCS2 to specify a Unicode UCS2 encoding. The dwOptions parameter specifies how the service center will handle retries. If the parameter is set to SMS_OPTION_DELIVERY_NONE, the service center will retry sending the message. If SMS_OPTION_DELIVERY_NO_RETRY is specified, the message won't retry the delivery.


The final parameter points to a DWORD that will receive a message ID value. The message ID can be used to get status of the message using the function SmsGetMessageStatus. This parameter can be NULL if the message ID isn't needed.


The return value of SmsSendMessage is a standard HRESULT value, with S_OK (0) indicating success. The function can take a short but noticeable amount of time to complete, so it's best to display a wait cursor or use some other method to prevent the user from thinking the system is momentarily locked up.


The structures used by SmsSendMessage included the SMS_ADDRESS and TEXT_PROVIDER_SPECIFIC_DATA structures. The SMS_ADDRESS structure is defined as


typedef struct sms_address_tag {
SMS_ADDRESS_TYPE smsatAddressType;
TCHAR ptsAddress[SMS_MAX_ADDRESS_LENGTH];
} SMS_ADDRESS, *LPSMS_ADDRESS;


The first field of the structure is the address type. For most uses, this field can be set to SMSAT_INTERNATIONAL. The second field is the address; for SMSAT_INTERNATIONAL, the address is in the form of a phone number complete with country code and area code, as in +12225551212.


When sending standard text messages, the provider-specific data structure used is TEXT_PROVIDER_SPECIFIC_DATA, shown here. Other structures can be used when sending other types of message, but for brevity I'll describe only this structure.


typedef struct text_provider_specific_data_tag {
DWORD dwMessageOptions;
PROVIDER_SPECIFIC_MESSAGE_CLASS psMessageClass;
PROVIDER_SPECIFIC_REPLACE_OPTION psReplaceOption;
DWORD dwHeaderDataSize;
BYTE pbHeaderData[SMS_DATAGRAM_SIZE];
BOOL fMessageContainsEMSHeaders;
DWORD dwProtocolID;
} TEXT_PROVIDER_SPECIFIC_DATA;


This structure definition is somewhat misleading because only the first three fields are used when sending a standard text message. The additional fields are used only for message concatenation. The first field is the message options field that can request that various message bits be set, such as reply path, discard, or status. The message class value ranges from PS_MESSAGE_CLASS0 through PS_MESSAGE_CLASS3 and PS_MESSAGE_CLASSUNSPECIFIED. The message class indicates how the service center handles the message. For text messages, PS_MESSAGE_CLASS0 is used. The psReplaceOption field contains PSRO_NONE for standard messages. The field can be set to PSRO_REPLACE_TYPEn, where n is a value from 1 through 7. If a replace type field is set, the message will replace a message at the destination with the same parameters and the same replace type value.


A function of this complexity deserves an example. The following code calls SmsOpen, fills in the proper structures, and then sends the message. The SMS handle is then closed with SmsClose.


SMS_HANDLE smshHandle;
SMS_ADDRESS smsaDestination;
TEXT_PROVIDER_SPECIFIC_DATA tpsd;
SMS_MESSAGE_ID smsmidMessageID = 0;
// try to open an SMS Handle
HRESULT hr = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, &smshHandle, NULL);
if (hr != ERROR_SUCCESS) {
printf ("SmsOpen fail %x %d", hr, GetLastError());
return 0;
}
// Create the destination address
memset (&smsaDestination, 0, sizeof (smsaDestination));
smsaDestination.smsatAddressType = SMSAT_INTERNATIONAL;
lstrcpy(smsaDestination.ptsAddress, TEXT("+18005551212"));
// Set up provider specific data
tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
tpsd.psMessageClass = PS_MESSAGE_CLASS0;
tpsd.psReplaceOption = PSRO_NONE;
char szMessage[] = "Watson! Come here, I need you!";
// Send the message, indicating success or failure
hr = SmsSendMessage (smshHandle, NULL, &smsaDestination, NULL,
(PBYTE) szMessage, strlen(szMessage)+1,
(PBYTE) &tpsd, 12, SMSDE_OPTIMAL,
SMS_OPTION_DELIVERY_NONE, &smsmidMessageID);
if (hr == ERROR_SUCCESS)
printf ("Message sent");
SmsClose (smshHandle);


SmsOpen is called with SMS_MODE_SEND to open a handle for sending. Notice that the final parameter is NULL because there isn't a need for the read event handle. The destination address is then filled in with a 10-digit phone number. The provider-specific data is constructed with no options and message class 0. The message is then constructed, and the call to SmsSendMessage is made. Notice that the size of the provider-specific data is not set to sizeof (TEXT_PROVIDER_SPECIFIC_DATA) because this is a simple, standalone message and the last few fields of TEXT_PROVIDER_SPECIFIC_DATA aren't used. After the message is sent, the handle is closed with SmsClose, which has the handle as the single parameter.


Receiving a Message




Receiving a message is accomplished with the function SmsReadMessage. When opening an SMS handle for reading, an event handle must be passed as the last parameter. The SMS system will use this handle to signal the application when a message has been received.


When the event is signaled, the application can get an idea of the size of the incoming message by calling SmsGetMessageSize, prototyped as


HRESULT SmsGetMessageSize (const SMS_HANDLE smshHandle,
DWORD * const pdwDataSize);


The two parameters are the SMS handle that was opened previously and a pointer to a DWORD that will receive the message size. The size received is not necessarily the exact size of the message. Instead, it is an upper bound that can be used to allocate the buffer that receives the message.


With a buffer allocated, the message can be read using SmsReadMessage, prototyped as


HRESULT SmsReadMessage (const SMS_HANDLE smshHandle,
SMS_ADDRESS * const psmsaSMSCAddress,
SMS_ADDRESS * const psmsaSourceAddress,
SYSTEMTIME * const pstReceiveTime,
BYTE * const pbBuffer, DWORD dwBufferSize,
BYTE * const pbProviderSpecificBuffer,
DWORD dwProviderSpecificDataBuffer,
DWORD* pdwBytesRead);


The first parameter is an SMS handle that was opened in receive mode. The second parameter is an optional SMS_ADDRESS structure that can receive the number of the SMS service center that sent the message. If the message center address is of no interest, this parameter can be set to NULL. The third parameter points to an SMS_ADDRESS structure that will be filled in with the address of the message received. The pstReceiveTime parameter points to a SYSTEMTIME structure that will receive the UTC-based time of the message. This parameter can be NULL if the time isn't required. The next two parameters, pbBuffer and dwBufferSize, are the pointer to the buffer to receive the data and the size of the buffer. The pbProviderSpecificBuffer parameter points to a buffer that will receive the provider-specific data that accompanies the message, and dwProviderSpecificDataBuffer contains the size of the buffer. The final parameter points to a DWORD that will receive the size of the message received.


SmsReadMessage will fail if there is no message to be read, so the application must block on the event used when SmsOpen was called and only read the message when the event is signaled. The blocking nature of the process means that SmsReadMessage, or at least the wait on the event object, should be done in a secondary thread. The following code is a separate thread that creates an event, opens an SMS handle, blocks on the event, and if signaled reads the message.


DWORD ThreadRead (PVOID pArg) {
SMS_ADDRESS smsaDestination;
TEXT_PROVIDER_SPECIFIC_DATA tpsd;
SMS_HANDLE smshHandle;
HANDLE hRead = CreateEvent (NULL, FALSE, FALSE, NULL);
// Open an SMS Handle
HRESULT hr = SmsOpen (SMS_MSGTYPE_TEXT, SMS_MODE_RECEIVE,
&smshHandle, &hRead);
if (hr != ERROR_SUCCESS) {
printf ("SmsOpen fail %x %d\r\n", hr, GetLastError());
return 0;
}
// Wait for message to come in.
int rc = WaitForSingleObject (hRead, 30000);
if (rc != WAIT_OBJECT_0) {
printf ("WaitForSingleObject %d\r\n", rc);
SmsClose (smshHandle);
return 0;
}
memset (&smsaDestination, 0, sizeof (smsaDestination));
DWORD dwSize, dwRead = 0;
hr = SmsGetMessageSize (smshHandle, &dwSize);
if (hr != ERROR_SUCCESS) {
dwSize = 1024;
return 0;
}
char *pMessage = (char *)malloc (dwSize+1);
memset (&tpsd, 0, sizeof (tpsd));
hr = SmsReadMessage (smshHandle, NULL, &smsaDestination, NULL,
(PBYTE)pMessage, dwSize,
(PBYTE)&tpsd, sizeof(TEXT_PROVIDER_SPECIFIC_DATA),
&dwRead);
if (hr == ERROR_SUCCESS) {
printf ("Dst Address >%s<\r\n", smsaDestination.ptsAddress);
printf ("Msg: >%s<", pMessage);
} else
printf ("Failed %x LastErr:%d\r\n", hr, GetLastError());
free (pMessage);
SmsClose (smshHandle);
printf ("ThreadExit");
return 0;
}


This code could be better written to check the length of the received data and to insure that the message is zero terminated.


Configuring the SMS System




There are a number of functions in the SMS API that are provided for querying the state and managing the SMS system. The SMS phone number for the device can be queried with a call to SmsGetPhoneNumber, defined as


HRESULT SmsGetPhoneNumber (SMS_ADDRESS* const psmsaAddress);


The only parameter is an SMS_ADDRESS structure that is filled in with the phone number of the device.


The status of a sent message can be queried with SmsQueryMessageStatus, prototyped as


HRESULT SmsGetMessageStatus (const SMS_HANDLE smshHandle,
SMS_MESSAGE_ID smsmidMessageID,
SMS_STATUS_INFORMATION * const psmssiStatusInformation,
const DWORD dwTimeout);


The first two parameters are the SMS handle and the message ID that was returned by SmsSendMessage. The dwTimeout value is the time, in milliseconds, that the function should wait for status information from the SMS service center. If the function returns successfully, the SMS_STATUS_INFORMATION structure is filled with status information about the message. The structure is defined as


typedef struct sms_status_information_tag {
SMS_MESSAGE_ID smsmidMessageID;
DWORD dwMessageStatus0;
DWORD dwMessageStatus1;
SMS_ADDRESS smsaRecipientAddress;
SYSTEMTIME stServiceCenterTimeStamp;
SYSTEMTIME stDischargeTime;
} SMS_STATUS_INFORMATION, *LPSMS_STATUS_INFORMATION;


The first field is the ID of the message. The next two fields contain status flags that define the state of the message. There are two fields because there are more than 32 status flags defined. The SMS_ADDRESS field is filled with the destination address of the message. The stServiceCenterTimeStamp field contains the time the message was received by the service center. The stDischargeTime field is a time that depends on the status flags returned in the two dwMessageStatus fields.


The SMS service center number can be queried and set with the functions SmsGetSMSC and SmsSetSMSC, prototyped as


HRESULT SmsGetSMSC (SMS_ADDRESS* const psmsaSMSCAddress);


and


HRESULT SmsSetSMSC (const SMS_ADDRESS * const psmsaSMSCAddress);


Both functions take a single parameter, a pointer to an SMS_ADDRESS structure. Typically, the telephony provider preconfigures this service center number in the phone.


The current time can be estimated with a call to SmsGetTime, prototyped as


HRESULT SmsGetTime (SYSTEMTIME * const ptsCurrentTime,
DWORD * const pdwErrorMargin);


The time returned is based on the time received by the SMS service center the last time the system received a timestamp. The time is a UTC number, so it needs to be corrected for the local time zone. The pdwErrorMargin parameter should point to a DWORD that receives an estimated error margin, in seconds, for the time. If an error margin can't be determined, the error margin will be set to 0xFFFFFFFF.


An application can ask to be started when a message is received by calling the function SmsSetMessageNotification, prototyped as


HRESULT SmsSetMessageNotification (const SMSREGISTRATIONDATA * psmsrd);


The single parameter is a pointer to an SMSREGISTRATIONDATA structure, defined as


typedef struct smsregistrationdata_tag {
DWORD cbSize;
TCHAR tszAppName[SMS_MAX_APPNAME_LENGTH];
TCHAR tszParams[SMS_MAX_PARAMS_LENGTH];
TCHAR tszProtocolName[SMS_MAX_PROTOCOLNAME_LENGTH];
} SMSREGISTRATIONDATA, *LPSMSREGISTRATIONDATA;


The cbSize field should be set with the size of the structure before calling the function. The tszAppName and tszParams fields specify the application name and command line for the application when it is launched. The tszProtocolName field should be set to the message protocol for the messages the application wants to receive. For example, if the application wants to receive standard text messages, the field should be set to SMS_MSGTYPE_TEXT.


When the application no longer wants to be notified when messages are received, it can call SmsClearMessageNotification, prototyped as


HRESULT SmsClearMessageNotification (const LPCTSTR tszProtocolName);


The single parameter is the message protocol that was specified when SmsSetMessageNotification was called.



The SMSTalk Example




The following example uses a number of the techniques discussed in this chapter to create an application that sends and receives SMS messages. SMSTalk is a dialog-based, multithreaded application that monitors the SMS read queue as well as provides a method for the user to compose and send SMS messages.


The example is designed to run both on the Smartphone and the Pocket PC phone edition. The example is designed for binary compatibility, as opposed to source code compatibility. Notice that SMSTalk checks for the Smartphone and makes the necessary changes to the user interface at run time. Figure 19-9 shows the SMSTalk main dialog on both a Smartphone and a Pocket PC phone edition device.




Figure 19-9: The SMSTalk application running on both a Smartphone and a Pocket PC


The dialogs look different because the application uses different dialog box templates depending on the device the application is running on. The source code, shown in Listing 19-2, has a rather long resource file because all the dialogs must be described twice, once for each device. The resource file also contains different menu bar templates for the two devices.


Listing 19-2: The SMSTalk source code








SMSTalk.rc
//======================================================================
// Resource file
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include "windows.h"
#include "aygshell.h"
#include "SMSTalk.h" // Program-specific stuff
//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON "SMSTalk.ico" // Program icon
//----------------------------------------------------------------------
// Main window dialog template for Pocket PC
//
SMSTalk_PPC DIALOG discardable 25, 5, 120, 98
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | DS_MODALFRAME
CAPTION "SMS Talk"
FONT 8, "System"
BEGIN
LTEXT "Received Messages", -1, 4, 4, 128, 10,
LISTBOX IDD_MSGLIST, 4, 14, 128, 48, WS_TABSTOP
LTEXT "Message", -1, 4, 62, 32, 10,
EDITTEXT IDD_MSGADDR, 36, 60, 96, 12, WS_TABSTOP |
ES_NUMBER
EDITTEXT IDD_MSGTEXT, 4, 74, 128, 60, WS_TABSTOP |
ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL
PUSHBUTTON "&New", ID_CMDNEW, 4, 137, 40, 12, WS_TABSTOP
PUSHBUTTON "&Reply", ID_CMDREPLY, 48, 137, 40, 12, WS_TABSTOP
END
//----------------------------------------------------------------------
// Main window dialog template for Smartphone
//
SMSTalk_SP DIALOG discardable 25, 5, 120, 98
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU |
DS_CENTER | DS_MODALFRAME
CAPTION "SMS Talk"
BEGIN
LTEXT "Unread messages", -1, 2, 2, 96, 8,
LISTBOX IDD_MSGLIST, 2, 11, 96, 12, WS_TABSTOP
CONTROL ", IDD_MSGLISTUD, UPDOWN_CLASS,
UDS_AUTOBUDDY | UDS_HORZ | UDS_ALIGNRIGHT | UDS_ARROWKEYS |
UDS_SETBUDDYINT | UDS_WRAP | UDS_EXPANDABLE,
0, 0, 0, 0
LTEXT "Number", -1, 2, 24, 34, 8,
EDITTEXT IDD_MSGADDR, 36, 23, 62, 10, ES_READONLY
LTEXT "Message Text", -1, 2, 34, 96, 8,
EDITTEXT IDD_MSGTEXT, 2, 43, 96, 40, WS_TABSTOP |
ES_MULTILINE | ES_READONLY
CONTROL ", IDD_MSGTEXTUD, UPDOWN_CLASS, UDS_AUTOBUDDY |
UDS_HORZ | UDS_ARROWKEYS | UDS_SETBUDDYINT |
UDS_WRAP | UDS_EXPANDABLE | UDS_NOSCROLL,
0, 0, 0, 0
END
//----------------------------------------------------------------------
// Compose window dialog template for Pocket PC
//
WriteMsgDlg_PPC DIALOG discardable 25, 5, 120, 98
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | DS_MODALFRAME
CAPTION "Compose Message"
BEGIN
LTEXT "Number", -1, 4, 6, 32, 10,
EDITTEXT IDD_MSGADDR, 36, 4, 96, 12, WS_TABSTOP |
ES_NUMBER
LTEXT "Message Text", -1, 4, 20, 128, 10,
EDITTEXT IDD_MSGTEXT, 4, 30, 128, 54, WS_TABSTOP |
ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL
PUSHBUTTON "&Send", ID_CMDSEND, 4, 90, 40, 12, WS_TABSTOP
PUSHBUTTON "&Cancel", IDCANCEL, 48, 90, 40, 12, WS_TABSTOP
END
//----------------------------------------------------------------------
// Compose window dialog template for Smartphone
//
WriteMsgDlg_SP DIALOG discardable 25, 5, 120, 98
STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU |
DS_CENTER | DS_MODALFRAME
CAPTION "SMS Talk"
BEGIN
LTEXT "Number", -1, 2, 2, 96, 8,
EDITTEXT IDD_MSGADDR, 2, 11, 96, 10, WS_TABSTOP
LTEXT "Message Text", -1, 2, 24, 96, 8,
EDITTEXT IDD_MSGTEXT, 2, 33, 96, 50, WS_TABSTOP |
ES_MULTILINE
CONTROL ", IDD_MSGTEXTUD, UPDOWN_CLASS, UDS_AUTOBUDDY |
UDS_HORZ | UDS_ARROWKEYS | UDS_SETBUDDYINT |
UDS_WRAP | UDS_EXPANDABLE | UDS_NOSCROLL,
0, 0, 0, 0
END
//----------------------------------------------------------------------
// String resource table
//
STRINGTABLE DISCARDABLE
BEGIN
IDS_EXIT "Exit"
IDS_MENU "Menu"
IDS_MSG "Message"
IDS_FILE "File"
IDS_OK "OK"
IDS_CANCEL "Cancel"
IDS_SEND "Send"
END
//----------------------------------------------------------------------
// SoftKeyBar resource on main window for Smartphone
//
ID_MENU_SP RCDATA MOVEABLE PURE
BEGIN
ID_MENU_SP, 2,
I_IMAGENONE, IDOK, TBSTATE_ENABLED, TBSTYLE_BUTTON |
TBSTYLE_AUTOSIZE, IDS_EXIT, 0, NOMENU,
I_IMAGENONE, IDPOP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN |
TBSTYLE_AUTOSIZE, IDS_MENU, 0, 0,
END
ID_MENU_SP MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "Reply", ID_CMDREPLY
MENUITEM "New Message" ID_CMDNEW
MENUITEM SEPARATOR
MENUITEM "Delete", ID_CMDDEL
END
END
//----------------------------------------------------------------------
// SoftKeyBar resource on Compose dialog for Smartphone
//
ID_DLGMENU_SP RCDATA MOVEABLE PURE
BEGIN
ID_MENU_SP, 2,
I_IMAGENONE, ID_CMDSEND, TBSTATE_ENABLED, TBSTYLE_BUTTON |
TBSTYLE_AUTOSIZE, IDS_SEND, 0, NOMENU,
I_IMAGENONE, IDCANCEL, TBSTATE_ENABLED, TBSTYLE_BUTTON |
TBSTYLE_AUTOSIZE, IDS_CANCEL, 0, NOMENU,
END
//----------------------------------------------------------------------
// Menu bar resource main window for Pocket PC
//
ID_MENU_PPC RCDATA MOVEABLE PURE
BEGIN
ID_MENU_PPC, 2,
I_IMAGENONE, IDFILE, TBSTATE_ENABLED, TBSTYLE_DROPDOWN |
TBSTYLE_AUTOSIZE,IDS_FILE,0,0,
I_IMAGENONE, IDPOP, TBSTATE_ENABLED, TBSTYLE_DROPDOWN |
TBSTYLE_AUTOSIZE,IDS_MSG,0,1
END
ID_MENU_PPC MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&About", IDM_ABOUT
MENUITEM "E&xit", IDOK
END
POPUP "&Help"
BEGIN
MENUITEM "&Delete", ID_CMDDEL
MENUITEM SEPARATOR
MENUITEM "&Reply", ID_CMDREPLY
MENUITEM "New Message" ID_CMDNEW
END
END


SMSTalk.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.
BOOL (*Fxn)(HWND, UINT, WPARAM, LPARAM);
};
struct decodeCMD { // Structure associates
UINT Code; // menu IDs with a
LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function.
};

//----------------------------------------------------------------------
// Program defines used by application
//
typedef struct {
SMS_ADDRESS smsAddr;
SYSTEMTIME stMsg;
int nSize;
WCHAR wcMessage[160];
} MYMSG_STRUCT, *PMYMSG_STRUCT;
#define MAX_MSGS 250
typedef struct {
int nMsgCnt;
MYMSG_STRUCT pMsgs[MAX_MSGS];
} MYMSG_DBASE, *PMYMSG_DBASE;
//----------------------------------------------------------------------
// Generic defines used by application
#define MYMSG_TELLNOTIFY (WM_USER + 100)
#define ID_ICON 1
#define ID_MENU_SP 100
#define ID_MENU_PPC 101
#define ID_DLGMENU_SP 102
#define IDD_MSGLIST 110 // Control IDs
#define IDD_MSGLISTUD 111
#define IDD_MSGTEXT 112
#define IDD_MSGTEXTUD 113
#define IDD_MSGADDR 114
#define IDM_EXIT 200
#define ID_CMDSEND 201
#define ID_CMDNEW 202
#define ID_CMDREPLY 203
#define ID_CMDDEL 204
#define ID_CMDREAD 205
#define IDM_ABOUT 206
#define IDFILE 207
#define IDPOP 208
#define IDS_EXIT 401
#define IDS_MENU 402
#define IDS_MSG 403
#define IDS_FILE 404
#define IDS_OK 405
#define IDS_CANCEL 406
#define IDS_SEND 407
//----------------------------------------------------------------------
// Function prototypes
//
void ErrorBox (HWND hWnd, LPCTSTR lpszFormat, ...);
BOOL OnSmartPhone(void);
int RefreshMessageList (HWND hWnd, int nSel);
int SetButtons (HWND hWnd);
// Window procedures
BOOL CALLBACK MainDlgProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK WriteDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK AboutDlgProc(HWND, UINT, WPARAM, LPARAM);
// Message handlers
BOOL DoCreateDialogMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoInitDialogMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoCommandMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoHotKeyMain (HWND, UINT, WPARAM, LPARAM);
BOOL DoTellNotifyMain (HWND, UINT, WPARAM, LPARAM);
// Command functions
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandDelMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandReplyMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandNewMessage (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandMsgList (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandAbout (HWND, WORD, HWND, WORD);
// Thread prototype
DWORD WINAPI MonitorThread (PVOID pArg);


SMSTalk.cpp
//======================================================================
// SMSTalk - Demonstrates SMS messaging system
//
// Written for the book Programming Windows CE
// Copyright (C) 2003 Douglas Boling
//======================================================================
#include <windows.h> // For all that Windows stuff
#include <aygshell.h> // Extended shell defines
#include <tpcshell.h>
#include <sms.h> // SMS functions
#include "SMSTalk.h" // Program-specific stuff
#define MY_MSGWAITING_STRING TEXT("SMSMsgReadEvent")
#define EMPTY_MSG_LIST TEXT("<No new messages>")
#define MAXMESSAGELEN 4096
//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("SMSTalk");
const TCHAR szOtherApp[] = TEXT("Another application already has the SMS system open.
\n\nPlease close the (email?) application");
HINSTANCE hInst; // Program instance handle
HWND g_hMain = 0;
HANDLE g_hReadEvent = 0;
HANDLE g_hQuitEvent = 0;
BOOL g_fContinue = TRUE;
BOOL g_fOnSPhone = FALSE;
PMYMSG_DBASE g_pMsgDB = 0;
// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
WM_CREATE, DoCreateDialogMain,
WM_INITDIALOG, DoInitDialogMain,
WM_COMMAND, DoCommandMain,
WM_HOTKEY, DoHotKeyMain,
MYMSG_TELLNOTIFY, DoTellNotifyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
IDD_MSGLIST, DoMainCommandMsgList,
IDOK, DoMainCommandExit,
IDCANCEL, DoMainCommandExit,
ID_CMDREPLY, DoMainCommandReplyMessage,
ID_CMDNEW, DoMainCommandNewMessage,
ID_CMDDEL, DoMainCommandDelMessage,
IDM_ABOUT, DoMainCommandAbout,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow) {
INT i;
HWND hWnd;
HANDLE hThread;
TCHAR szDlgTemplate[32];
SMS_HANDLE smshHandle;
hInst = hInstance;
// Look to see if another instance of the app is running.
hWnd = FindWindow (NULL, szAppName);
// See if we were launched with a command line
if (*lpCmdLine) {
// Check to see if app started due to notification.
if (lstrcmp (lpCmdLine, MY_MSGWAITING_STRING) == 0) {
if (hWnd) {
SendMessage (hWnd, MYMSG_TELLNOTIFY, 0, 0);
}
}
}
// Set first instance to the foreground and exit
if (hWnd) {
SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
return 0;
}
// See if we're running on a smartphone.
g_fOnSPhone = OnSmartPhone ();
// Allocate message array
g_pMsgDB = (PMYMSG_DBASE)LocalAlloc (LPTR, sizeof (MYMSG_DBASE));
if (g_pMsgDB == 0) {
ErrorBox (NULL, TEXT("Out of memory"));
return -1;
}
g_pMsgDB->nMsgCnt = 0;
// Create secondary thread for timer event notification.
// then try to open an SMS Handle for reading messages.
g_hQuitEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
g_hReadEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
HRESULT hr = SmsOpen (SMS_MSGTYPE_TEXT, SMS_MODE_RECEIVE,
&smshHandle, &g_hReadEvent);
if (hr == SMS_E_RECEIVEHANDLEALREADYOPEN) {
ErrorBox (hWnd, (LPCTSTR)szOtherApp);
return 0;
} else if (hr != ERROR_SUCCESS) {
ErrorBox (hWnd, TEXT("SmsOpen fail %x %d"), hr, GetLastError());
return 0;
}
hThread = CreateThread (NULL, 0, MonitorThread, (PVOID)smshHandle,
0, (DWORD *)&i);
if (hThread == 0)
return -1;
// Display dialog box as main window. Use different template if
// running on the smartphone
if (g_fOnSPhone)
_tcscpy (szDlgTemplate, TEXT("SMSTalk_SP"));
else
_tcscpy (szDlgTemplate, TEXT("SMSTalk_PPC"));
DialogBoxParam (hInstance, szDlgTemplate, NULL, MainDlgProc,
(LPARAM)lpCmdLine);
// Signal notification thread to terminate
g_fContinue = FALSE;
SetEvent (g_hQuitEvent);
WaitForSingleObject (hThread, 1000);
CloseHandle (hThread);
CloseHandle (g_hQuitEvent); // Don't close ReadEvent, SMS does that
if (g_pMsgDB) LocalFree (g_pMsgDB);
return 0;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainDlgProc - Callback function for application window
//
BOOL CALLBACK MainDlgProc (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 FALSE;
}
//----------------------------------------------------------------------
// DoCreateDialogMain - Process WM_CREATE message for window.
//
BOOL DoCreateDialogMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {

if (!g_fOnSPhone) {
// set up Menu bar for Pocket PC
SHMENUBARINFO mbi;
memset (&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
mbi.nToolBarId = ID_MENU_PPC; // IDM_MENU;
mbi.hInstRes = hInst;
// If we could not initialize the dialog box, return an error
if (!SHCreateMenuBar(&mbi)) {
ErrorBox (hWnd, TEXT("Menubar failed"));
DestroyWindow (hWnd);
return FALSE;
}
}
return TRUE;
}
//----------------------------------------------------------------------
// DoInitDialogMain - Process WM_INITDIALOG message for window.
//
BOOL DoInitDialogMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
// Save the window handle
g_hMain = hWnd;
// Specify that the dialog box should stretch full screen
SHINITDLGINFO shidi;
memset (&shidi, 0, sizeof(shidi));
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_SIZEDLG ;//SHIDIF_SIZEDLGFULLSCREEN
shidi.hDlg = hWnd;
if(!SHInitDialog(&shidi))
return FALSE;
// Create menubar
SHMENUBARINFO mbi;
memset (&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
if (g_fOnSPhone)
mbi.nToolBarId = ID_MENU_SP;
else
mbi.nToolBarId = ID_MENU_PPC;
mbi.hInstRes = hInst;

// If we could not initialize the dialog box, return an error
if (!SHCreateMenuBar(&mbi)) {
ErrorBox (hWnd, TEXT("Menubar failed"));
DestroyWindow (hWnd);
return FALSE;
}
// This is only needed on the smartphone
if (g_fOnSPhone) {
// Override back key since we have an edit control
SendMessage (SHFindMenuBar (hWnd), SHCMBM_OVERRIDEKEY, VK_TBACK,
MAKELPARAM (SHMBOF_NODEFAULT | SHMBOF_NOTIFY,
SHMBOF_NODEFAULT | SHMBOF_NOTIFY));
}
// set the title bar
SHSetNavBarText (hWnd, TEXT("SMS Talk"));
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0,
(LPARAM)EMPTY_MSG_LIST);
SetButtons (hWnd);
return TRUE;
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
BOOL 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) {
(*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl,
wNotifyCode);
return TRUE;
}
}
return FALSE;
}
//----------------------------------------------------------------------
// DoHotKeyMain - Process WM_HOTKEY message for window.
//
BOOL DoHotKeyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
SHSendBackToFocusWindow (wMsg, wParam, lParam);
return 0;
}
//----------------------------------------------------------------------
// DoTellNotifyMain - Process MYMSG_TELLNOTIFY message for window.
//
BOOL DoTellNotifyMain (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
RefreshMessageList (hWnd, lParam);
SetButtons (hWnd);
return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command.
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
EndDialog (hWnd, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandDelMessage - Process Read message button.
//
LPARAM DoMainCommandDelMessage (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int i, nSel;
nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL,0,0);
if (nSel != LB_ERR) {
for (i = nSel; i < g_pMsgDB->nMsgCnt-1; i++)
g_pMsgDB->pMsgs[i] = g_pMsgDB->pMsgs[i+1];
g_pMsgDB->nMsgCnt--;
RefreshMessageList (hWnd, -1);
}
SetButtons (hWnd);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandReplyMessage - Process Reply message button.
//
LPARAM DoMainCommandReplyMessage (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
int nSel;
LPCTSTR lpTemplate;
LPARAM lp = 0;
nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL,0,0);
if (nSel != LB_ERR)
lp = (LPARAM)&g_pMsgDB->pMsgs[nSel].smsAddr;
// Display reply dialog box.
if (g_fOnSPhone)
lpTemplate = TEXT("WriteMsgDlg_SP");
else
lpTemplate = TEXT("WriteMsgDlg_PPC");
DialogBoxParam (hInst, lpTemplate, NULL, WriteDlgProc, lp);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandNewMessage - Process New message button.
//
LPARAM DoMainCommandNewMessage (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
LPCTSTR lpTemplate;
// Display reply dialog box.
if (g_fOnSPhone)
lpTemplate = TEXT("WriteMsgDlg_SP");
else
lpTemplate = TEXT("WriteMsgDlg_PPC");
DialogBoxParam (hInst, lpTemplate, NULL, WriteDlgProc, 0);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandAbout - Process About menu item.
//
LPARAM DoMainCommandAbout (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
TCHAR szAbout[] = TEXT("SMS Talk\nCopyright 2003\nDouglas Boling");
// Display about information in a message box.
MessageBox (hWnd, szAbout, TEXT("About"), MB_OK | MB_ICONASTERISK);
return 0;
}
//----------------------------------------------------------------------
// DoMainCommandMsgList - Process message list listbox.
//
LPARAM DoMainCommandMsgList (HWND hWnd, WORD idItem, HWND hwndCtl,
WORD wNotifyCode) {
if (wNotifyCode == LBN_SELCHANGE)
SetButtons (hWnd);
return 0;
}
//----------------------------------------------------------------------
// RefreshMessageList - Fill in message listbox from message array
//
int RefreshMessageList (HWND hWnd, int nSel) {
TCHAR szStr[256];
int i;
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_RESETCONTENT, 0, 0);
for (i = 0; i < g_pMsgDB->nMsgCnt; i++) {
wsprintf (szStr, TEXT("%d/%02d/%02d %d:%02d:%02d %s"),
g_pMsgDB->pMsgs[i].stMsg.wMonth,
g_pMsgDB->pMsgs[i].stMsg.wDay,
g_pMsgDB->pMsgs[i].stMsg.wYear%100,
g_pMsgDB->pMsgs[i].stMsg.wHour,
g_pMsgDB->pMsgs[i].stMsg.wMinute,
g_pMsgDB->pMsgs[i].stMsg.wSecond,
g_pMsgDB->pMsgs[i].wcMessage);
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0,
(LPARAM)szStr);
}
if (g_pMsgDB->nMsgCnt == 0)
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_ADDSTRING, 0,
(LPARAM)EMPTY_MSG_LIST);
else {
if (nSel != -1)
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_SETCURSEL,0,nSel);
else
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_SETCURSEL, 0, i-1);
}
return 0;
}
//----------------------------------------------------------------------
// SetButtons - Utility function to compute enabled state of btns
//
int SetButtons (HWND hWnd) {
int nSel;
BOOL fReply = FALSE;
TCHAR szText[128];
LPTSTR pMsg = TEXT("), pNum = TEXT(");
nSel = SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETCURSEL, 0, 0);
if (nSel != LB_ERR) {
SendDlgItemMessage (hWnd, IDD_MSGLIST, LB_GETTEXT, nSel,
(LPARAM)szText);
if (_tcscmp (szText, EMPTY_MSG_LIST)) {
fReply = TRUE;
pNum = g_pMsgDB->pMsgs[nSel].smsAddr.ptsAddress;
pMsg = g_pMsgDB->pMsgs[nSel].wcMessage;
}
}
EnableWindow (GetDlgItem (hWnd, ID_CMDREPLY), fReply);
// Set the text in the number and message fields
SetWindowText (GetDlgItem (hWnd, IDD_MSGADDR), pNum);
SetWindowText (GetDlgItem (hWnd, IDD_MSGTEXT), pMsg);
// Disable the menu bar button if necessary
TBBUTTONINFO tbi;
HWND hwndMB = SHFindMenuBar (hWnd);
memset (&tbi, 0, sizeof (tbi));
tbi.cbSize = sizeof (tbi);
tbi.dwMask = TBIF_STATE;
if(SendMessage (hwndMB, TB_GETBUTTONINFO, IDPOP, (LPARAM)&tbi)) {
if (fReply)
tbi.fsState |= TBSTATE_ENABLED;
else
tbi.fsState &= ~TBSTATE_ENABLED;
SendMessage (hwndMB, TB_SETBUTTONINFO, IDPOP, (LPARAM)&tbi);
}
return nSel;
}
//----------------------------------------------------------------------
// SendSmsMessage - Send an SMS message
//
HRESULT SendSmsMessage (HWND hWNd, SMS_ADDRESS smsDest, LPTSTR pMsg) {
HRESULT hr;
SMS_HANDLE smshHandle;
TEXT_PROVIDER_SPECIFIC_DATA tpsd;
SMS_MESSAGE_ID smsmidMessageID = 0;

// try to open an SMS Handle
hr = SmsOpen(SMS_MSGTYPE_TEXT, SMS_MODE_SEND, &smshHandle, NULL);
if (hr != ERROR_SUCCESS)
return hr;
// Set up provider specific data
tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
tpsd.psMessageClass = PS_MESSAGE_CLASS0;
tpsd.psReplaceOption = PSRO_NONE;
tpsd.dwHeaderDataSize = 0;
// Send the message, indicating success or failure
hr = SmsSendMessage (smshHandle, NULL, &smsDest, NULL, (PBYTE)pMsg,
lstrlen(pMsg) * sizeof (TCHAR),
(PBYTE) &tpsd, 12, SMSDE_OPTIMAL,
SMS_OPTION_DELIVERY_NONE, &smsmidMessageID);
SmsClose (smshHandle);
return hr;
}
//======================================================================
// WriteMsg Dialog procedure
//
BOOL CALLBACK WriteDlgProc (HWND hWnd, UINT wMsg, WPARAM wParam,
LPARAM lParam) {
SHINITDLGINFO shidi;
static SMS_ADDRESS smsDest;
TCHAR szMsg[SMS_DATAGRAM_SIZE+2];
HRESULT hr;
SHInputDialog (hWnd, wMsg, wParam);
switch (wMsg) {
case WM_INITDIALOG:
// Specify that the dialog box should stretch full screen
memset (&shidi, 0, sizeof(shidi));
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_SIZEDLGFULLSCREEN;
if (!g_fOnSPhone) shidi.dwFlags |= SHIDIF_DONEBUTTON;
shidi.hDlg = hWnd;
if(!SHInitDialog(&shidi))
return FALSE;
// This is only needed on the smartphone
if (g_fOnSPhone) {
// Create MenuBar
SHMENUBARINFO mbi;
memset (&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
mbi.nToolBarId = ID_DLGMENU_SP;
mbi.hInstRes = hInst;
// If we could not initialize the dialog box, return an error
if (!SHCreateMenuBar(&mbi)) {
ErrorBox (hWnd, TEXT("Menubar failed"));
DestroyWindow (hWnd);
return FALSE;
}
// Override back key since we have an edit control
SendMessage (SHFindMenuBar (hWnd), SHCMBM_OVERRIDEKEY, VK_TBACK,
MAKELPARAM (SHMBOF_NODEFAULT | SHMBOF_NOTIFY,
SHMBOF_NODEFAULT | SHMBOF_NOTIFY));
// Set input mode of number field to numbers
SendDlgItemMessage (hWnd, IDD_MSGADDR, EM_SETINPUTMODE, 0,
EIM_NUMBERS);
}
SendDlgItemMessage (hWnd, IDD_MSGTEXT, EM_LIMITTEXT,
SMS_DATAGRAM_SIZE, 0);
// If there is a reply address passed, place it in control
if (lParam) {
// Copy dest address
smsDest = *(SMS_ADDRESS *)lParam;
SetDlgItemText (hWnd, IDD_MSGADDR, smsDest.ptsAddress);
SetFocus (GetDlgItem (hWnd, IDD_MSGTEXT));
return FALSE;
} else {
smsDest.smsatAddressType = SMSAT_INTERNATIONAL;
memset (smsDest.ptsAddress, 0,
sizeof (smsDest.ptsAddress));
}
return TRUE;
case WM_HOTKEY:
SHSendBackToFocusWindow (wMsg, wParam, lParam);
return TRUE;
case WM_COMMAND:
switch (LOWORD (wParam)) {
case ID_CMDSEND:
GetDlgItemText (hWnd,IDD_MSGADDR, smsDest.ptsAddress,
dim (smsDest.ptsAddress));
GetDlgItemText (hWnd,IDD_MSGTEXT, szMsg, dim (szMsg));
hr = SendSmsMessage (hWnd, smsDest, szMsg);
if (hr == 0)
MessageBox (hWnd, TEXT("Message Sent"), szAppName,
MB_OK | MB_ICONASTERISK);
else
ErrorBox (hWnd, TEXT ("Send message fail %x %d"),
hr, GetLastError());
case IDOK:
case IDCANCEL:
EndDialog (hWnd, 0);
return TRUE;
}
break;
}
return FALSE;
}
//----------------------------------------------------------------------
// ErrorBox - Displays a message box with a formatted string
//
void ErrorBox (HWND hWnd, LPCTSTR lpszFormat, ...) {
int nBuf;
TCHAR szBuffer[512];
va_list args;
va_start(args, lpszFormat);
nBuf = _vstprintf(szBuffer, lpszFormat, args);
MessageBox (hWnd, szBuffer, TEXT("Error Msg"), MB_OK);
va_end(args);
}
//----------------------------------------------------------------------
// OnSmartPhone - Determines if we're running on a smartphone
//
BOOL OnSmartPhone (void) {
TCHAR szPlat[128];
int rc;
rc = SystemParametersInfo(SPI_GETPLATFORMTYPE, dim(szPlat),szPlat,0);
if (rc) {
if (lstrcmpi (szPlat, TEXT("Smartphone")) == 0)
return TRUE;
}
return FALSE;
}
//======================================================================
// MonitorThread - Monitors event for timer notification
//
DWORD WINAPI MonitorThread (PVOID pArg) {
TEXT_PROVIDER_SPECIFIC_DATA tpsd;
SMS_HANDLE smshHandle = (SMS_HANDLE)pArg;
PMYMSG_STRUCT pNextMsg;
BYTE bBuffer[MAXMESSAGELEN];
PBYTE pIn;
SYSTEMTIME st;
HANDLE hWait[2];
HRESULT hr;
int rc;
DWORD dwInSize, dwSize, dwRead = 0;
hWait[0] = g_hReadEvent; // Need two events since it isn't
hWait[1] = g_hQuitEvent; // allowed for us to signal SMS event.
while (g_fContinue) {
rc = WaitForMultipleObjects (2, hWait, FALSE, INFINITE);
if (!g_fContinue || (rc != WAIT_OBJECT_0))
break;
// Point to the next free entry in the array
pNextMsg = &g_pMsgDB->pMsgs[g_pMsgDB->nMsgCnt];
// Get the message size
hr = SmsGetMessageSize (smshHandle, &dwSize);
if (hr != ERROR_SUCCESS) continue;
// Check for message larger than std buffer
if (dwSize > sizeof (pNextMsg->wcMessage)) {
if (dwSize > MAXMESSAGELEN)
continue;
pIn = bBuffer;
dwInSize = MAXMESSAGELEN;
} else {
pIn = (PBYTE)pNextMsg->wcMessage;
dwInSize = sizeof (pNextMsg->wcMessage);
}
// Set up provider specific data
tpsd.dwMessageOptions = PS_MESSAGE_OPTION_NONE;
tpsd.psMessageClass = PS_MESSAGE_CLASS0;
tpsd.psReplaceOption = PSRO_NONE;
tpsd.dwHeaderDataSize = 0;
// Read the message
hr = SmsReadMessage (smshHandle, NULL, &pNextMsg->smsAddr, &st,
(PBYTE)pIn, dwInSize, (PBYTE)&tpsd,
sizeof(TEXT_PROVIDER_SPECIFIC_DATA),
&dwRead);
if (hr == ERROR_SUCCESS) {
// Convert GMT message time to local time
FILETIME ft, ftLocal;
SystemTimeToFileTime (&st, &ft);
FileTimeToLocalFileTime (&ft, &ftLocal);
FileTimeToSystemTime (&ftLocal, &pNextMsg->stMsg);
// If using alt buffer, copy to std buff
if ((DWORD)pIn == (DWORD)pNextMsg->wcMessage) {
pNextMsg->nSize = (int) dwRead;
} else {
memset (pNextMsg->wcMessage, 0,
sizeof(pNextMsg->wcMessage));
memcpy (pNextMsg->wcMessage, pIn,
sizeof(pNextMsg->wcMessage)-2);
pNextMsg->nSize = sizeof(pNextMsg->wcMessage);
}
// Increment message count
if (g_pMsgDB->nMsgCnt < MAX_MSGS-1) {
if (g_hMain)
PostMessage (g_hMain, MYMSG_TELLNOTIFY, 1,
g_pMsgDB->nMsgCnt);
g_pMsgDB->nMsgCnt++;
}
} else {
ErrorBox (g_hMain, TEXT("Error %x (%d) reading msg"),
hr, GetLastError());
break;
}
}
SmsClose (smshHandle);
return 0;
}












The example illustrates a number of techniques used in a Smartphone application. The dialog templates use both expandable edit fields and spinner controls. The Back button is overridden in both the main dialog and the Compose dialog because both contain edit controls. The edit control that holds the destination number in the Compose dialog has its input mode overridden to numeric mode because the address to be entered is more than likely a phone number.


The SMS code in the application uses a separate thread, appropriately named MonitorThread, to monitor incoming SMS messages and uses a send routine, SendSmsMessage, that sends messages. Before the monitor thread is launched, the SmsOpen is called to request a read-access handle. This call will fail if another application currently has an open SMS handle with read access. If the open fails, SMSTalk displays a message box notifying the user of the problem and terminates. On the Pocket PC, this failure is quite likely because the e-mail program Pocket Inbox stays running in the background. Pocket Inbox can be closed by selecting the Inbox from the Start menu and then entering Ctrl-Q on the soft keyboard.


Sending an SMS message is accomplished by selecting the Reply or the New menu item. First a dialog is displayed so that the message can be composed. If the user selects to send the message, the program calls SendSmsMessage. This routine opens an SMS handle for write access, fills in the appropriate structures, and sends the message in the simple text format.


Incoming messages are saved in an array in memory. The messages can then be viewed by highlighting a message in the list box on the Pocket PC, or in the spinner on the Smartphone. SMSTalk does not save the messages because the goal is to demonstrate the features of the SMS system and the Smartphone with the least amount of clutter. Because of this arrangement, any messages received by SMSTalk will be lost when the program terminates. Saving the messages in a file would be a great enhancement and is left as a task for the reader.


/ 169