CEMAPI实战攻略(二)——建立与短信信箱的连接

2022-09-06 15:30:40 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

CEMAPI实战攻略

by 吴春雷

QQ:819543772

Email:wuchunlei@163.com

二.建立与短信信箱的连接

上一部分已经讨论过,如何搭建开发和测试环境,以及如何初始化CEMAPI,再继续这一部分的讨论之前,我们先要澄清几个概念。第一个是会话(Seesion),相信开发网络应用的朋友都不陌生,为了提高通讯效率降低通讯开销,有时候我们需要再目标与本地之间创建一个通道,在通道创建之初,目标与本地先做一些列的响应和请求确认两边的身份,当通道建立以后,目标与本地之间的通讯过程中就不再涉及两边的身份确认,这通常目标与本地之间的建立的通道,通常被称作会话,也就是Session。在使用Cemapi读取短信之前,应用程序也需要与设备上的信息(邮件)系统之间建立一个Session,用以 确认双方的身份,这是采用Cemapi读取短信的第一步。第二个概念是短消息(邮件)仓库(MsgStore),在WM中,邮件和短消息是属于一个系统的,Session建立了与这个系统之间的连接,然后必须告诉系统,我们的程序是要对邮件功能进行操作,还是要对短信功能进行操作,通过调用相应的函数(后面会介绍),MsgStore会指向我们需要操作的短信或邮件的仓库上。第三个概念是信箱,或者叫文件夹(Folder),当获得了指向一个具体仓库的MsgStore以后,下一步就需要获取具体的信箱(文件夹)了,比如当程序确定了希望对收件箱还是发件箱进行操作以后,Folder将会指向我们想要操作的具体的信箱。

OK,澄清了这三个概念,就可以进一步讨论,如何建立会话,获取具体信箱了。

1. 会话接口IMAPISession

从mapidefs.h中我们可以看到,通过DECLARE_MAPI_INTERFACE_这个宏使IMAPISession派生自IUnKnow接口,IUnKnow接口中定义引用技术等与COM有关的基本操作,关于IUnKnow的详细内容大家可以参见COM技术的相关资料。IMAPISession接口中值得注意的一个函数是GetMsgStoresTable,后面我们将通过调用该函数获取短信(邮件)仓库的列表。

2. 如何创建与MAPI的会话

Cemapi中,我们将使用MAPILogonEx函数建立与短信(邮件)系统的会话,MAPILogonEx在Mapix.h中的定义如下:

typedef HRESULT (STDMETHODCALLTYPE MAPILOGONEX)(

ULONG ulUIParam,

LPTSTR lpszProfileName,

LPTSTR lpszPassword,

ULONG ulFlags,

LPMAPISESSION FAR * lppSession

);

MAPILOGONEX MAPILogonEx;

从定义中可以看出MAPILogonEx函数返回一个HRESULT类型,采用宏FAILED和SUCCESSED可以判断函数是否成功返回。同时,该函数有五个参数,这五个参数分别表示,短信(邮件)系统登陆UI的现实方式以及Session的共享方式,配置文件的文件名,邮箱密码,编码方式(默认)和指向IMAPISession接口指针的指针,对于短信应用程序的开发,前四个参数均无意义,可以直接设置为NULL。如果函数调用成功,我们将会从最后一个参数那里得到短信(邮件)系统的Session指针。调用方法如下:

IMAPISession *m_pSession=NULL;

hr=MAPILogonEx(NULL,NULL,NULL,NULL,&m_pSession);

if(FAILED(hr) || NULL==m_pSession)

{

//异常处理

}

3. 如何终止与短信(邮件)系统的会话,并释放Session对象

使用IMAPISession接口中Logoff方法可以终止与短信(邮件)系统的会话,Logoff方法定义为:

HRESULT IMAPISession::Logoff(ULONG ulUIParam,ULONG ulFlags,ULONG ulReserved);

方法返回一个HRESULT对象,通过它可以判断调用是否成功。前两个参数的意义与MAPILogonEx中的同名参数相同,最后一个参数保留不用。对于短信操作来说,三个参数均可设置为NULL。

