不迎不送,来去自便,无茶无酒,谈笑随缘

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

CEMAPI实战攻略(三)——操作信箱中的短信息(下)

By 吴春雷

QQ:819543772

Email:wuchunlei@163.com 

6.       解析原始短消息        

当成功获取原始信息以后,还不能从中直接获得短信正文等我们想要的内容,要想得到这些内容,还需要对原始信息作一些操作。还记得我们前面提过的短消息的组成结构吗?下面的内容从原始短信中获取每个结构中的内容。

a)               获取正文

前面提到了Subjectbody的关系,在发送短信的时候,Subject的内容后面加上一个\n,在加上body中的内容作为一条短信的正文。但奇怪的是,使用cemapi获取短信正文的时候不能使用PR_BODY属性,而需要获得PR_SUBJECT属性,这一点请读者注意。

然后就是老套路了,要想获得对象中某种属性的内容,首先需要建立一个动态结构体对象,这次我们只需要获取原始信息中PR_SUBJECT属性的内容,因此结构体的第一个ULONG参数应该为1,第二个参数ULONG*数组元素数应该也为1,值为PR_SUBJECT。然后利用IMessage::GetProps方法来获取PR_SUBJECT属性。非常简单,程序列表如下:

//取短信内容

        HRESULT hr;

        ULONG cValues = 0;

        SPropValue *pspvSubject= NULL;

        CString strSubject(_T(""));

        SizedSPropTagArray(1, sptaSubject) = { 1, PR_SUBJECT};

       

        LPMESSAGE m_pMsg=NULL;

 

        m_pMsg=……     //取消息指针,方法见上文

 

        ASSERT(m_pMsg!=NULL);

       

        hr = m_pMsg->GetProps((SPropTagArray *) & sptaSubject, MAPI_UNICODE, &cValues, & pspvSubject);         //从原始消息中提取属性

 

        if(FAILED(hr))

        {

            //错误处理

        }

       

        if(pspvSubject ->ulPropTag!=10) //每次返回10的时候程序都会抛出异常,这里假设为错误代码

        {

            strSubject= pspvSubject ->Value.lpszW;          //消息正文

        }

        else {strSubject=_T("");}

pspvSubject中的ulPropTag的值等于10的时候,系统都会跑出一个不知道是什么类型的异常,我想了各种办法也没法捕获它。所以只好在获取消息以后判断一下ulPropTag属性。如果有朋友了解原因,希望能给我发个邮件告诉我。

b)               获取发送方电话号码

发送方电话号码对应标志PR_SENDER_EMAIL_ADDRESS。获取方式与获取短信正文相同,源代码如下:

        //取发信者号码

        HRESULT hr;

        ULONG cValues = 0;

        SPropValue *pspvFromTel = NULL;    

        LPMESSAGE m_pMsg=NULL;

        CString strFromTel(_T(""));

 

        m_pMsg=……;        //取消息指针

        ASSERTm_pMsg;

 

        SizedSPropTagArray(1, sptaFromTel) = { 1, PR_SENDER_EMAIL_ADDRESS};

        hr = m_pMsg->GetProps((SPropTagArray *) &sptaFromTel, MAPI_UNICODE, &cValues, & pspvFromTel);

       

        if(FAILED(hr))

        {

            //异常处理

        }

       

        if(203358239==pspvFromTel->ulPropTag){      //203358239为该项存储在的标志?

            strFromTel= pspvFromTel ->Value.lpszW;

        }

        else strFromTel=_T("");

同样的尴尬,如果pspvEmail->ulPropTag的值不为203358239的时候,系统会抛出一个怎么也抓不到的异常,所以这里只能先对该成员的值进行判断。

c)               获取接收方电话号码

这里稍稍有些麻烦,但是也不用担心,因为使用到的方法和结构体只有一个前面没见到过的,那就是ADRLIST结构体,该结构体定义如下:

