Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / WinMobile

Reading SMS (MMS) and Emails on Windows Mobile (5, 6) Devices Using CEMAPI

4.75/5 (7 votes)
4 Nov 2010CPOL5 min read 34K   675  
In this article, I would like to tell you how to read the SMS, MMS, and Emails data from your Windows Mobile device. Also I’ll describe some differences between reading message body in Windows Mobile 5 and Windows Mobile 6 devices.

Table of Contents

MAPI Overview. Description of the Mail API Object Model

The Mail API on Pocket PC provides you with a set of COM interfaces that are used primarily for work with the data associated with e-mail. You can get data from the messages using this API.

Here you can see the Mail API Object Model:

read-sms-on-win-mobile/Model.png

Below, you can see how the MAPI component object architecture relates to the mail client on the Windows Mobile device.

read-sms-on-win-mobile/FM4_SelectFolder.png

Here are some interfaces of the Mail API Object Model:

  • The IMAPISession interface is the parent object that you use when working with MAPI. It is used to initialize MAPI and enables you to log on to a message store.
  • The IMsgStore interface is used to provide access to the file storage associated with a specific e-mail transport.
  • The IMAPIFolder interface is used to access messages and subfolders contained within a message store.
  • The IMAPITable interface is used to view collections of MAPI objects. For example, the MAPI folder will contain a table of messages.
  • The IMAPIProp interface is used to set and retrieve MAPI object properties.
  • The IMessage interface is used to handle individual e-mail messages.
  • The IAttach interface is used to handle e-mail message attachments.

Description of Some MAPI Objects and Data Types

Below, I’ll show you how to work with properties. If you want to get a property, you can call the following IMAPIProp::GetProps() function:

C++
HRESULT IMAPIProp::GetProps(
	LPSPropTagArray lpPropTagArray, 
  	ULONG ulFlags, 
	ULONG *lpcValues, 
	LPSPropValue *lppPropArray);
  • lpPropTagArray parameter is a pointer to the SPropTagArray structure that contains an array of property tags, which you want to retrieve from the object.
  • ulFlags - MAPI_UNICODE flag.
  • lpcValues is the number of properties returned.
  • lppPropArray is the SPropValue array that contains the returned property values.

Note: Property values are returned in the same order that is specified in the lpPropTagArray structure.

The SPropTagArray structure is defined as follows:

C++
typedef struct _SPropTagArray {
   ULONG cValues;              - the number of items in the array
   ULONG aulPropTag[MAPI_DIM]; - array of property tags
} SPropTagArray, FAR *LPSPropTagArray;

And the SPropValue structure is defined as follows:

C++
typedef struct _SPropValue {
   ULONG ulPropTag;	 - specifies the property tag for the property
   ULONG dwAlignPad;	- is used by MAPI to ensure that the structure has
  				          proper memory alignment
   union _PV Value;	 - the specific value based on the data type for the
			            property
} SPropValue, FAR *LPSPropValue;

For example, I will show you how to read some properties from IMsgStore:

C++
LPSPropValue rgprops = NULL;
ULONG cValues = 0;
… 
SizedSPropTagArray(2, rgTags) = {2,{PR_CE_IPM_INBOX_ENTRYID,PR_OBJECT_TYPE}};

hr = msgStore->GetProps((LPSPropTagArray)&rgTags, 
				MAPI_UNICODE, 
				&cValues, 
				&rgprops);
…

SizedSPropTagArray is a macro that helps to initialize the SPropTagArray structure.

Sometimes you need to open a large property, such as message attachment or message body. You need to use the IMAPIProp::OpenProperty() method to access it. This function opens a property value and uses the IStream interface for reading of the property data in large blocks.

C++
HRESULT IMAPIProp::OpenProperty(
	ULONG ulPropTag,  - the property tag that you are interested in
	LPCIID lpiid,     - not supported in Pocket PC
	ULONG ulInterfaceOptions, - not supported in Pocket PC
	ULONG ulFlags, 	   - determines the access mode of the property. 
			     Can be read-only. Or for reading and writing –
			     MAPI_MODIFY flag
	LPUNKNOWN *lppUnk); - pointer to the interface for the IStream object

