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

CEMAPI实战攻略

by 吴春雷

QQ:819543772

Email:[email protected]

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

上一部分已经讨论过,如何搭建开发和测试环境,以及如何初始化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函数建立与短信(邮件)系统的会话,MAPILogonExMapix.h中的定义如下:

typedef HRESULT (STDMETHODCALLTYPE MAPILOGONEX)(

    ULONG ulUIParam,

    LPTSTR lpszProfileName,

    LPTSTR lpszPassword,

    ULONG ulFlags,  

    LPMAPISESSION FAR * lppSession

);

MAPILOGONEX MAPILogonEx;

从定义中可以看出MAPILogonEx函数返回一个HRESULT类型,采用宏FAILEDSUCCESSED可以判断函数是否成功返回。同时,该函数有五个参数,这五个参数分别表示,短信(邮件)系统登陆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类型的成员变量,和一个长度为2ULONG类型的数组成员组成。结构体中cValues的值被初始化为2aulPropTag[0]=PR_ENTRYIDaulProTag[1]=PR_DISPLAY_NAME

这里面涉及到了两个常量符号,PR_ENTRYIDPR_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;

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

m_pRows->aRow[0].lpProps[1].Value.lpszWPR_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对象*/

         };

// 获取FolderEntry 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};

 

      // 获取FolderEntry 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);         //释放掉对象

}

//释放掉IMAPISessionIMsgStoreIMAPIFolder对象

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;

         }

 }

你可能感兴趣的:(map)