typedef struct _ADRLIST

{

    ULONG           cEntries;

    ADRENTRY        aEntries[MAPI_DIM];

} ADRLIST, FAR * LPADRLIST;

 

typedef struct _ADRENTRY

{

    ULONG           ulReserved1;    /* Never used */

    ULONG           cValues;

    LPSPropValue    rgPropVals;

} ADRENTRY, FAR * LPADRENTRY;

MAPI_DIM的值为1。这个定义是不是有些眼熟?回忆一下结构体SRowSet的定义,是不是除了成员命名不一样以外,其它的东西都是相同的?那是不是预示着ADRLIST这个结构体与SRowSet有相近的功能呢?恭喜您,您的猜测是正确的。这个结构体也用于在IMAPITable::QueryRows方法中返回行记录,只不过在使用ADRLIST之前,无需我们再使用IMAPITable::SetColumns来为表格设置行记录的格式。ADRLIST所代表的内容是联系人列表的结构体。

短信的联系人不止一位,因此很容易想到联系人会是一个列表,在Cemapi中,想到了表自然就想到了IMAPITable接口对象,这里有一点小小的不同,我们将使用IMAPITable::GetRecipientTable方法来直接获取联系人列表,而不需要构建动态结构体变量,方法的定义如下:

HRESULT IMAPITable::GetRecipientTable(ULONG,IMAPITable**);

返回用来判断方法是否正确执行。参数列表:

ULONG:某种标志,mapidefs.h中并没有对这个标志的定义,短信应用中一般取0

IMAPITable**:该参数用于返回联系人列表。

 

当成功获取联系人列表之后,我们就可以依次遍历每个联系的所有属性,如果存在PR_EMAIL_ADDRESS属性则这个联系人就是我们要获取的。代码如下:

IMAPITable* m_pTable = NULL;

        CString strToTel(_T(""));

        ADRLIST * pstToRows=NULL;  

        HRESULT hr;

        LPMESSAGE m_pMsg=NULL;

 

        m_pMsg=……;        //取消息指针

 

        //获取联系人信息列表

        m_pMsg->GetRecipientTable( NULL, &m_pTable );

 

         if(NULL==m_pTable)

         {

             //没有接收人信息,可能是InBox中的短信,直接退出

         }

 

        

 

         //获取每个联系人信息

         while(!FAILED(hr = pTable->QueryRows(1, 0, (LPSRowSet*)& pstToRows)))

         {

             if(pstToRows ->cEntries == 0 )     //没有联系人

                  break;

     

 

             for(int n = 0; n < pstToRows ->cEntries; n++ )

             {

                  //遍历每个联系人属性

                  for(int i = 0; i < pstToRows ->aEntries[n].cValues ; i++)

                  {

                       //判断如果是PR_EMAIL_ADDRESS属性,那么就找到了联系人地址

                       if( PR_EMAIL_ADDRESS == pstToRows ->aEntries[n].rgPropVals[i].ulPropTag )

                       {

                           //联系人地址

                          

                       }

                  }

             }

 

             

             MAPIFreeBuffer(pstToRows);

 

         }

d)               获取发送/接收/创建时间

根据短信类型的不同,使用PR_MESSAGE_DELIVERY_TIME属性可以获取短信的发送/接收/创建时间。还是老方法,创建动态数组SPropTagArray,利用GetProps方法获取消息的PR_MESSAGE_DELIVERY_TIME属性所对应的值。然后从_PV联合体的ft成员获取UTC文件时间,然后采用系统API FileTimeToLocalFileTime函数将UTC时间转换为本地文件时间,最后用FileTmieToSystemTime函数将本地文件时间转换为系统时间。源代码如下:

        HRESULT hr;

        ULONG cValues = 0;

        SPropValue *pspvTime = NULL;   

 

        LPMESSAGE m_pMsg=NULL;

 

        m_pMsg=……;        //取消息指针

 

        SizedSPropTagArray(1, sptaTime) = { 1, PR_MESSAGE_DELIVERY_TIME }; //建立动态结构体

 

 

        hr =m_pMsg->GetProps((SPropTagArray *) &sptaTime, MAPI_UNICODE, &cValues, & pspvTime);

 

        if(FAILED(hr))

        {

            //错误处理

        }

 

        if(pspvTime ->ulPropTag!=10){       //等于时通常会抛出异常,因此猜测等于的时候读取失败

            //格式化时间

            FILETIME ft;

            SYSTEMTIME stTime;

            FileTimeToLocalFileTime(&pspvTime ->Value.ft,&ft);

            FileTimeToSystemTime(&ft,&stTime);

           

        }

