mfc DECLARE_MESSAGE_MAP

转载自:http://www.cppblog.com/citywanderer/articles/8660.htmlhttp://www.cppblog.com/citywanderer/articles/8660.html

一、关于DECLARE_MESSAGE_MAP宏定义
使用MFC向导,在ApplicationType页面选择DialogBased,生成一个对话框项目,Dialog类命名为CCapturePacketDlg,在CCapturePacketDlg.cpp中自动产生下列代码:

1    BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog)
2      ON_WM_PAINT()
3  END_MESSAGE_MAP()
  1. 先来分析ON_WM_PAINT(),在头文件“afxmsg.h”有它的宏定义,如下:
1    #define   ON_WM_PAINT() \ 
2           { WM_PAINT,  0  0  0 , AfxSig_vv, \
3         (AFX_PMSG)(AFX_PMSGW) \
4         (static_cast <   void  (AFX_MSG_CALL CWnd:: * )( void  >  (  & ThisClass :: OnPaint)) } 
,
说明:层次序号x.y.z表示x为根节点也就是上面代码中的行号,y、z为上一级的定义展开。
2.1 #define WM_PAINT                        0x000F
2.2 AfxSig_vv = AfxSig_v_v_v
2.2.1 enum AfxSig::AfxSig_v_v_v = 19

3.1 AFX_PMSG:typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); //为一个函数指针
3.2 AFX_PMSGW:typedef void (AFX_MSG_CALL CWnd::*AFX_PMSGW)(void);   //为一个函数指针

将ON_WM_PAINT()完全展开:
1 {
2        0x000F
3        0,
4        0,
5        0,
6        19,
7        //Converts OnPaint to the type of CCmdTarget finally. Derive Class 's pointer -> Base Class's pointer
8        (AFX_MSG_CALL CCmdTarget::*)((AFX_MSG_CALL CWnd::*)(static_cast< void (AFX_MSG_CALL CWnd::*)(void>(&ThisClass :: OnPaint))
9    }

   2.   再来分析BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog),在“afxwin.h”中有定义:

 1 #define  BEGIN_MESSAGE_MAP(theClass, baseClass) \
 2     PTM_WARNING_DISABLE \
 3      const  AFX_MSGMAP *  theClass::GetMessageMap()  const  \
 4          return GetThisMessageMap(); }  \
 5      const  AFX_MSGMAP *  PASCAL theClass::GetThisMessageMap() \
 6      { \
 7        typedef theClass ThisClass;                           \
 8        typedef baseClass TheBaseClass;                       \
 9        static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
10        {

2.1 PTM_WARNING_DISABLE: 
#define PTM_WARNING_DISABLE \
 __pragma(warning( push ))  \ //#pragma warning( push [ ,n ] ),Where n represents a warning level (1 through 4). 
                                              //The pragma warning( push ) stores the current warning state for all warnings.
 __pragma(warning( disable : 4867 ))//Do not issue the specified warning message(s).
//http://msdn2.microsoft.com/en-us/2c8f766e.aspx
// Allows selective modification of the behavior of compiler warning messages.
3.1 struct AFX_MSGMAP
 {
  3.1.1 const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
  3.1.2 const AFX_MSGMAP_ENTRY* lpEntries;
 };
3.1.2 struct AFX_MSGMAP_ENTRY
 {
  UINT nMessage;   // windows message
  UINT nCode;      // control code or WM_NOTIFY code
  UINT nID;        // control ID (or 0 for windows messages)
  UINT nLastID;    // used for entries specifying a range of control id's
  UINT_PTR nSig;       // signature type (action) or pointer to message #
  3.1.2.1 AFX_PMSG pfn;    // routine to call (or special value)
 };
 3.1.2.1 typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

5.1 #define PASCAL      __stdcall
将BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog)完全展开:

 1 __pragma(warning( push )) __pragma(warning( disable :  4867  ))
 2      const   struct  AFX_MSGMAP *  CCapturePacketDlg::GetMessageMap()  const
 3     
 4        return GetThisMessageMap(); 
 5    }

 6      const   struct  AFX_MSGMAP *  __stdcall CCapturePacketDlg::GetThisMessageMap()
 7      {
 8        typedef CCapturePacketDlg ThisClass;                           
 9        typedef CDialog TheBaseClass;        
10        static const struct AFX_MSGMAP_ENTRY _messageEntries[] = 
11        {

   3    最后分析END_MESSAGE_MAP(),在“afxwin.h”中有定义:

1 #define  END_MESSAGE_MAP() \
2          {0000, AfxSig_end, (AFX_PMSG)0 }  \
3     }; \
4          static   const  AFX_MSGMAP messageMap  =  \
5          &TheBaseClass::GetThisMessageMap, &_messageEntries[0] } ; \
6          return   & messageMap; \
7     }                                  \
8     PTM_WARNING_RESTORE

