jiangj

jiangj

0个粉丝

7

问答

0

专栏

0

资料

jiangj  发布于  2012-12-04 11:43:41
采纳率 0%
7个问答
2490

CEMAPI建立与短信信箱的连接

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



上一部分已经讨论过,如何搭建开发和测试环境,以及如何初始化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;




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



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



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



方法返回一个HRESULT对象,通过它可以判断调用是否成功。前两个参数的意义与MAPILogonEx中的同名参数相同,最后一个参数保留不用。对于短信操作来说,三个参数均可设置为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              &a, mp;a, mp;a, mp;n, bsp;flt;  &, ;nbs, p;   &nb, sp; /* 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)        //释放资源

{      

  F, reeProws(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;

}



}


当成功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;



}


从定义中可以看出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)



{



//异常处理



}


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

Markdown 语法

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

Markdown 语法

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

举报类型

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

详细说明

易百纳技术社区