For example, let’s take a look at how to read the body from the IMessage object:

C++
LPSTREAM pstmBody = NULL;
HRESULT hr = pMsg->OpenProperty (PR_CE_MIME_TEXT, NULL, STGM_READ, 0, 
		(IUnknown **) &pstmBody);

You also need to know how to work with IMAPITables.

If you want to read data from the table, you need to use the SRow structure and the SRowSet structure:

C++
typedef struct _SRow {
   ULONG ulAdrEntryPad;   -  a set of bytes that are used to properly align 
			   the structure in memory 
   ULONG cValues;	   - contains the number of items
   LPSPropValue lpProps;  - the pointer to the array of SPropValue structures
} SRow, FAR *LPSRow;

typedef struct _SRowSet {
   ULONG cRows;		    - the number of SRow structures
   SRow aRow[MAPI_DIM];    - an array of SRow structures
} SRowSet, FAR *LPSRowSet;

Remember that whenever you are returned the SRow or SRowSet structure, you also need to call the FreeProws() and MAPIFreeBuffer() functions, accordingly, to properly release any memory that has been allocated.

Reading Message Stores

Let’s talk about how to read the messages from the mobile device.

First of all, you need to open IMAPISession as follows:

C++
IMAPISession *iMAPISession_;
…

if(FAILED(CoInitializeEx(NULL, 0)))
	{
		return E_FAIL;
	}

	if(MAPIInitialize(NULL) != S_OK)
	{
		return E_FAIL;
	}

	if(MAPILogonEx(0, NULL, NULL, 0, &iMAPISession_) != S_OK)
	{
		MAPIUninitialize();
		return E_FAIL;
	}

Then you need to enumerate all message stores.

First of all, you have to get the table of all message stores:

C++
IMAPITable *pIMapiStoresTable = NULL;
hr = pIMapi->GetMsgStoresTable(0, &pIMapiStoresTable);

Using this table, you can obtain the name of each store. To do that, you should get RowSet from pIMapiStoresTable using the QueryRows() method:

C++
// Set the number and order of columns that will be returned after QueryRows()
SizedSPropTagArray(2, tblColumns) = {2,{PR_DISPLAY_NAME, PR_ENTRYID}};
pIMapiStoresTable->SetColumns((LPSPropTagArray)&tblColumns, 0);

SRowSet *pRowSet1 = NULL;
hr = pIMapiStoresTable->QueryRows(1, 0, &pRowSet1);
LPWSTR storeName = pRowSet1->aRow[0].lpProps[0].Value.lpszW;

Besides the name, you also need the IMsgStore object for each store:

C++
ENTRYID* pEntry =(ENTRYID*)pRowSet1->aRow[0].lpProps[1].Value.bin.lpb;
ULONG ulStoreBytes = pRowSet1->aRow[0].lpProps[1].Value.bin.cb;

IMsgStore *iMessageStore = NULL;

if (FAILED(iMAPISession_->OpenMsgStore
	(NULL, ulStoreBytes, pEntry, NULL, NULL, &iMessageStore)))
{
	return E_FAIL;
}

Each IMsgStore object has the following subfolders:

  • PM_IPM_OUTBOX_ENTRYID, for the Outbox folder
  • PM_IPM_SENTMAIL_ENTRYID, for the Sent Items folder 
  • PM_IPM_WASTEBASKET_ENTRYID, for the Deleted Items folder
  • PM_CE_IPM_DRAFTS_ENTRYID, for the Drafts folder
  • PM_CE_IPM_INBOX_ENTRYID, for the Inbox folder

For example, you can read data from the Outbox folder performing the following steps:

C++
LPSPropValue rgprops = NULL;
LPSPropValue lppPropArray = NULL;
ULONG cValues = 0;
IMAPIFolder *pSubFolder = NULL;