2.1 AfxSig_end:enum AfxSig.AfxSig_end = 0
2.2 AFX_PMSG:typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);//函数指针

4.1 struct AFX_MSGMAP
 {
  const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
  const AFX_MSGMAP_ENTRY* lpEntries;
 };

8.1 #define PTM_WARNING_RESTORE \
 __pragma(warning( pop ))
//pop restores the state of all warnings (including 4705, 4706, and 4707) to what it was at the beginning of the code.

·最后将

1 BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog)
2     ON_WM_PAINT()
3 END_MESSAGE_MAP()
完全展开为:
 1 __pragma(warning( push )) __pragma(warning( disable :  4867  ))
 2      const   struct  AFX_MSGMAP *  CCapturePacketDlg::GetMessageMap()  const
 3     
 4        return GetThisMessageMap(); 
 5    }

 6      const   struct  AFX_MSGMAP *  __stdcall CCapturePacketDlg::GetThisMessageMap()
 7      {
 8        typedef CCapturePacketDlg ThisClass;                           
 9        typedef CDialog TheBaseClass;        
10        static const struct AFX_MSGMAP_ENTRY _messageEntries[] = 
11        {
12            {
13                0x000F
14                0,
15                0,
16                0,
17                19,
18                //Converts OnPaint to the type of CCmdTarget finally. Derive Class 's pointer -> Base Class's pointer
19                (AFX_MSG_CALL CCmdTarget::*)((AFX_MSG_CALL CWnd::*)(static_cast< void (AFX_MSG_CALL CWnd::*)(void>(&ThisClass :: OnPaint))
20            }
,
21            //add others
22            {
23                0,
24                0,
25                0,
26                0,
27                0,
28                (AFX_PMSG)0
29            }

30        }

31        static const struct AFX_MSGMAP messageMap = 
32        {
33            &TheBaseClass::GetThisMessageMap,
34            &_messageEntries[0]
35        }
;
36        return &messageMap;
37    }

38 __pragma(warning( pop ))
39
其中GetMessageMap()是在哪里声明的呢?在CCapturePacketDlg的定义中有一个这样的宏:DECLARE_MESSAGE_MAP()
老办法查看它的定义:
1 #define  DECLARE_MESSAGE_MAP() \
2 protected : \
3      static   const  AFX_MSGMAP *  PASCAL GetThisMessageMap(); \
4      virtual   const  AFX_MSGMAP *  GetMessageMap()  const ; \
注意函数为static,即它们是类的函数。函数中的static变量实际也在类对象未生成之前已经存在。(这种说法不知道是否正确?)
小结:
每次用MFC类向导生成一个类时,系统会在类的声明部分添加两个方法的声明:GetThisMessageMap(),GetMessageMap()。在类的实现部分.cpp文件中加上这两个方法的定义。
当然这所有的代码都是由系统生成的,如果我们要定义自己的消息处理函数呢,例如,我们要添加一个按钮(ID为:IDC_BUTTON1)的单击处理函数我们可以添加宏ON_NOTIFY(NM_CLICK, IDC_BUTTON1, OnMyClick),OnMyClick为自定义函数,但是他必须与ON_NOTIFY中的函数原型一致。

二、关于DECLARE_DYNCREATE宏
使用MFC向导,在ApplicationType页面选择SingleDocument,生成一个单文档项目,Document类命名为CDynamicDoc,在CDynamicDoc.h中自动产生DECLARE_DYNCREATE(CDynamicDoc),CDynamicDoc.cpp中产生IMPLEMENT_DYNCREATE(CDynamicDoc, CDocument)。
1、展开CDynamicDoc.h中的DECLARE_DYNCREATE(CDynamicDoc):
1 //  not serializable, but dynamically constructable
2      #define  DECLARE_DYNCREATE(class_name) \
3         DECLARE_DYNAMIC(class_name) \
4          static  CObject *  PASCAL CreateObject();
3.1如下定义:
1 #ifdef _AFXDLL
2      #define  DECLARE_DYNAMIC(class_name) \
3      protected : \
4          static  CRuntimeClass *  PASCAL _GetBaseClass(); \
5      public : \
6          static   const  CRuntimeClass  class ##class_name; \
7          static  CRuntimeClass *  PASCAL GetThisClass(); \
8          virtual  CRuntimeClass *  GetRuntimeClass()  const ; \
so the result(DECLARE_DYNCREATE(CDynamicDoc)) of combining the above two is following:
1 protected
2          static  CRuntimeClass *  PASCAL _GetBaseClass(); 
3      public
4          static   const  CRuntimeClass classCDynamicDoc; 
5          static  CRuntimeClass *  PASCAL GetThisClass(); 
6          virtual  CRuntimeClass *  GetRuntimeClass()  const
7          static  CObject *  PASCAL CreateObject();

2、展开CDynamicDoc.cpp中的IMPLEMENT_DYNCREATE(CDynamicDoc, CDocument):
1 #define  IMPLEMENT_DYNCREATE(class_name, base_class_name) \
2     CObject *  PASCAL class_name::CreateObject() \
3          return new class_name; }  \
4     IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,  0xFFFF , \
5         class_name::CreateObject, NULL)
4.1如下定义:
 1 #define  IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
 2     CRuntimeClass *  PASCAL class_name::_GetBaseClass() \
 3          return RUNTIME_CLASS(base_class_name); }  \
 4     AFX_COMDAT  const  CRuntimeClass class_name:: class ##class_name  =   { \
 5        #class_name, sizeof(class class_name), wSchema, pfnNew, \
 6            &class_name::_GetBaseClass, NULL, class_init }
