65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (7投票s)

2010年11月3日

CPOL

5分钟阅读

viewsIcon

34522

downloadIcon

691

在本文中,我想告诉您如何从 Windows Mobile 设备读取短信、彩信和电子邮件数据。我还将描述在 Windows Mobile 5 和 Windows Mobile 6 设备上读取消息正文之间的一些差异。

目录

MAPI 概述。邮件 API 对象模型的描述

Pocket PC 上的邮件 API 提供了一组 COM 接口,主要用于处理与电子邮件相关的数据。您可以使用此 API 从消息中获取数据。

在这里您可以看到邮件 API 对象模型

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

下面,您可以看到 MAPI 组件对象体系结构与 Windows Mobile 设备上的邮件客户端的关系。

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

这里是邮件 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;

请记住,无论何时返回 SRowSRowSet 结构,您都需要相应地调用 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); 
…

通过每个 SPropValueulPropTag,您可以检测到所需的信息。例如,您可以检测到以下信息

…
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 设备读取彩信正文的问题

© . All rights reserved.