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
#define MAPI_CC 2
#define MAPI_BCC 3
…
}
}
…
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.
ApriorIT is a Software Research and Development company that works in advanced knowledge-intensive scopes.
Company offers integrated research&development services for the software projects in such directions as Corporate Security, Remote Control, Mobile Development, Embedded Systems, Virtualization, Drivers and others.
Official site http://www.apriorit.com