有时候,我们需要关心某条message的改变,需要做出及时的响应,我们当然不能主动的不断的QUERY MESSAGE的状态,好在系统提供了IMAPIAdviseSink,通过它我们可以获得Message移动、改变以及删除等等通知。
首先我们要做的是实现自己的IMAPIAdviseSink接口,原型是:
class CAdviseSink : public IMAPIAdviseSink
{
public:
CAdviseSink();
~CAdviseSink();
MAPIMETHOD_(ULONG,OnNotify)(ULONG cNotif, LPNOTIFICATION lpNotifications);
MAPIMETHOD(QueryInterface)(REFIID iid, void** ppvObject);
MAPIMETHOD_(ULONG, AddRef)();
MAPIMETHOD_(ULONG, Release)();
private:
ULONG m_cRef;
};
我们主要关注的是OnNotify,其他函数我们可以按照标准实现就可以了。我们先来看看OnNotify的一个简单的实现,关键地方我加了注释:
ULONG CAdviseSink::OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications)
{
// cNotif : 指定有多少个Notification通知
// lpNotifications : Notification数组,个数为cNotif
for(int i = 0; i < (int)cNotif; ++i)
{
//根据不同的Notification类型做不同的处理,类型有很多种,这里只是简单的列出的几种,要获取这些通知和注册AdviseSink密切相关,你需要告诉系统,你关心哪些方面的消息,比如消息的移动,删除等等,系统就会把这些相应的通知发给你,而其他你不关心的,就不会通知到你,这些我们会在后面注册部分讲到。
switch(lpNotifications[i].ulEventType)
{
case fnevObjectMoved:
break;
case fnevObjectModified:
break;
case fnevObjectDeleted:
break;
default:
break;
}
}
return 0;
}
接下来是注册AdviseSink,它与每个Account的Store相对应,比如SMS、OUTLOOK等等。以下是注册步骤:
A. 获取要监视的Message Store对象,从前面的文章里的我们已经知道如何获得指定的Message Store,这里我们拿SMS的Store来举例。
B. 创建我们自己的CAdviseSink对象
C. 调用IMsgStore::Advise注册
以下是注册示例代码:
IMsgStore* pMsgStore = …… //获取SMS Message Store
CAdviseSink* g_pAdviseSink = new CAdviseSink();
ULONG m_ulAdviseSink = 0; //用来标识AdviseSink,当取消注册时我们需要用到它。
// uEventMask : 它的作用是告诉系统,我们关心哪些方面的notification,没有列出来的事件在CAdviseSink::OnNotify里面就不会响应到。
ULONG uEventMask = fnevCriticalError | fnevNewMail | fnevObjectCreated | fnevObjectDeleted |
fnevObjectModified | fnevObjectMoved | fnevObjectCopied | fnevSearchComplete | fnevTableModified |
fnevStatusObjectModified | fnevReservedForMapi | fnevExtended;
pMsgStore->Advise(0, NULL, uEventMask, g_pAdviseSink, &m_ulAdviseSink);
这样就注册成功了。
以下是取消注册的示例代码:
if(m_ulAdviseSink)
{
pMsgStore->Unadvise(m_ulAdviseSink);
}
//记的释放对象
if(g_pAdviseSink)
{
delete g_pAdviseSink;
g_pAdviseSink = NULL;
}
(四)IMAPIAdviseSink的一个例子
AdviseSink对于我们了解系统SMS以及OUTLOOK的消息运作有很大帮助,我们可以挂接到SMS、OUTLOOK的Message Store上,看看在做某些操作时,系统到底对Message做了些什么。下面我举个一个例子来说明它的用途:
不知道大家有没有注意,在Smartphone上的Deleted Box里面有个按钮叫Restore,即恢复功能,如果是你用系统菜单把一条Message删除到Deleted Box的话,Restore可以正常工作,但是如果你用MAPI去操作呢?比如IMAPIFolder::CopyMessages,你可以试一下,这个时候Restore失效了,由此可见系统在用菜单删除Message的时候自己做了些手脚,那我们怎么知道它具体做了哪些事情呢?这个时候IMAPIAdviseSink就派上用场了,不过在这之前先简要介绍一下MAPI属性,我们可以看一下mapitags.h中关于属性的定义,非常明显,一个属性由类型和ID两个部分通过PROP_TAG宏作用生成。类型代表这个属性的值是以什么形式存放在数据库里面的,这关系到你能否正确读写该属性,比如我们在调用IMessage:: GetProps时候,返回了结构体SPropValue:
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 */
…….
} __UPV;
typedef struct _SPropValue
{
ULONG ulPropTag;
ULONG dwAlignPad;
union _PV Value;
} SPropValue, FAR * LPSPropValue;
看到union _PV了吗,我们如何知道该取哪个值来用呢?这就得通过属性的类型来告诉我们了,我们可以通过宏PROP_TYPE来获取一个属性的类型,通过PROP_ID来获取ID。
好了,下面是OnNotify实现:
ULONG CAdviseSink::OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications)
{
if(cNotif > 0 && NULL != lpNotifications)
{
for(int i = 0; i < (int)cNotif; ++i)
{
switch(lpNotifications[i].ulEventType)
{
//Message 发生了改变,这时lpNotifications[i].info.obj.lpPropTagArray就代表了发生改变的属性值列表。
case fnevObjectModified:
{
// iPropCount : 表示一共有多少个属性发生了改变
int iPropCount = lpNotifications[i].info.obj.lpPropTagArray->cValues;
for(int j = 0; j < iPropCount; j++)
{
// ulPropType : 属性类型,即PT_LONG,PT_DOUBLE,PT_BINARY等等,定义可以在mapidefs.h里面找到
// ulPropID : 属性ID,可以在mapitags.h里面找到
// 在此处打上断点或者打上日志,我们就可以知道在系统删除Message时,对它到底做了什么。
ULONG ulPropType = PROP_TYPE(lpNotifications[i].info.obj.lpPropTagArray->aulPropTag[j]);
ULONG ulPropID = PROP_ID(lpNotifications[i].info.obj.lpPropTagArray->aulPropTag[j]); }
break;
}
default:
break;
}
}
}
return 0;
}
按照上一篇介绍的方法,把它挂到SMS Store上,启动调试状态,等待Modified事件发生。然后到系统收件箱中创建一条新的SMS,并且删除它,前面新建时的通知我们不关心,当删除时OnNotify一共会收到3次通知消息,认真观察,第一次类型为3(PT_LONG),ID为13827(PR_CONTENT_UNREAD),第二次也一样,第三次呢,类型为258(PT_BINARY),ID为34072,恩?这并不是标准的属性,从SDK上看从0x8000到0xFFFE应该是用户自定义属性区域,可见这是MS在实现SMS程序时自己添加的一个属性,做什么用呢?类型是PT_BINARY,会不会是这条Message原始所在Box的EntryID呢?把它的数据打印出来和Draft Box的EntryID一比较,果然一模一样,这下明白了,MS在这里做了个手脚,在它自己的程序里面删除MESSAGE时,会添加一个自定义的属性,并把MESSAGE原始所在BOX的EntryID写进去,等到点Restore时,就读取这个值,把MESSAGE恢复回去,我们利用MAPI操作的MESSAGE没有置这个属性,当然不起作用了,所以我们所要做的只是在CopyMessage之前,把这个属性写上而已,这时再试一把,OK,搞定。
这只是一个简单的例子,AdviseSink对于平时对MAPI的调试很有帮助,从注册时的那一堆标志就可以看出,它不仅仅是用来监视Message而已,功能还是很强大的,不过我个人对其他功能倒没有很深入的研究,就不做介绍了,如果有朋友做过类似方面的研究,请告知我下,也让我学习一下。