; \
 7     CRuntimeClass *  PASCAL class_name::GetThisClass() \
 8          return _RUNTIME_CLASS(class_name); }  \
 9     CRuntimeClass *  class_name::GetRuntimeClass()  const  \
10          return _RUNTIME_CLASS(class_name); }  \
4.1.2 CRuntimeClass如下定义:
 1 struct  CRuntimeClass
 2      {
 3    // Attributes
 4        LPCSTR m_lpszClassName;
 5        int m_nObjectSize;
 6        UINT m_wSchema; // schema number of the loaded class
 7        CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
 8    #ifdef _AFXDLL
 9        CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
10    #else
11        CRuntimeClass* m_pBaseClass;
12    #endif
13
14    // Operations
15        CObject* CreateObject();
16        BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
17
18        // dynamic name lookup and creation
19        static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);
20        static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);
21        static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
22        static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);
23
24    // Implementation
25        void Store(CArchive& ar) const;
26        static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
27
28        // CRuntimeClass objects linked together in simple list
29        CRuntimeClass* m_pNextClass;       // linked list of registered classes
30        const AFX_CLASSINIT* m_pClassInit;
31    }
;
4.1.2.30 AFX_CLASSINIT如下定义:(这个变量非常重要,它完成了将新的类加在List头部的功能,List中的节点类型就是CRuntimeClass)
 1 /**/ /////
 2      //  Basic object model
 3
 4      //  generate static object constructor for class registration
 5      void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass);
 6      struct  AFX_CLASSINIT
 7          { AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } } ;
 8      // C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\objcore.cpp Line157
 9      void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass)
10      {
11        AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
12        AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
13        pModuleState->m_classList.AddHead(pNewClass);
14        AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
15    }

16      // 可以将AfxClassInit()函数的功能简单的如下表示:
17     AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass *  pNewClass)
18      {
19        pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
20        CRuntimeClass::pFirstClass = pNewClass;
21    }

4.1.3 RUNTIME_CLASS如下定义:
1 #define  RUNTIME_CLASS(class_name) (class_name::GetThisClass())
4.1.4 AFX_COMDAT如下定义:
1 #define  AFX_COMDAT __declspec(selectany)
说明:“#”——operator (#) converts macro parameters to string literals without expanding the parameter definition.
“##”——operator ( ## ), which is sometimes called the "merging" operator, is used in both object-like and function-like macros. 
4.1.8 _RUNTIME_CLASS如下定义:
1 #define  _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
so the result(IMPLEMENT_DYNCREATE(CDynamicDoc, CDocument)) of combining the aboves is following:
  1 // CDynamicDoc, CDocument->class_name, base_class_name
  2    static   CObject *  PASCAL CDynamicDoc::CreateObject()
 