当成功Logoff以后,如果确信不再需要Session对象以后,可以通过Release方法释放对象。源代码如下:

if(NULL!=m_pSession) //释放Session

{

HRESULT hr=m_pSession->Logoff(NULL,NULL,NULL);

if(FAILED(hr))

{

//异常处理

}

m_pSession->Release();

m_pSession=NULL;

}

4. 短信(邮件)仓库接口IMsgStore

IMsgStore继承自IMAPIProp接口,而IMAPIProp接口又继承自IUnknow接口,这个接口中值得我们重点关注的函数有GetProps,OpenEntry两个函数,后面我们将会通过这两个函数获取具体信箱(Folder)对象。

5. 建立与短信仓库的连接

在实现连接以前,先来看一个很有意思的宏

#define SizedSPropTagArray(_ctag, _name)

struct _SPropTagArray_ ## _name

{

ULONG cValues;

ULONG aulPropTag[_ctag];

} _name

为什么说这个结构体有意思,仔细看一下就知道了,利用了一个带参数的宏,实现了动态声明结构体的功能,更奇妙的是,连结构体的名称都可以动态创建。大家别怪我顾洛寡闻,这种声明方式还真是不太常见。这个数据结构在Cemapi中扮演一个很重要的角色,通过定制的实现它,可以告诉函数,我希望获取或设置那些属性。看下面一段程序:

SizedSPropTagArray(2 , Columns) = {

2,

PR_ENTRYID, //Entry ID

PR_DISPLAY_NAME //Display Name

};

这段程序动态的声明了一个名为_SPropTagArray_Columns的结构体,并且声明了一个名为Columns的结构体变量。这个结构体中有一个ULONG类型的成员变量,和一个长度为2的ULONG类型的数组成员组成。结构体中cValues的值被初始化为2,aulPropTag[0]=PR_ENTRYID,aulProTag[1]=PR_DISPLAY_NAME。

这里面涉及到了两个常量符号,PR_ENTRYID和PR_DISPLAY_NAME,这两个符号分别表示对象ID和显示名称,这里所说的对象可以是短信(邮件)存储仓库,也可是具体信箱Folder,还可以是短消息本身,调用不同的函数,这些符号会被解释为具体对象的某些属性。

另外还有一个重要的接口需要说明,那就是IMAPITable,这个接口也从IUnKnow中继承。在WM系统中的短信(邮件)仓库、具体信箱Folder以及Folder中的短信都不是唯一的,在使用Cemapi中的接口方法获取这些对象的时候,将会采用表的形式返回结果,IMAPITable接口的作用就是用于描述这个表的结构。

有了这两个类型作为基础,我们就可以通过尝试获取WM系统中的短信(邮件)仓库列表了,前面提到了IMAPISession接口一个方法GetMsgStoresTable,从名字上应该就很直观的知道了这个方法的功能,即获取MsgStore(短信邮件仓库)列表。该方法定义为:

HRESULT IMAPISession::GetMsgStoresTable(ULONG ulFlags,LPMAPITABLE FAR * lppTable)

返回值依旧标志方法是否运行成功,不再赘述。参数中

ulFlags:表示字符编码类型,这里好像只有MAPI_UNICODE标志供选择。

lppTable:实际是一个IMAPITable **类型,该方法通过它返回MsgStore(短信邮件仓库)列表。

当GetMsgStoresTable方法成功获取了IMAPITable接口的对象以后,这时该对象里面的数据还是以原始的方式组织的,我们无法获取表中的记录,这时候就需要调用IMAPITable接口中的SetColumns方法来告诉IMAPTABLE对象,内部数据将以什么形式进行组织。该方法定义为:

HRESULT IMAPITable::SetColumns(LPSPropTagArray , ULONG);

返回值用于判断方法调用是否成功。参数说明:

LPSPropTagArray:用于说明IMAPITable中记录的组织形式,把前面提到过的Columns对象作为参数传入,则表示告诉IMAPITable对象,表格中每条记录有两列,第一列是对象ID(PR_ENTRYID),第二列是对象现实名称(PR_DISPLAY_NAME)。

ULONG:某种标志,一般设置为0,这里我没有找到相关资料,希望高手们补充。