7.       获取短消息ID

         短消息ID(ENTRYID)是唯一标志一条短消息的标识,通过它我们可以找到这条消息在具体信箱中的位置,进而对这条消息进行删除、移动等操作。获取ENTRYID的方式有两种,一种是前面讲到过的遍历FolderIMAPITable对象的行记录时获得的。例如:

m_pRows->aRow[0].lpProps[0].Value.bin.cb

            m_pRows->aRow[0].lpProps[0].Value.bin.lpb

    另一种方式是使用GetProps方法直接获取消息中PR_ENTRYID属性所对应的值,方法如下:

 

        //获取EntryID

        ULONG cValues = 0;

        SPropValue *pspvID= NULL;

        SizedSPropTagArray(1, sptaID ) = { 1, PR_ENTRYID};

        hr = m_pMsg->GetProps((SPropTagArray *) &sptaID, MAPI_UNICODE, &cValues, &pspvID);

 

pspvID->Value.bin就是需要的ENTRYID

 

8.       在具体信箱中建立一条短消息

在建立一条消息前,很自然的会想到,我需要在哪个具体信箱中建立该消息,因此建立消息的第一步就是获取指向具体信箱(Folder)的对象指针,获取IMAPIFolder指针的方法见前面的内容。假设我们已经获取了Folder的指针对象m_pFolder,下面的步骤能够让我们在该信箱中建立一条新消息。

a)               建立一条空消息

使用IMAPIFolder::CreateMessage方法可以在具体信箱中建立一条空消息。该方法定义如下:

HRESULT IMAPIFolder::CreateMessage(LPCIID,ULONG,IMessage **)

返回值用来标识方法是否成功执行。参数列表:

LPCIIDGUID结构的指针,直接设置为NULL即可。

ULONG:某种未知的标志,希望大家补充,直接设置为0可以正常工作。

IMessage **:用于返回刚刚建立的消息对象指针。

b)               添加联系人

上一步建立的消息中没有任何内容,现在来为其加入接收人信息。首先要做的是建立接收人对应的结构体ADRLISTADRENETRY中的SPropValue属性,该属性用于指定接收人类型,对方地址的类型(还有可能是邮件哦,别忘了CEMAPI不止是用来读取短信的),接收人地址等属性,我们主要设置这三个属性。代码如下:

SPropValue propRecipient[3];

        ZeroMemory(&propRecipient, sizeof(propRecipient));

        propRecipient[0].ulPropTag = PR_RECIPIENT_TYPE;     //接收人类型

        propRecipient[0].Value.l = MAPI_TO;                

        propRecipient[1].ulPropTag = PR_ADDRTYPE;               //地址类型

        propRecipient[1].Value.lpszW = _T("SMS");               //短信

        propRecipient[2].ulPropTag = PR_EMAIL_ADDRESS;          //地址

propRecipient[2].Value.lpszW = (LPWSTR)CString(_T(15011056698));  //设置接收者号码

  然后,将属性添加到联系人结构体ADDLIST对象中,并使用IMessage::ModifyRecipient方法将该对象设置到消息中去。该方法定义为:

HRESULT IMessage::ModifyRecipient(ULONG,ADDLIST *);