3     
 
4        return new CDynamicDoc; 
 
5    }

 
6
 
7      static  CRuntimeClass *  PASCAL CDynamicDoc::_GetBaseClass()
 
8     
 
9        return CDocument::GetThisClass()
10    }

11
12     __declspec(selectany)  static   const  CRuntimeClass CDynamicDoc::classCDynamicDoc  =  
13      {
14        "CDynamicDoc"
15        , sizeof(class CDynamicDoc)
16        , 0xFFFF
17        , CDynamicDoc::CreateObject
18        , &CDynamicDoc::_GetBaseClass
19        , NULL
20        , NULL
21    }
;
22
23      static  CRuntimeClass *  PASCAL CDynamicDoc::GetThisClass()
24      {
25        return ((CRuntimeClass*)(&CDynamicDoc::classCDynamicDoc));
26    }

27
28     CRuntimeClass *  CDynamicDoc::GetRuntimeClass()  const
29      {
30        return ((CRuntimeClass*)(&CDynamicDoc::classCDynamicDoc));
31    }
小结:注意了,上面的成员变量、很多函数都是static
如果你想看这些宏的简化版,可以参考侯老的《深入浅出MFC》,如下:

 1//in header file
 2class CView : public CWnd
 3{
 4public:
 5    static CRuntimeClass classCView;
 6    virtual CRuntimeClass* GetRuntimeClass() const;
 7    //……
 8}
;
 9//in implementation file
10static char_lpszCView = "CView";
11CRuntimeClass CView::classCView =
12{
13    _lpszCView
14    , sizeof(CView)
15    , 0xFFF
16    , NULL
17    , &CWnd::classCWnd
18    , NULL
19}
;
20static AFX_CLASSINIT _init_CView(&CView::classCView)
21{
22    (&CView::classCView)->m_pNextClass = CRuntimeClass::pFirstClass;
23    CRuntimeClass::pFirstClass = &CView::classCView;
24}

25CRuntimeClass* CView::GetRuntimeClass() const
26{
27    return &CView::classCView;
28}
其中他将CRuntimeClass简化定义为:
struct CRuntimeClass
{
// Attributes
        LPCSTR m_lpszClassName;
        int m_nObjectSize;
        UINT m_wSchema; // schema number of the loaded class
        CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
        CRuntimeClass* m_pBaseClass;

        // CRuntimeClass objects linked together in simple list
        static CRuntimeClass* pFirstClass; // start of class list
        CRuntimeClass* m_pNextClass;       // linked list of registered classes
};

三、宏DECLARE_SERIAL(CStroke)、IMPLEMENT_SERIAL(CStroke, CObject, 1),给出它们的宏定义及结果:
 1 // declaration file
 2 #define  DECLARE_SERIAL(class_name) \
 3     _DECLARE_DYNCREATE(class_name) \
 4     AFX_API friend CArchive &  AFXAPI  operator >> (CArchive &  ar, class_name *   & pOb);
 5
 6      #define  _DECLARE_DYNCREATE(class_name) \
 7     _DECLARE_DYNAMIC(class_name) \
 8      static  CObject *  PASCAL CreateObject();
 9
10      #define  _DECLARE_DYNAMIC(class_name) \
11      protected : \
12          static  CRuntimeClass *  PASCAL _GetBaseClass(); \
13      public : \
14          static  CRuntimeClass  class ##class_name; \
15          static  CRuntimeClass *  PASCAL GetThisClass(); \
16          virtual  CRuntimeClass *  GetRuntimeClass()  const ; \
17 // implement file
18 #define  IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
19     CObject *  PASCAL class_name::CreateObject() \
20          return new class_name; }  \
21      extern  AFX_CLASSINIT _init_##class_name; \
22     _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
23         class_name::CreateObject,  & _init_##class_name) \
24     AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
25     CArchive &  AFXAPI  operator >> (CArchive &  ar, class_name *   & pOb) \
26          { pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
27            return ar; }
 \