有了表格,有了记录的结构,下一步要做什么应该很容易就能想到。Yes ,取表格中的所有记录,并且遍历这些记录,查找显示名称(PR_DISPLAY_NAME)为SMS的记录。这里再介绍一种数据结构SRowSet,其定义如下:

typedef struct _SRowSet

{

ULONG cRows; /* 行数 */

SRow aRow[MAPI_DIM]; /* 行记录具体信息 */

} SRowSet, FAR * LPSRowSet;

很有意思,MAPI_DIM的值为1,但是绝不是说所有从IMAPITable中取出的行记录都只有一列,恰恰相反,列的数量是由我们前面提到的动态结构体变量Columns中的cValues的值来决定的,这里请读者朋友们注意。SRow也为一个结构体,其定义如下:

typedef struct _SRow

{

ULONG ulAdrEntryPad;

ULONG cValues; /* 用于标志lpProps成员的数量 */

LPSPropValue lpProps; /* 属性结构体*/

} SRow, FAR * LPSRow;

lpProps成员所对应的结构体SPropValue才是行记录中真正的数据。其定义如下

typedef struct _SPropValue

{

ULONG ulPropTag; /*属性标志,常用于辅助判断属性值是否成功获取*/

ULONG dwAlignPad;

union _PV Value;

} SPropValue, FAR * LPSPropValue;

这个结构中Value成员非常有用,它由很多成员组成,每个成员对应着对象的一个属性,该联合体定义如下:

typedef union _PV

{

short int i; /* case PT_I2 */

LONG l; /* case PT_LONG */

ULONG ul; /* alias for PT_LONG */

float flt; /* case PT_R4 */

double dbl; /* case PT_DOUBLE */

unsigned short int b; /* case PT_BOOLEAN */

CURRENCY cur; /* case PT_CURRENCY */

double at; /* case PT_APPTIME */

FILETIME ft; /* case PT_SYSTIME */

LPSTR lpszA; /* case PT_STRING8 */

SBinary bin; /* case PT_BINARY */

LPWSTR lpszW; /* case PT_UNICODE */

LPGUID lpguid; /* case PT_CLSID */

LARGE_INTEGER li; /* case PT_I8 */

SShortArray MVi; /* case PT_MV_I2 */

SLongArray MVl; /* case PT_MV_LONG */

SRealArray MVflt; /* case PT_MV_R4 */

SDoubleArray MVdbl; /* case PT_MV_DOUBLE */

SCurrencyArray MVcur; /* case PT_MV_CURRENCY */

SAppTimeArray MVat; /* case PT_MV_APPTIME */

SDateTimeArray MVft; /* case PT_MV_SYSTIME */

SBinaryArray MVbin; /* case PT_MV_BINARY */

SLPSTRArray MVszA; /* case PT_MV_STRING8 */

SWStringArray MVszW; /* case PT_MV_UNICODE */

SGuidArray MVguid; /* case PT_MV_CLSID */

SLargeIntegerArray MVli; /* case PT_MV_I8 */

SCODE err; /* case PT_ERROR */

LONG x; /* case PT_NULL, PT_OBJECT (no usable value) */

} __UPV;

看到这么成员是不是眼有些花呀?我认为这些成员不必全部了解,因为我们不必像想孔乙己那样,知道茴香豆的茴字怎么写,还要知道有几种写法(当然您也可以不这么认为)。其实我们只需要知道ft,lpszA,lpszW以及bin这四个成员就可以了,他们分别代表发送(接收)时间,显示名称或消息标题或正文或发送号码或接受号码等字符串(ASCII),显示名称或消息标题或正文或发送号码或接受号码等字符串(UNICODE)以及对象的EntryID(对象可以是短信邮件仓库,可以是具体信箱Folder也可以是某条短信)。在这一小节中,我们只用到了lpszW和bin。SBinary也为一个结构体对象,它用来唯一标示某一对象的ID,其定义如下:

typedef struct _SBinary

{

ULONG cb;

LPBYTE lpb;

} SBinary, FAR *LPSBinary;