返回值标志方法是否正确运行,参数列表:

ULONG:操作类型,MODRECIP_ADD为添加联系人信息,MODRECIP_MODIFY为修改联系人信息,MODRECIP_REMOVE为删除联系人信息。标志定义如下:

#define MODRECIP_ADD            ((ULONG) 0x00000002)

#define MODRECIP_MODIFY         ((ULONG) 0x00000004)

#define MODRECIP_REMOVE         ((ULONG) 0x00000008)

         ADDLIST *:用于将要修改的联系人信息传递给方法。

              设置联系人的代码如下:

ADRLIST adrlist;

        adrlist.cEntries = 1;

        adrlist.aEntries[0].cValues = 3;            \\表示设置了三个SPropValue属性

        adrlist.aEntries[0].rgPropVals = (LPSPropValue)(&propRecipient);

        hr = m_pMsg->ModifyRecipients(MODRECIP_ADD, &adrlist);

 

        if (FAILED(hr))

        {

            //异常处理

        }

c)               添加正文,发送方号码和创建时间等属性

为了能够使短信能够支持中文,在为短信对象添加正文时要指定正文的编码方式,用以下代码可以获取短信的UNICODE属性

        MAPINAMEID idName;   

        ZeroMemory(&idName, sizeof(MAPINAMEID));   

        GUID PS_MAPI={0x00020328,0,0,0xC0,0,0,0,0,0,0,0x46}; 

        idName.lpguid = (LPGUID)&PS_MAPI;   

        idName.ulKind = MNID_STRING;   

        idName.Kind.lpwstrName = L"SMS:Unicode";   

        LPMAPINAMEID pidName = &idName;   

        LPSPropTagArray pPropTag = NULL;   

        hr = m_pMsg->GetIDsFromNames(1, &pidName, MAPI_CREATE, &pPropTag);

这段代码是我从微软的网站上拷贝下来的,国内也有很多人在用,但却没有过多的解释。由于参考资料有限,我只能根据猜测来尝试着解释一下这段代码,如果某位达人发现我的解释有问题,希望赶紧与我联系,以便尽快修改,不要误导读者。

这里有一个命名属性集(Named properties )或叫属性识别码的概念,它是一组由GUID和名字(name)组成的属性集合,注册在MAPI系统中,用来唯一标志系统中使用到的某一个属性及其相关的详细信息,比如UNICODE属性。GUID和名字组合成为命名属性集的NAME,而命名属性集还有一个ID用为唯一标明该属性。当我们得到命名属性集的NAMEID以后,我们可以通过GetIDsFromNamesGetNamesFromIDs获取未知的那个对象。方法IMessage::GetIDsFromNames的原型定义为:

HRESULT IMessage::GetIDsFromNames(LPCIID,ULONG,SPropTagArrray **);

方法返回值标志了方法是否正确执行。参数列表:

LPCIIDGUID结构体,为什么这里要设置成1,不详。

ULONG:某种标志。可以取MAPI_CREATESTREAM_APPEND,意义不详,这里取MAPI_CREATE

SPropTagArray**:用于返回GUIDname对应的命名属性集中的属性列表

获得了命名属性集中的属性列表以后,我们就可以开始设置短信的发送号码,正文以及创建时间了。

设置方法没有离开我们前面所讨论的内容,这里直接给出代码,然后在对代码做简要的解释。

