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.
In this article, you can also find information about how to read message recipients.
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:
Bellow you can see how the MAPI component object architecture relates to the mail client on the Windows Mobile device.
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:
HRESULT IMAPIProp::GetProps(
LPSPropTagArray lpPropTagArray,
ULONG ulFlags,
ULONG *lpcValues,
LPSPropValue *lppPropArray);
lpPropTagArray
parameter is a pointer to theSPropTagArray
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 theSPropValue
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:
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:
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
:
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.
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:
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:
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:
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:
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:
// 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:
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 folderPM_IPM_SENTMAIL_ENTRYID
, for the Sent Items folderPM_IPM_WASTEBASKET_ENTRYID
, for the Deleted Items folderPM_CE_IPM_DRAFTS_ENTRYID
, for the Drafts folderPM_CE_IPM_INBOX_ENTRYID
, for the Inbox folder
For example, you can read data from the Outbox folder performing the following steps:
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);
โฆ
//Step #6. Read body.
In this section, I described you 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 mailPR_BODY_HTML_A
(multibyte, for html mail)
incoming mail bodies are stored in:
- ActiveSync account (both Exchange ActiveSync and PC Sync)
PR_BODY_A
, orPR_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.
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):
โฆ
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:
โฆ
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:
โฆ
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);
{/code}
{code}
//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;
//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;
//Step #7. Read recipients.
First of all, you need to read the recipient table from the IMessage
object:
โฆ
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:
โฆ
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:
โฆ
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:
โฆ
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.
Download Sources of the example (ZIP, 12 KB)