这里面的两个成员含义不必深究,我们只需要知道,这两个成员所组成的结构体对象SBinary可以作为唯一标示对象的ID,(对象依旧可以是短信邮件仓库,可以是具体信箱Folder也可以是某条短信,在这一小节中它表示短信邮箱仓库对象的ID。

IMAPITable中提供了QueryRows方法来获取行记录,其定义如下:

HRESULT IMAPITable::QueryRows(LONG,ULONG,SRowSet **);

返回值用于判断方法调用是否成功,这里要注意,如果取不到任何行记录的时候也会返回失败,因此可以用于判断行记录是否已经遍历完毕。参数说明:

LONG:希望获取多少行记录。

ULONG:标志可以是如下定义的符号之一,很抱歉,具体每种标志代表什么含义,并没有资料特别的说明,有兴趣的朋友可以研究一下。再短信应用中,这个值一般会设置为0。

#define TBL_LEAF_ROW ((ULONG) 1)

#define TBL_EMPTY_CATEGORY ((ULONG) 2)

#define TBL_EXPANDED_CATEGORY ((ULONG) 3)

#define TBL_COLLAPSED_CATEGORY ((ULONG) 4)

SRowSet **:这个参数用于返回查找到的行记录。

每次QueryRows成功执行以后,IMAPITable中的游标会自动移动第一个参数LONG行记录,直到遍历完毕为止。

现在我们已经获取短信邮件系统中的所有短信邮件仓库了,下面要做的就是找到显示名称为SMS的那个MsgStore仓库,并获去指向该仓库的对象指针。还记得Columns这个动态结构体变量吗?我们通过SetColumns方法给行记录定义了两列,第一列为对象ID(PR_ENTRYID),第二列为显示名称(PR_DISPLAY_NAME),那么每一个SRowSet对象中就会有两个SPropValue结构体对象,第一个就代表PR_ENTRYID,第二个则代表PR_DISPLAY_NAME,第一个SPropValue中的Value联合体中的bin成员有效,而第二个SPropValue中的Value联合体中的lpszW成员有效。如果我们使用QueryRows方法获取到的SRowSet *对象为m_pRows,则下面代码则表示上述说明内容。

m_pRows->aRow[0].lpProps[0].Value.bin为PR_ENTRYID

m_pRows->aRow[0].lpProps[1].Value.lpszW为PR_DISPLAY_NAME

有了对象ID,我们就可以通过IMAPISession中的OpenEntry方法获取短信仓库对象IMsgStore了。OpenEntry方法定义为:

HRESULT IMAPISession::OpenEntry(ULONG,LPENTRYID,LPCIID,ULONG,ULONG*,LPUNKNOW*);

返回值说明了方法调用是否成功,参数说明如下:

ULONG:短信邮件仓库的EntryId,也即对应的SBinary结构中的cb成员

LPENTRYID:短信邮件仓库的EntryId指针,也即对应的SBinary结构中的lpb成员

LPCIID:本质是一个指向GUID结构体变量的指针,若想更深入的了解GUID结构体请参考COM相关资料,这里只给出定义:

typedef struct _GUID { // size is 16

DWORD Data1;

WORD Data2;

WORD Data3;

BYTE Data4[8];

} GUID;

ULONG:访问标志,cemapi中只支持最优访问方式,MAPI_BEST_ACCESS

ULONG*:用于返回Message类型

LPUNKNOW *:一个指向IUnKnow或其派生类指针的指针,用于返回派生自IUnknow接口的对象,这里是IMsgStore对象。

OK,相关的内容基本上已经介绍完了,说了很多,估计您已经看的云里雾里了,还是用一段完整程序来给上面的内容做一个总结吧。

IMAPITable *m_pTable = NULL;

HRESULT hr = 0;

SRowSet *m_pRows = NULL;

SizedSPropTagArray(2 , Columns) =

{

2 ,

PR_ENTRYID, //

PR_DISPLAY_NAME //Display Name

};

if(NULL==m_pSession)

{

//异常处理

}

hr=m_pSession->GetMsgStoresTable(MAPI_UNICODE , &m_pTable); //获取IMAPITable对象

if(FAILED(hr) || NULL==m_pTable)

{

//没有取到表结构或取表结构时出错

}

hr=m_pTable->SetColumns((LPSPropTagArray)&Columns, 0); //设置行记录结构

if(FAILED(hr))

{

//异常处理

}

while(SUCCEEDED(m_pTable->QueryRows(1, 0, &m_pRows))) //循环遍历所有行记录

{

if (NULL == m_pRows || m_pRows->cRows != 1)

{

break;

}

//查找显示名字为SMS的行记录

if (_tcsicmp(m_pRows->aRow[0].lpProps[1].Value.lpszW, _T(“SMS”)) == 0)

{

ULONG ulMsgType;

//则获取指向短信仓库的对象

hr=m_pSession->OpenEntry(m_pRows->aRow[0].lpProps[0].Value.bin.cb,

(LPENTRYID)m_pRows->aRow[0].lpProps[0].Value.bin.lpb,

NULL,

MAPI_BEST_ACCESS,

&ulMsgType,

(LPUNKNOWN*)&m_pMsgStore);

if(FAILED(hr) || NULL==m_pMsgStore)

{

//异常处理

}

break;

}

FreeProws(m_pRows); //释放

m_pRows = NULL;

}

if(m_pRows) //释放资源

{

FreeProws(m_pRows);

m_pRows = NULL;

}

6. 释放IMsgStore对象

IMsgStore接口提供了Release方法释放对象资源,调用方式如下:

if(NULL!=m_pMsgStore)

{

m_pMsgStore->Release();

}

7. 与某一具体信箱建立连接,获取具体信箱接口IMAPIFolder对象

获取具体信箱IMAPIFolder对象要比获取IMsgStore对象容易很多,因为在短信仓库MsgStore下,只有收件箱,发件箱,草稿箱,废件箱,已发送邮件箱5种具体信箱(Folder),我们可以通过指定要获取的信箱(Folder)类型来直接获取指向该具体信箱的IMAPIFolder对象。

首先,依旧需要建立一个动态的SPropTagArray结构体变量,用于告诉短信仓库IMsgStore对象,我们需要获取哪一个具体信箱的IMAPIFolder对象,代码如下:

SizedSPropTagArray(1, Columns) =

{

1,

PR_CE_IPM_INBOX_ENTRYID /*表示要获取指向系统收件箱的IMAPIFolder对象*/

};

用于表示具体信箱(Folder)的标志:

PR_CE_IPM_INBOX_ENTRYID:系统收件箱

PR_CE_IPM_OUTBOX_ENTRYID:系统发件箱

PR_CE_IPM_DRAFTS_ENTRYID:草稿箱

PR_IPM_SENTMAIL_ENTRYID:已发邮件箱

PR_IPM_WASTEBASKET_ENTRYID):废件箱

