用MAPI操作短信邮件
MAPI函数不仅可以读取短信,还可以创建短信,读写邮件等。 具体有哪些功能我也说不全,可以自己google一下。这里只详细说明MAPI读取短信或邮件的过程。在网上其他地方也有不少这有的例子,但不完全,甚至有错误。 在本例中,没有对MSDN中能查到的函数多加说明, 不懂的自己查.
前提,环境VS2005以及以上,语言是C++.
需要包含的头文件:
#include <cemapi.h>
#include <mapix.h>
#include <mapiutil.h>
需要包含的库:
#pragma comment(lib, "cemapi")
〇. 变量的声明
IMAPISession* m_pSession; //会话
IMsgStore* m_pMsgStore;
IMAPIFolder* m_folder;
LPMAPITABLE m_pMsgTable;
IMAPISession: MSDN说: The IMAPISession interface is used to manage objects associated with a MAPI logon session. 意思是, IMAPISession接口用来管理与MAPI登录会话相关的对象.
IMsgStore: The IMsgStore interface provides access to message store information and to messages and folders. IMsgStore接口提供对消息存储信息和文件夹的访问.
IMAPIFolder: The IMAPIFolder interface is used to perform operations on the messages and subfolders in a folder. IMAPIFolder接口用来执行对消息和子文件夹的操作.
LPMAPITABLE: IMAPITable接口指针, The IMAPITable interface is used to provide a read-only view of a table. IMAPITable is used by clients and service providers to manipulate the way a table appears. IMAPITable接口用来提供支队的表视图, 此接口被客户端和服务提供者执行表的出现( or 行为?).
变量的初始化: 以上变量初始化为NULL.
一. 首先需要初始化环境,并创建一个会话,
m_pSession = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
MAPIInitialize(NULL);
MAPILogonEx(NULL, NULL, NULL, NULL, &m_pSession);
说明:m_pSession为声明是IMAPISession* m_pSession
结束会话,退出环境:
if(m_pSession)
{
m_pSession->Logoff(0, 0, 0);
m_pSession->Release();
m_pSession = NULL;
}
if(m_pMsgStore)
MAPIFreeBuffer(m_pMsgStore);
if(m_folder)
MAPIFreeBuffer(m_folder);
if(m_pMsgTable)
MAPIFreeBuffer(m_pMsgTable);
MAPIUninitialize();
CoUninitialize();
以上两段代码可以分别放在一个类的构造和析构函数里面
二. 打开指定的帐户:
一台机器上可能存在多个帐户,如:短信帐户,多个邮件帐户,每个帐户名有一个display name。我们可能根据这个名字找到我们所需要的帐户。
OpenMsgStore函数让我们得到m_pMsgStore. 当然,是在函数执行成功的前提下.
// 功能: 打开指定的帐户
// 参数 tchar是指定的帐户名,如果要读取短信,tchar必须为sms。如果想要读取163邮箱中的邮件,tchar可以是“163”,但也不一定,这得看你在创建这个邮件帐户的时候的配置。
// 返回值: 如果成功打开帐户,返回TRUE, 否则返回FALSE.
BOOL CMapi::OpenMsgStore( TCHAR tchar[] )
{
if(m_pMsgStore) //帐户已经打开, 返回true
return TRUE;
LPMAPITABLE pTable = NULL; //LPMAPITABLE可以认为类似于数据库的表,是包含多行数据,每行可以顺序读取出来.
HRESULT hr = 0;
LPSRowSet pRows = NULL;// 指向表中的行
ULONG cValues;
SPropValue* pProps = NULL; // SPropValue用作返回对象属性值的结构
// get stores table
hr = m_pSession->GetMsgStoresTable(MAPI_UNICODE , &pTable); // 获取帐户表
...
BOOL bFound = FALSE;
while(SUCCEEDED(pTable->QueryRows(1, 0, &pRows))) //循环读取帐户表, 将读取的行信息返回到pRows指针指向的对象中,一行即代表一个帐户
{
if (NULL == pRows || pRows->cRows != 1)
{
break;
}
// open msg store
SBinary& blob = pRows->aRow[0].lpProps->Value.bin; // 此行(帐户)的EntryId,
hr = m_pSession->OpenMsgStore(NULL, blob.cb, (LPENTRYID)blob.lpb, NULL, 0, &m_pMsgStore);//根据EntryId打开帐户
if(FAILED(hr))
{
FreeProws(pRows);
pRows = NULL;
continue;
}
SPropTagArray props; // SpropTagArray是一个数组,用来存储属性列表, 而 SPropValue* 用来存储属性值的列表
props.cValues = 1; //指定属性的个数.
props.aulPropTag[0] = PR_DISPLAY_NAME; // 指定属性名, PR_DISPLAY_NAME是指帐户名.
hr = m_pMsgStore->GetProps(&props, MAPI_UNICODE, &cValues, &pProps); // 查询返回属性值, 属性值存储在pProps指针指向的对象中.
if (_tcsicmp(pProps[0].Value.lpszW, tchar) == 0) // 是否等于参数指定的帐户名, 如果是则返回,
{
SBinary& sesBin = pRows->aRow[0].lpProps[0].Value.bin;
m_pSession->OpenEntry(sesBin.cb, // 打开容器内的对象, 返回更深入的指针(MSDN上这么说的,我也不明白这是什么意思, 原话是:
// The OpenEntry method opens an object within the container, returning an interface pointer for further access.)
(LPENTRYID)sesBin.lpb,
NULL,
MAPI_BEST_ACCESS,
0,
(LPUNKNOWN*)&m_pMsgStore);
FreeProws(pRows); // 释放行资源
pRows = NULL;
MAPIFreeBuffer(pProps); // 释放属性资源
bFound = TRUE;
break;
}
FreeProws(pRows);
pRows = NULL;
MAPIFreeBuffer(pProps);
}
return bFound;;
}
三. 打开文件夹
OpenFolder函数将打开一个文件夹, 如果执行成功, 我们将得到一个有效的m_folder.
// 功能: 函数将打开指定的文件夹
// folderId, 文件夹的ID
// PR_CE_IPM_INBOX_ENTRYID 收件箱
// PR_IPM_SENTMAIL_ENTRYID 发件箱
// PR_CE_IPM_DRAFTS_ENTRYID 草稿箱
// 返回: 打开成功返回TRUE, 否则返回FALSE.
BOOL CMapi::OpenFolder( DWORD folderId )
{
if(m_pMsgStore==NULL)
return FALSE;
ULONG cValues;
SPropValue* pProps = NULL;
HRESULT hr;
if(m_folder)
{
MAPIFreeBuffer(m_folder);
}
SPropTagArray propDefaultFolder;propDefaultFolder.cValues = 1;
propDefaultFolder.aulPropTag[0] =folderId;//PR_CE_IPM_DRAFTS_ENTRYID; // 指定属性
hr = m_pMsgStore->GetProps (&propDefaultFolder, MAPI_UNICODE, &cValues, &pProps); // 获得属性值
SBinary& eidDrafts = pProps->Value.bin; // 获得文件夹的entryId
hr = m_pMsgStore->OpenEntry(eidDrafts.cb, (LPENTRYID)eidDrafts.lpb, NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)&m_folder); // 打开文件夹
MAPIFreeBuffer(pProps);
return SUCCEEDED(hr);
}
四. 开文信息列表, 打开文件夹后,就可以打开信息列表了. 类似于打开帐户.
OpenRows会打开一个文件信息表, 我们将得到m_pMsgTable.
//功能:打开文件信息列表
//返回: 打开成功返回TRUE, 否则返回FALSE
BOOL CMapi::OpenRows()
{
if(m_folder==NULL)
return FALSE;
if(m_pMsgTable)
{
MAPIFreeBuffer(m_pMsgTable);
m_pMsgTable = NULL;
}
HRESULT hr = m_folder->GetContentsTable(0, &m_pMsgTable);
if(FAILED(hr))
return FALSE;
return TRUE;
}
五. 返回行集. 获得 m_pMsgTable以后, 调用OpenOneRow可以返回table中的一行. 在OpenOneRow函数中m_pMsgTable调用QueryRows函数返回行, 根据QueryRows的参数, 我们可以获取table中的一行或多行信息.
HRESULT QueryRows(
LONG LRowCount,
ULONG ulFlags,
SRowSet **lppRows
);
LRowCount指定请求的行数. 例如, 当LRowCount等于1,第一次调用此函数的时候, 将返回第一行, 再次调用时,将从第二行读取开始读取, 可以用SeekRow来指定读取的位置.
这些操作非常像数据库中的记录集,
LPSRowSet: 是SRowSet的指针,MSDN是这有解释的, The SRowSet structure contains an array of SRow structures, each SRow structure describing a row from a table.
//功能: 获取一行信息.
// 参数: pMsgRows, 指向行的指针, 用来接收行.
// 返回值: 成功返回TRUE, 否则返回FALSE.
BOOL CMapi::OpenOneRow(LPSRowSet& pMsgRows)
{
pMsgRows = NULL;
HRESULT hr = m_pMsgTable->QueryRows(1, 0, &pMsgRows);
if(FAILED(hr))
return FALSE;
if( pMsgRows->aRow[0].lpProps==NULL)
{
FreeProws(pMsgRows);
return FALSE;
}
return TRUE;
}
六. 得到一条消息. GetMessage和GetSessionMsg将返回一条具体的消息接口IMessage.
参数LPMESSAGE是IMessage的指针, MSDN解释说:The IMessage interface is used for managing messages, attachments, and recipients. 用于输出.
pMsgRows参数是调用OpenOneRow所获得的行集.
// 功能: 获取一条消息
// 参数: LPSRowSet: 输入, 行信息
// LPMESSAGE: 输出, 用来获取一条信息
BOOL CMapi::GetMessage( LPSRowSet pMsgRows, LPMESSAGE& pMessage )
{
SBinary sbEntry = pMsgRows->aRow[0].lpProps[0].Value.bin; // 获取EntryId
return GetSessionMsg(sbEntry.cb, (LPENTRYID) sbEntry.lpb, pMessage);
}
//功能: 获取一条具体的消息
// 参数: cbEntryId, 消息标识长度
// lpEntryId, 消息标识
// 成功返回TRUE, 否则返回FALSE.
BOOL CMapi::GetSessionMsg( ULONG cbEntryId, LPENTRYID lpEntryId, LPMESSAGE& pMessage )
{
if(m_pSession==NULL)
return FALSE;
HRESULT hr = m_pSession->OpenEntry(cbEntryId, lpEntryId,NULL, MAPI_BEST_ACCESS, NULL,
(LPUNKNOWN*) &pMessage);
if(FAILED(hr))
{
TRACE1("Get message error %d/n", hr);
return FALSE;
}
return TRUE;
}
七: 获取消息的文本数据, 利用GetMsgInfo函数, 我们可以获得消息的标题, 内容等字符类型的数据(属性被指定为PT_STRING类型). 如果指定非文本类型的属性, 此函数将会一场.
// 功能: 获取消息的一条属性
// 参数: pMessage,输入, 消息指针; Info,用于获取结果; propId, 属性名
// 例如: PR_SUBJECT,PR_BODY, PR_SENDER_EMAIL_ADDRESS等.
// 返回值: 略.
BOOL CMapi::GetMsgInfo( LPMESSAGE pMessage, CString&Info, DWORD propID )
{
SPropValue* vl = NULL;
ULONG cValues;
SPropTagArray Properties;
Properties.cValues = 1;
Properties.aulPropTag[0] = propID;
vl = NULL;
HRESULT hr = pMessage->GetProps((LPSPropTagArray)&Properties, MAPI_UNICODE,
&cValues, &vl);
if((vl->ulPropTag&propID)!=propID)
return FALSE;
if(FAILED(hr))
return FALSE;
if((vl->Value.lpszW) != NULL)
{
Info = vl->Value.lpszW;
}
MAPIFreeBuffer(vl);
return TRUE;
}
八. 获取收件人, 收件人虽然也是LPMESSAGE的一个属性, 但不能向获得发件人一样用GetMsgInfo来获取, 因为收件人可能是多个. GetReceiver函数将在参数Recvr中返回所有的收件人, 每个收件人用逗号分割.
BOOL CMapi::GetReceiver( LPMESSAGE pMsg, CString& recvr )
{
IMAPITable* pTable = NULL;
HRESULT hr;
//通过GetRecipientTable获取联系人信息列表
hr = pMsg->GetRecipientTable(NULL, &pTable );
if(hr==MAPI_E_NO_RECIPIENTS)
{
TRACE0("No recipient/n");
recvr = "N/A";
return TRUE;
}
if(FAILED(hr))
return FALSE;
LPADRLIST pRecipentRows = NULL;
while(!FAILED(hr = pTable->QueryRows(1, 0, (LPSRowSet*)&pRecipentRows)))
{
if( pRecipentRows->cEntries == 0 )
break;
CString& strContact = recvr;
for(ULONG n = 0; n < pRecipentRows->cEntries; n++ )
{
//每个Entry代表一个联系人信息,每个联系人信息又有多个属性组成
for(ULONG i = 0; i < pRecipentRows->aEntries[n].cValues ; i++)
{
//判断如果是PR_EMAIL_ADDRESS属性,那么就找到了联系人地址
if( PR_EMAIL_ADDRESS == pRecipentRows->aEntries[n].rgPropVals[i].ulPropTag )
{
if(strContact!=_T(""))
strContact += ",";
strContact += pRecipentRows->aEntries[n].rgPropVals[i].Value.lpszW;
MAPIFreeBuffer(pRecipentRows->aEntries[n].rgPropVals);
}
}
}
MAPIFreeBuffer(pRecipentRows);
}
return TRUE;
}
最后附加CMapi类的声明: 其中DoListen和UndoListen函数没有详细说明, 这两个函数分别是用来监听消息动作和取消监听的. 这部分内容以后讨论.
class CMapi
{
protected:
IMAPISession* m_pSession; //会话
IMsgStore* m_pMsgStore;
IMAPIFolder* m_folder;
LPMAPITABLE m_pMsgTable;
protected:
BOOL GetMsgInfo(LPMESSAGE pMessage, CString&Info, DWORD propID);
public:
CMapi(void);
~CMapi(void);
public:
// 功能: 打开指定的帐户
// 参数 tchar: 帐户名,如果要读取短信,tchar一般为sms。
// 返回值: 如果成功打开帐户,返回TRUE, 否则返回FALSE.
BOOL OpenMsgStore(TCHAR tchar[]);
BOOL OpenFolder(DWORD folderId);
BOOL OpenRows();
BOOL OpenOneRow(LPSRowSet& pMsgRows);
BOOL GetMessage(LPSRowSet pMsgRows, LPMESSAGE& pMessage);
BOOL GetSessionMsg(ULONG cbEntryId, LPENTRYID lpEntryId, LPMESSAGE& pMessage);
// 获得发送者
BOOL GetSender(LPMESSAGE lpMsg, CString& sender);
//获取消息标题
BOOL GetMsgSubject(LPMESSAGE lpMsg, CString& subject);
// 获取接收者,接收者可能有多个,recvr中用逗号分割多个接收者
BOOL GetReceiver(LPMESSAGE pMsg, CString& recvr);
// 获得内容
BOOL GetMsgContent(LPMESSAGE lpMsg, CString& content);
//获得信息创建时间
BOOL GetCreateTime(LPMESSAGE, CString& strTime);
//监听
BOOL DoListen(ULONG uMask, LPMAPIADVISESINK lpMapiSind, ULONG* lpuLongConnection);
BOOL UndoListen(ULONG lpuLongConnection);
};