// Step #1. Read properties from the message store.
// Where subFolderIdentifier = PM_IPM_OUTBOX_ENTRYID;
…
SizedSPropTagArray(2, rgTags) = {2,{subFolderIdentifier,PR_OBJECT_TYPE}};
hr = msgStore->GetProps((LPSPropTagArray)&rgTags, MAPI_UNICODE, &cValues, &rgprops);
…

// Step #2. Open the corresponding folder.
hr = msgStore->OpenEntry(rgprops[0].Value.bin.cb, 
(LPENTRYID)rgprops[0].Value.bin.lpb, NULL,
MAPI_MODIFY, NULL, (LPUNKNOWN*)&pSubFolder);
…

// Step #3. Get the table with messages.
IMAPITable* pSubFolderTable = NULL;			
hr = pSubFolder->GetContentsTable(0, &pSubFolderTable);

…

// Step #4. Read messages.

ULONG messageCount = 0;
hr = pSubFolderTable->GetRowCount(0, &messageCount);while(!exit) 
for (unsigned long i = 0; i < messageCount ; ++i)
{
	SRowSet *pRowSet = NULL;        
	SizedSPropTagArray(3, tblMessages) = {3,{PR_SENDER_NAME, PR_SUBJECT, 
								PR_ENTRYID}};
	pSubFolderTable->SetColumns((LPSPropTagArray)&tblMessages, 0);

	hr = pSubFolderTable->QueryRows(1, 0, &pRowSet);

	if(pRowSet->cRows != 1)
	{
		breake;
	}

	IMessage *pMsg = NULL;
	hr = msgStore->OpenEntry(pRowSet->aRow[0].lpProps[2].Value.bin.cb, 
(LPENTRYID)pRowSet->aRow[0].lpProps[2].Value.bin.lpb, 
					NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)&pMsg);
…
// Here we can get data that we need from the IMessage object.
// For example, read body, properties, recipients, and attachments.
…
}
//Step #5. Read message properties.
SizedSPropTagArray(propertyCount, rgMsgTags) = {propertyCount,{
        PR_SENDER_NAME, 
            PR_TITLE,
            PR_LAST_MODIFICATION_TIME, 
            PR_MESSAGE_DELIVERY_TIME, 
            PR_DELIVER_TIME,
            PR_MSG_STATUS,
            PR_MESSAGE_FLAGS,
            PR_SUBJECT,
            PR_SUBJECT_PREFIX,
            PR_HASATTACH,
            PR_SENDER_EMAIL_ADDRESS}};
        LPSPropValue rgMsgprops = NULL;

        ULONG cMsgValues = 0;

        HRESULT hr = pMsg->GetProps((LPSPropTagArray)&rgMsgTags, 
			MAPI_UNICODE, &cMsgValues, &rgMsgprops);

…
C++
//Step #6. Read body.

In this section, I described how to get the body in Raw data, MIME Text, or HTML Body formats. The saving of body data is different on Windows Mobile 5 and Windows Mobile 6 devices. That’s why, here are some notes:

For Windows Mobile 6: outgoing mail bodies are stored in:

  • PR_BODY_W for plain text mail
  • PR_BODY_HTML_A (multibyte, for HTML mail)

Incoming mail bodies are stored in:

  • ActiveSync account (both Exchange ActiveSync and PC Sync)
  • PR_BODY_A, or
  • PR_BODY_HTML_A
  • POP3/IMAP messages come in MIME format and we store the full MIME with body included in PR_CE_MIME_TEXT

Since MAPI specification does allow you to store info in different ways, you basically need to loop through the possible location of the properties until a match is found to make a robust client app. You can query PR_MSG_STATUS and compare it with the following flags:

  • MSGSTATUS_HAS_PR_BODY
  • MSGSTATUS_HAS_PR_BODY_HTML
  • MSGSTATUS_HAS_PR_CE_MIME_TEXT

to determine which properties are used for the current message.