//设置短信属性

        SPropValue props[8];

        ZeroMemory(&props, sizeof(props));

        props[0].ulPropTag = PR_MESSAGE_CLASS;      // (1)设置显示窗体类型为短信

        props[0].Value.lpszW = TEXT("IPM.SMStext");

        props[1].ulPropTag = PR_SUBJECT;                // (2)设置正文

        props[1].Value.lpszW = (LPWSTR)strBody.GetBuffer(); 

        props[2].ulPropTag = PR_SENDER_EMAIL_ADDRESS;

        props[2].Value.lpszW = (LPWSTR)strFrom.GetBuffer();  // (3)设置发送号码

        props[3].ulPropTag = PR_MSG_STATUS;             //(4)标志设置信息类型

        props[3].Value.ul = MSGSTATUS_RECTYPE_SMS;      //(5)设置具体类型

        props[4].ulPropTag = PR_MESSAGE_FLAGS;          //(6)标志设置发送属性

        props[4].Value.ul = MSGFLAG_FROMME | MSGFLAG_UNSENT;    //(7)设置具体发送属性

        props[5].ulPropTag = CHANGE_PROP_TYPE(pPropTag[0].aulPropTag[0], PT_BOOLEAN);    //设置UNICODE属性,前面用GetIDsFromNames方法获取的

        props[5].Value.b = TRUE;   

 

        //设置日期

        SYSTEMTIME st;

        FILETIME ft;

        GetSystem   Time(&st);

        SystemTimeToFileTime(&st,&ft);

        props[6].ulPropTag = PR_MESSAGE_DELIVERY_TIME;    //(8)设置建立时间

        props[6].Value.ft=ft;

        props[6].Value.b = TRUE;   

 

hr = m_pMsg->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);//(9)设置属性

        if (FAILED(hr))

        {

            //异常处理

           

        }

 

我在代码中关键的位置打了标志,下面我们一条一条去看。

(1)  注意 PR_MESSAGE_CLASS标志告诉系统我要创建的是短信,由于后面要设置PR_SUBJECT属性,如果不设置这个标志,那么我们应该写到短信正文中的内容就会被写进主题中。

(2)  注意设置短信的正文要用PR_SUBJECT属性,而不是PR_BODY属性

(3)  设置发送号码,它将标志该条短信来自那里。

(4)  PR_MSG_STATUS标示告诉系统,当前这个SPropValue将被用于设置信息类型。

(5)  MSGSTATUS_RECTYPE_SMS标识告诉系统,现在是为短信填写属性,这里还有一个可选项MSGSTATUS_RECTYPE_SMTP,用于标志邮件。本文中不用。

(6)  PR_MESSAGE_FLAGS标识告诉系统,当前这个SPropValue将被用于设置信息发送属性

(7)  MSGFLAG_FROMME | MSGFLAG_UNSENT :分别标志这个短信来自本地并且从未发送。与之相关的标识还有:#define MSGFLAG_READ            ((ULONG) 0x00000001)         //已读

#define MSGFLAG_UNMODIFIED      ((ULONG) 0x00000002)        //未读

#define MSGFLAG_SUBMIT          ((ULONG) 0x00000004)        //已提交

#define MSGFLAG_UNSENT          ((ULONG) 0x00000008)        //未发送

#define MSGFLAG_HASATTACH       ((ULONG) 0x00000010)        //有附件

#define MSGFLAG_FROMME          ((ULONG) 0x00000020)        //来自本地

#define MSGFLAG_ASSOCIATED      ((ULONG) 0x00000040)        //不明

#define MSGFLAG_RESEND          ((ULONG) 0x00000080)        //重发

#define MSGFLAG_RN_PENDING      ((ULONG) 0x00000100)        //不明

#define MSGFLAG_NRN_PENDING     ((ULONG) 0x00000200)      //不明

(8)  设置建立时间,这里注意要把系统时间转换为UFC文件时间。

(9)  SetProps方法用于给消息设置属性,其原型为

HRESULT SetPropsULONG cValues, SPropValue *lpPropArray, SPropProblemArray FAR ** lppProblems)

返回值表示方法是否执行成功。参数列表:

cValues:表示lpPropArray数组中元素的个数。

lpPropArray:要设置的属性列表

lppProblems:字面上理解是为了返回某些异常?没有找到相关资料,大家来补充吧。