然后我们需要用一个新的方法GetProps来获取具体信箱(Folder)的属性信息,其实我们的主要目的是获取属性中该具体信箱的EntryID。该方法被定义为:

HRESULT IMsgStore::GetProps(SPropTagArray *,ULONG,ULONG *,SPropTagArray**);

方法返回值标志方法是否执行成功。参数说明:

SPropTagArray * :利用前面动态结构体对象Columns,告诉IMsgStore对象,我需要取哪个具体信箱的属性。

ULONG:指明当前的编码方式,MAPI_UNICODE

SPropTagArray**:用于返回从具体信箱中获取的属性

最后用IMsgStore对象的OpenEntry方法建立获取指向具体信箱的IMAPIFolder接口对象。该方法的定义与IMAPISession中的同名对象相同,这里不再赘述。

获取指向具体信箱的IMAPIFolder接口对象的源程序如下:

HRESULT hr=0;

LPSPropValue stProps = NULL;

ULONG ulValues = 0;

SizedSPropTagArray(1, Columns) =

{

1,

PR_CE_IPM_INBOX_ENTRYID /*表示要获取指向系统收件箱的IMAPIFolder对象*/

};

// 获取Folder的Entry ID,然后通过OpenEntry获得对象

m_pMsgStore->GetProps((LPSPropTagArray) &Columns, MAPI_UNICODE, &ulValues, &stProps);

