使用 CEMAPI 在 Windows Mobile (5, 6) 设备上读取短信 (彩信) 和电子邮件






4.75/5 (7投票s)
在本文中,我想告诉您如何从 Windows Mobile 设备读取短信、彩信和电子邮件数据。我还将描述在 Windows Mobile 5 和 Windows Mobile 6 设备上读取消息正文之间的一些差异。
目录
MAPI 概述。邮件 API 对象模型的描述
Pocket PC 上的邮件 API 提供了一组 COM 接口,主要用于处理与电子邮件相关的数据。您可以使用此 API 从消息中获取数据。
在这里您可以看到邮件 API 对象模型
下面,您可以看到 MAPI 组件对象体系结构与 Windows Mobile 设备上的邮件客户端的关系。
这里是邮件 API 对象模型的一些接口
IMAPISession
接口是您在处理 MAPI 时使用的父对象。它用于初始化 MAPI 并允许您登录到消息存储。IMsgStore
接口用于提供对与特定电子邮件传输相关联的文件存储的访问。IMAPIFolder
接口用于访问消息存储中包含的消息和子文件夹。IMAPITable
接口用于查看 MAPI 对象集合。例如,MAPI 文件夹将包含一个消息表。IMAPIProp
接口用于设置和检索 MAPI 对象属性。IMessage
接口用于处理单个电子邮件消息。IAttach
接口用于处理电子邮件消息附件。
某些 MAPI 对象和数据类型的描述
下面,我将向您展示如何处理属性。如果您想获取属性,可以调用以下 IMAPIProp::GetProps()
函数
HRESULT IMAPIProp::GetProps(
LPSPropTagArray lpPropTagArray,
ULONG ulFlags,
ULONG *lpcValues,
LPSPropValue *lppPropArray);
lpPropTagArray
参数是指向SPropTagArray
结构的指针,该结构包含一个属性标记数组,您希望从对象中检索这些标记。ulFlags
-MAPI_UNICODE
标志。lpcValues
是返回的属性数量。lppPropArray
是包含返回属性值的SPropValue
数组。
注意:属性值按 lpPropTagArray
结构中指定的顺序返回。
SPropTagArray
结构定义如下
typedef struct _SPropTagArray {
ULONG cValues; - the number of items in the array
ULONG aulPropTag[MAPI_DIM]; - array of property tags
} SPropTagArray, FAR *LPSPropTagArray;
SPropValue
结构定义如下
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;
例如,我将向您展示如何从 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
是一个宏,用于帮助初始化 SPropTagArray
结构。
有时您需要打开一个大的属性,例如邮件附件或邮件正文。您需要使用 IMAPIProp::OpenProperty()
方法来访问它。此函数打开一个属性值,并使用 IStream
接口以大块形式读取属性数据。
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
例如,让我们看看如何从 IMessage
对象读取正文
LPSTREAM pstmBody = NULL;
HRESULT hr = pMsg->OpenProperty (PR_CE_MIME_TEXT, NULL, STGM_READ, 0,
(IUnknown **) &pstmBody);
您还需要了解如何处理 IMAPITables
。
如果您想从表中读取数据,您需要使用 SRow
结构和 SRowSet
结构
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;
请记住,无论何时返回 SRow
或 SRowSet
结构,您都需要相应地调用 FreeProws()
和 MAPIFreeBuffer()
函数,以正确释放任何已分配的内存。
读取消息存储
让我们谈谈如何从移动设备读取消息。
首先,您需要像这样打开 IMAPISession
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;
}
然后您需要枚举所有消息存储。
首先,您必须获取所有消息存储的表
IMAPITable *pIMapiStoresTable = NULL;
hr = pIMapi->GetMsgStoresTable(0, &pIMapiStoresTable);
使用此表,您可以获取每个存储的名称。为此,您应该使用 QueryRows()
方法从 pIMapiStoresTable
获取 RowSet
// 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;
除了名称,您还需要每个存储的 IMsgStore
对象
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;
}
每个 IMsgStore
对象都有以下子文件夹
PM_IPM_OUTBOX_ENTRYID
,用于发件箱文件夹PM_IPM_SENTMAIL_ENTRYID
,用于已发送邮件文件夹PM_IPM_WASTEBASKET_ENTRYID
,用于已删除邮件文件夹PM_CE_IPM_DRAFTS_ENTRYID
,用于草稿文件夹PM_CE_IPM_INBOX_ENTRYID
,用于收件箱文件夹
例如,您可以执行以下步骤从发件箱文件夹读取数据
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.
在本节中,我描述了如何以原始数据、MIME 文本或 HTML 正文格式获取正文。正文数据的保存方式在 Windows Mobile 5 和 Windows Mobile 6 设备上有所不同。因此,这里有一些注意事项
对于 Windows Mobile 6:外出邮件正文存储在
PR_BODY_W
用于纯文本邮件PR_BODY_HTML_A
(多字节,用于 HTML 邮件)
入站邮件正文存储在
- ActiveSync 帐户 (Exchange ActiveSync 和 PC Sync)
PR_BODY_A
,或PR_BODY_HTML_A
- POP3/IMAP 邮件以 MIME 格式传入,我们将包含正文的完整 MIME 存储在
PR_CE_MIME_TEXT
中
由于 MAPI 规范允许您以不同方式存储信息,因此您基本上需要循环遍历属性的可能位置,直到找到匹配项,才能制作一个健壮的客户端应用程序。您可以查询 PR_MSG_STATUS
并将其与以下标志进行比较
MSGSTATUS_HAS_PR_BODY
MSGSTATUS_HAS_PR_BODY_HTML
MSGSTATUS_HAS_PR_CE_MIME_TEXT
以确定当前消息使用的属性。
对于 Windows Mobile 5.0
- 入站正文存储在
PR_CE_MIME_TEXT
(多字节) - 外出邮件正文存储在
PR_BODY
(Unicode) - 一切都是纯文本格式。不支持 HTML。
LPSTREAM pstmBody = NULL;
HRESULT hr = pMsg->OpenProperty (PR_BODY, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
您也可以尝试使用其他类型的属性读取数据,例如 PR_BODY_W
(Unicode) 和 PR_BODY_A
(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);
MIME 文本正文的示例如下
…
hr = pMsg->OpenProperty (PR_CE_MIME_TEXT, NULL, STGM_READ, 0, (IUnknown **) &pstmBody);
…
仅在 Windows Mobile 6 上,HTML 正文的示例如下
…
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);
//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.
首先,您需要从 IMessage
对象读取收件人表
…
IMAPITable * pRecipientTable = NULL;
hr = pMsg->GetRecipientTable(MAPI_UNICODE, &pRecipientTable);
…
然后您需要读取收件人数量。您可以使用相应的方法从 pRecipientTable
获取此信息
…
ULONG recipientCount = 0;
hr = pRecipientTable->GetRowCount(0, &recipientCount);
…
和普通的 IMAPITable
一样,您可以获取 SRowSet
的集合,然后读取有关收件人行的信息
…
SRowSet * pRowSet = NULL;
hr = pRecipientTable->QueryRows(1, 0, &pRowSet);
…
通过每个 SPropValue
的 ulPropTag
,您可以检测到所需的信息。例如,您可以检测到以下信息
…
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 */
…
}
}
…
结论
我希望本文能帮助您了解
- MAPI 对象模型及其与邮件客户端的关系
- 如何处理某些 MAPI 对象和数据类型
- 如何从 Windows Mobile 设备读取消息
- Win Mobile 5 和 Win Mobile 6 读取消息正文之间的区别
在本文中,我没有展示如何处理附件,也没有描述从 Windows Mobile 6 设备读取彩信正文的问题。