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:
Below, 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 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:
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:
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 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:
LPSPropValue rgprops = NULL;
LPSPropValue lppPropArray = NULL;
ULONG cValues = 0;
IMAPIFolder *pSubFolder = NULL;
…
SizedSPropTagArray(2, rgTags) = {2,{subFolderIdentifier,PR_OBJECT_TYPE}};
hr = msgStore->GetProps((LPSPropTagArray)&rgTags, MAPI_UNICODE, &cValues, &rgprops);
…
hr = msgStore->OpenEntry(rgprops[0].Value.bin.cb,
(LPENTRYID)rgprops[0].Value.bin.lpb, NULL,
MAPI_MODIFY, NULL, (LPUNKNOWN*)&pSubFolder);
…
IMAPITable* pSubFolderTable = NULL;
hr = pSubFolder->GetContentsTable(0, &pSubFolderTable);
…
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);
…
…
}
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);
…
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.
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,
NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
…
hr = pMsg->OpenProperty (0x1013001F,
NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
…
std::vector<unsigned char> outBuffer;
if(pstmBody == NULL)
{
return E_FAIL;
}
STATSTG stg;
HRESULT hr = pstmBody->Stat(&stg,STATFLAG_NONAME); 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;
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: {
if(pspv->Value.lpszW != NULL)
…
}
break;
case PR_DISPLAY_NAME:
{
…
…
}
break;
case PR_RECIPIENT_TYPE:
{
…
#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.