hr=m_pMsgStore->OpenEntry(stProps[0].Value.bin.cb, (LPENTRYID)stProps[0].Value.bin.lpb, NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)&m_pFolder );

if(FAILED(hr) || NULL==m_pFolder)

{

//异常处理

}

MAPIFreeBuffer(stProps); //释放掉对象

8. 释放掉Folder对象

If(NULL!=m_pFoder)

{

m_pFolder->Release();

}

9. 本节所涉及到的源程序

//获取IMAPISession会话对象

void Session()

{

IMAPISession *m_pSession=NULL;

hr=MAPILogonEx(NULL,NULL,NULL,NULL,&m_pSession);

if(FAILED(hr) || NULL==m_pSession)

{

//异常处理

}

}

//获取指向短信仓库的IMsgStroe接口对象

void MsgStore()

{

IMAPITable *m_pTable = NULL;

HRESULT hr = 0;

SRowSet *m_pRows = NULL;

SizedSPropTagArray(2 , Columns) =

{

2 ,

PR_ENTRYID, //

PR_DISPLAY_NAME //Display Name

};

if(NULL==m_pSession)

{

//异常处理

}

hr=m_pSession->GetMsgStoresTable(MAPI_UNICODE , &m_pTable); //获取IMAPITable对象

if(FAILED(hr) || NULL==m_pTable)

{

//没有取到表结构或取表结构时出错

}

hr=m_pTable->SetColumns((LPSPropTagArray)&Columns, 0); //设置行记录结构

if(FAILED(hr))

{

//异常处理

}

while(SUCCEEDED(m_pTable->QueryRows(1, 0, &m_pRows))) //循环遍历所有行记录

{

if (NULL == m_pRows || m_pRows->cRows != 1)

{

break;

}

//查找显示名字为SMS的行记录

if (_tcsicmp(m_pRows->aRow[0].lpProps[1].Value.lpszW, _T(“SMS”)) == 0)

{

ULONG ulMsgType;

//则获取指向短信仓库的对象

hr=m_pSession->OpenEntry(m_pRows->aRow[0].lpProps[0].Value.bin.cb,

(LPENTRYID)m_pRows->aRow[0].lpProps[0].Value.bin.lpb,

NULL,

MAPI_BEST_ACCESS,

&ulMsgType,

(LPUNKNOWN*)&m_pMsgStore);

if(FAILED(hr) || NULL==m_pMsgStore)

{

//异常处理

}

break;

}

FreeProws(m_pRows); //释放

m_pRows = NULL;

}

if(m_pRows) //释放资源

{

FreeProws(m_pRows);

m_pRows = NULL;

}

}

//获取指向具体信箱的IMAPIFolder接口对象

void Folder(ULONG ulType)

{

HRESULT hr=0;

LPSPropValue stProps = NULL;

ULONG ulValues = 0;

ULONG ulTags[] = { 1, ulType};

// 获取Folder的Entry ID,然后通过OpenEntry获得对象

m_pMsgStore->GetProps((LPSPropTagArray) ulTags, MAPI_UNICODE, &ulValues, &stProps);

hr=m_pMsgStore->OpenEntry(stProps[0].Value.bin.cb, (LPENTRYID)stProps[0].Value.bin.lpb, NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)&m_pFolder );

if(FAILED(hr) || NULL==m_pFolder)

{

throw(CMsgException(_T(“获取FOLDER失败!”),_T(“CMsgControl->Folder”),ERR_GET_FOLDER));

}

MAPIFreeBuffer(stProps); //释放掉对象

}

//释放掉IMAPISession、IMsgStore、IMAPIFolder对象

void UnInit()

{

if(NULL!=m_pSession) //释放Session

{

m_pSession->Logoff(NULL,NULL,NULL);

m_pSession->Release();

m_pSession=NULL;

}

if(NULL!=m_pMsgStore) //释放MsgStore

{

m_pMsgStore->Release();

m_pMsgStore=NULL;

}

if(NULL!=m_pFolder) //释放Folder

{

m_pFolder->Release();

m_pMsgStore=NULL;

}

}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/155466.html原文链接:https://javaforall.cn

0 人点赞