d)               最后别忘了SaveChange

   做完上述操作以后,短信还不能显示在具体邮箱中,最后需要调用一下IMessage::SaveChange方法确认修改,当该方法成功调用后,新的短信将会显示在具体信箱中。

e)               为什么我发送的短消息内容写在了主题上而正文却是空的

注意(c)中的第(1)条:“PR_MESSAGE_CLASS标志告诉系统我要创建的是短信,由于后面要设置PR_SUBJECT属性,如果不设置这个标志,那么我们应该写到短信正文中的内容就会被写进主题中。”单独列出个标题来强调一下,否则使用WM2003以前版本的朋友会很困惑。

9.       从具体信箱中删除一条短消息

删除一条短信就显得容易多了,还记得我们获取的ENTRYID吗?利用这个ID设置一个名为ENTRYLIST的结构体,然后直接调用IMAPIFolder::DeleteMessages方法就可以了。ENTRYLIST结构体定义:

typedef struct _SBinaryArray

{

    ULONG       cValues;

    SBinary     FAR *lpbin;

} SBinaryArray;

typedef SBinaryArray ENTRYLIST, FAR *LPENTRYLIST;

很明显,这是一个ENTRYLIST就是SBinary的列表,而SBinary用于保存短信的ENTRYIDENTRYLIST中可以设置一组SBinary对象,就意味着DeleteMessages可以直接删除一组短消息(当然也可以删除一条)。这里只给出DeleteMessages的调用方式,参数的含义就不解释了(因为我找不到资料L)。删除短信的源代码如下:

  void CMsgControl::DeleteMsg(SBinary bin)

  {

      HRESULT hr=0;

 

      //删除短消息

      ENTRYLIST stEntryList;

      stEntryList.cValues=1;            //删除1条短信

      stEntryList.lpbin=new SBinary[1];    //这里注意要分配空间

      stEntryList.lpbin[0].cb=bin.cb;

      stEntryList.lpbin[0].lpb=bin.lpb;

      hr=m_pFolder->DeleteMessages(&stEntryList,0,NULL,0);       

 

      delete [] stEntryList.lpbin;        //释放内存控件

      stEntryList.lpbin=NULL;

  }

10.       将短消息移动到某个具体的信箱

有了删除短信和创建短信的方法以后,聪明的您该能够想到如何移动短信了吧?在源位置获取短信——在目的位置创建新短信——删除原位置的短信。哈哈,就这么简单,只要注意事务性就OK了,其它的没有什么可说的。

11.     InBox中的信息标记为已读或未读

前面已经提到过如何设置短信的状态了,设置短信状态只需要将PR_MESSAGE_FLAGS属性设置为MSGFLAG_READ(已读)或MSGFLAG_UNMODIFIED(未读)就可以了。设置方法前面已经说过,不再赘述。源代码如下:

    //标记已读和未读标志

    void CMsgInBox::Mark(IMessage *m_pMsg,int nType)

    {

        ASSERT(m_pMsg!=NULL);

 

        SPropValue    props[1];

 

        if(0==nType){   //标记为已读

            props[0].ulPropTag = PR_MESSAGE_FLAGS;

            props[0].Value.ul = MSGFLAG_READ;

            m_pMsg->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);

            m_pMsg->SaveChanges(0); //保存改变

        }

        else if(1==nType)       //标记为未读

        {

            props[0].ulPropTag = PR_MESSAGE_FLAGS;

            props[0].Value.ul = MSGFLAG_UNMODIFIED;

            m_pMsg->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);

            m_pMsg->SaveChanges(0); //保存改变

        }

        else

        {

            throw(CMsgException(_T("未知的标记值"),_T("CMsgInBox->Mark"),ERR_UNKNOW_FLAG));

        }

       

       

    }

12.   本节所涉及的源程序

比较多,暂时略。我会整理好提供下载的。

posted on 2009-03-10 17:43  wude  阅读(296)  评论(0编辑  收藏  举报