For Windows Mobile 5.0:

  • incoming bodies are stored in PR_CE_MIME_TEXT (in multibyte)
  • outgoing mail bodies are stored in PR_BODY (in Unicode)
  • everything is in the plain text format. There is no HTML support.
C++
LPSTREAM pstmBody = NULL;
HRESULT hr = pMsg->OpenProperty (PR_BODY, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);

Also you can try to read data using other types of the property, such as PR_BODY_W (for unicode) and PR_BODY_A (for ASCII):

C++
…
hr = pMsg->OpenProperty (PR_BODY_W, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
…
hr = pMsg->OpenProperty (PR_BODY_A, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);

The sample for MIME text body is as follows:

C++
…
hr = pMsg->OpenProperty (PR_CE_MIME_TEXT, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
… 

The sample for HTML body only on Windows Mobile 6 is as follows:

C++
…
hr = pMsg->OpenProperty (0x1013001E/*PR_BODY_HTML_A*/, 
		NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
…
hr = pMsg->OpenProperty (0x1013001F/*PR_BODY_HTML_W*/, 
		NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
C++
//Step #6.1 Read data from the stream.
std::vector<unsigned char> outBuffer;
    if(pstmBody == NULL)
    {
        return E_FAIL;
    }
    STATSTG stg;
    HRESULT hr = pstmBody->Stat(&stg,STATFLAG_NONAME); // here we read 
						// statistic information.
    if (FAILED(hr))
    {
        return hr;
    }

    unsigned int bodysize = stg.cbSize.LowPart;
    if(bodysize == 0)
    {
        return hr;
    }

    unsigned char* bodybuf = new unsigned char[bodysize];
    ZeroMemory(bodybuf, bodysize);
    ULONG read;
    hr = pstmBody->Read(bodybuf, bodysize, &read);
    if (FAILED(hr) || bodysize != read)
    {
        delete [] bodybuf; 
        return hr;
    }

    outBuffer.assign(bodybuf, bodybuf + bodysize);
    delete [] bodybuf;
C++
//Step #7. Read recipients. 

First of all, you need to read the recipient table from the IMessage object:

C++
…
IMAPITable * pRecipientTable = NULL;
hr = pMsg->GetRecipientTable(MAPI_UNICODE, &pRecipientTable);
…

Then you need to read the number of recipients. You can get this information from pRecipientTable using the corresponding method:

C++
…
ULONG recipientCount = 0;
hr = pRecipientTable->GetRowCount(0, &recipientCount);
…

And as for usual IMAPITable, you can get the collection of SRowSet and after that read information about the recipient row:

C++
…
SRowSet * pRowSet = NULL;
hr = pRecipientTable->QueryRows(1, 0, &pRowSet); 
…

By ulPropTag of each SPropValue, you can detect information you need. For example, you can detect the following information:

C++
switch (pspv->ulPropTag)
        {
        case PR_EMAIL_ADDRESS:
        case PR_EMAIL_ADDRESS_A: // This is a number or email
            {
                if(pspv->Value.lpszW != NULL)
		        …
		    }
    		  break;
        case PR_DISPLAY_NAME:	
            {
                …
               // pspv->Value.lpszW - This is a name
               …
            }
     		break;
        case PR_RECIPIENT_TYPE: 
           {
               …
               // pspv->Value.ul - This is a type. 
               //  original constants from "mapidefs.h"
               #define MAPI_TO   1  /* Recipient is a primary recipient  */
               #define MAPI_CC   2  /* Recipient is a copy recipient     */
               #define MAPI_BCC  3  /* Recipient is blind copy recipient */
               …
           }
        }
…

Conclusion

I hope this article helped you to get to know about:

  • MAPI Object Model and how it relates to the mail client
  • How to work with some MAPI objects and data types
  • How to read messages from the Windows Mobile device
  • The differences between reading message body from Win Mobile 5 and Win Mobile 6

In this article, I didn’t show how to work with attachments and didn’t describe the problem of reading MMS bodies from Windows Mobile 6 devices.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)