jiangj

jiangj

0个粉丝

7

问答

0

专栏

0

资料

jiangj  发布于  2012-12-04 11:42:03
采纳率 0%
7个问答
2892

CEMAPI操作信箱中的短信息(下)

 
  1. 解析原始短消息

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

a) 获取正文

前面提到了Subject和body的关系,在发送短信的时候,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=……;        //取消息指针

    ASSERT(m_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); 

    }
  1. 获取短消息ID

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

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

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

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

a) 建立一条空消息

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

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

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

LPCIID:GUID结构的指针,直接设置为NULL即可。

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

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

b) 添加联系人

上一步建立的消息中没有任何内容,现在来为其加入接收人信息。首先要做的是建立接收人对应的结构体ADRLIST中ADRENETRY中的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用为唯一标明该属性。当我们得到命名属性集的NAME或ID以后,我们可以通过GetIDsFromNames和GetNamesFromIDs获取未知的那个对象。方法IMessage::GetIDsFromNames的原型定义为:

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

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

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

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

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

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

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

//设置短信属性

    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 SetProps(ULONG cValues, SPropValue *lpPropArray, SPropProblemArray FAR ** lppProblems)

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

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

lpPropArray:要设置的属性列表

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

d) 最后别忘了SaveChange

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

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

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

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

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

typedef struct _SBinaryArray

{

ULONG       cValues;

SBinary     FAR *lpbin;

} SBinaryArray;

typedef SBinaryArray ENTRYLIST, FAR *LPENTRYLIST;

很明显,这是一个ENTRYLIST就是SBinary的列表,而SBinary用于保存短信的ENTRYID。ENTRYLIST中可以设置一组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;

}

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

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

  1. 将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));

    }

}
  1. 本节所涉及的源程序

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

转自 http://blog.csdn.net/depraved_survival/archive/2009/03/10/3977178.aspx

我来回答
回答0个
时间排序
认可量排序
易百纳技术社区暂无数据
或将文件直接拖到这里
悬赏:
E币
网盘
* 网盘链接:
* 提取码:
悬赏:
E币

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
+ 添加网盘链接/附件

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
相关问答
无更多相似问答 去提问
举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

易百纳技术社区