28     
29      //  generate static object constructor for class registration
30      void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass);
31      struct  AFX_CLASSINIT
32          { AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } } ;
33
34      void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass)
35      {
36        AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
37        AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
38        pModuleState->m_classList.AddHead(pNewClass);
39        AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
40    }

41
42     
43      #define  _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
44     CRuntimeClass *  PASCAL class_name::_GetBaseClass() \
45          return RUNTIME_CLASS(base_class_name); }  \
46     AFX_COMDAT CRuntimeClass class_name:: class ##class_name  =   { \
47        #class_name, sizeof(class class_name), wSchema, pfnNew, \
48            &class_name::_GetBaseClass, NULL, class_init }
; \
49     CRuntimeClass *  PASCAL class_name::GetThisClass() \
50          return _RUNTIME_CLASS(class_name); }  \
51     CRuntimeClass *  class_name::GetRuntimeClass()  const  \
52          return _RUNTIME_CLASS(class_name); }  \
53     
54      #define  _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
55
56      #define  RUNTIME_CLASS(class_name) (class_name::GetThisClass())

 1 // header file
 2      protected
 3          static  CRuntimeClass *  PASCAL _GetBaseClass(); 
 4      public
 5          static  CRuntimeClass classCStroke; 
 6          static  CRuntimeClass *  PASCAL GetThisClass(); 
 7          virtual  CRuntimeClass *  GetRuntimeClass()  const
 8          static  CObject *  PASCAL CreateObject();
 9         AFX_API friend CArchive &  AFXAPI  operator >> (CArchive &  ar, CStroke *   & pOb);
10 // implement file
11      // static
12      static  CObject *  PASCAL CStroke::CreateObject()
13      {
14        return new CStroke;
15    }

16      // static
17      static  CRuntimeClass *  PASCAL CStroke::GetThisClass();
18     
19        return ((CRuntimeClass*)(&CStroke::classCStroke))
20    }

21      // static
22      static  CRuntimeClass *  PASCAL CStroke::_GetBaseClass() 
23     
24        return (CObject::GetThisClass());
25    }

26      // static
27      static  AFX_COMDAT CRuntimeClass CStroke::classCStroke  =  
28      {
29        "CStroke"
30        , sizeof(class CStroke)
31        , 1
32        , CStroke::CreateObject
33        , &class_name::_GetBaseClass
34        , NULL
35        , &_init_CStroke 
36    }

37     CRuntimeClass *  CStroke::GetRuntimeClass()  const
38     
39        return ((CRuntimeClass*)(&CStroke::classCStroke)); 
40    }

41      extern   struct  AFX_CLASSINIT _init_CStroke;
42      struct  AFX_CLASSINIT _init_CStroke
43      {
44        void AFXAPI AfxClassInit(CRuntimeClass* CStroke)
45        {
46            AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
47            AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
48            pModuleState->m_classList.AddHead(CStroke);
49            AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
50        }

51    }
;
52     CArchive &  AFXAPI  operator >> (CArchive &  ar, class_name *   & pOb) 
53     
54        pOb = (CStroke*) ar.ReadObject(RUNTIME_CLASS(CStroke));
55        return ar; 
56    }
总结,一旦RUNTIME_CLASS(CStroke)由#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())也就是CStroke::GetThisClass() 即
CStroke::classCStroke = 
 {
  "CStroke"
  , sizeof(class CStroke)
  , 1
  , CStroke::CreateObject
  , &class_name::_GetBaseClass
  , NULL
  , &_init_CStroke 
 }
其中,由extern AFX_CLASSINIT _initCStroke可知_init_CStroke是一个结构体AFX_CLASSINIT的对象,此结构体有构造函数:
 1 void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass);
 2      struct  AFX_CLASSINIT
 3          { AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } } ;
 4
 5      void  AFXAPI AfxClassInit(CRuntimeClass *  pNewClass)
 6      {
 7        AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
 8        AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
 9        pModuleState->m_classList.AddHead(pNewClass);
10        AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
11    }
所以一旦返回classCStroke,也就调用了_init_CStroke的构造函数即将类CStroke添加到了全局变量m_classList类的List中了,同时在变量classCStroke中,也可以得到类CStroke的名称、大小、一个CStroke的对象、类CStroke的基类以及AFX_CLASSINIT结构的一个对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值