CAtlModule类的实现

CAtlModule类的实现: 

=======================   dll   ======================= 
template   <class   T> 
class   ATL_NO_VTABLE   CAtlDllModuleT   :   public   CAtlModuleT <T> 

public   : 
..... 


=======================   exe   ======================= 
template   <class   T> 
class   ATL_NO_VTABLE   CAtlExeModuleT   :   public   CAtlModuleT <T> 

public   : 
..... 


=======================   service   ======================= 
template   <class   T,   UINT   nServiceNameID> 
class   ATL_NO_VTABLE   CAtlServiceModuleT   :   public   CAtlExeModuleT <T> 

public   : 
..... 


=======================   others   ======================= 
template   <class   T> 
class   ATL_NO_VTABLE   CAtlExeModuleT   :   public   CAtlModuleT <T> 

public   : 
..... 


template   <class   T> 
class   ATL_NO_VTABLE   CAtlModuleT   :   public   CAtlModule 

public   : 
..... 


class   ATL_NO_VTABLE   CAtlModule   :   public   _ATL_MODULE 

public   : 
..... 


typedef   _ATL_MODULE70   _ATL_MODULE; 

struct   _ATL_MODULE70 

UINT   cbSize; 
LONG   m_nLockCnt; 
_ATL_TERMFUNC_ELEM*   m_pTermFuncs; 
CComCriticalSection   m_csStaticDataInitAndTypeInfo; 
}; 

类继承关系图: 
_ATL_MODULE 
        ¦ 
        ¦ 
CAtlModule   
        ¦ 
        ¦ 
CAtlModuleT   ------>   CAtlDllModuleT 
        ¦ 
        ¦ 
CAtlExeModuleT   ----->   CAtlServiceModuleT 

CAtlModule类的函数分布:

=======================   CAtlModule   ======================= 
class   ATL_NO_VTABLE   CAtlModule   :   public   _ATL_MODULE 

public   : 
static   GUID   m_libid; 
IGlobalInterfaceTable
*   m_pGIT; 

CAtlModule()   
throw(){   ...   } 
void   Term()   throw(){   ...   } 
~CAtlModule()   throw(){   ...   } 
virtual   LONG   Lock()   throw(){   ...   } 
virtual   LONG   Unlock()   throw
(){   ...   } 
virtual   LONG   GetLockCount()   throw(){   ...   } 
HRESULT   AddTermFunc(_ATL_TERMFUNC
*   pFunc,   DWORD_PTR   dw)   throw(){   ...   } 
virtual   HRESULT   GetGITPtr(IGlobalInterfaceTable**   ppGIT)   throw(){   ...   } 
virtual   HRESULT   AddCommonRGSReplacements(IRegistrarBase*   /*pRegistrar*/)   throw()   =   0

//   Resource-based   Registration 
#ifdef   _ATL_STATIC_REGISTRY 
//   Statically   linking   to   Registry   Ponent 
HRESULT   WINAPI   UpdateRegistryFromResourceS(LPCTSTR   lpszRes,   BOOL   bRegister, 
struct   _ATL_REGMAP_ENTRY*   pMapEntries   =   NULL)   throw(); 
HRESULT   WINAPI   UpdateRegistryFromResourceS(UINT   nResID,   BOOL   bRegister, 
struct   _ATL_REGMAP_ENTRY*   pMapEntries   =   NULL)   throw(); 
#else 
HRESULT   WINAPI   UpdateRegistryFromResourceD(LPCTSTR   lpszRes,   BOOL   bRegister, 
struct   _ATL_REGMAP_ENTRY*   pMapEntries   =   NULL)   throw(){   ...   } 
HRESULT   WINAPI   UpdateRegistryFromResourceD(UINT   nResID,   BOOL   bRegister, 
struct   _ATL_REGMAP_ENTRY*   pMapEntries   =   NULL)   throw(){   ...   } 
#endif 

//   Implementation 
#if   !defined(_ATL_STATIC_REGISTRY) 
inline   HRESULT   WINAPI   UpdateRegistryFromResourceDHelper(LPCOLESTR   lpszRes,   BOOL   bRegister, 
struct   _ATL_REGMAP_ENTRY*   pMapEntries   =   NULL)   throw(){   ...   } 
#endif 

static   void   EscapeSingleQuote(LPOLESTR   lpDest,   LPCOLESTR   lp)   throw(){   ...   } 

//   search   for   an   occurence   of   string   p2   in   string   p1 
static   LPCTSTR   FindOneOf(LPCTSTR   p1,   LPCTSTR   p2)   throw(){   ...   } 
static   int   WordCmpI(LPCTSTR   psz1,   LPCTSTR   psz2)   throw(){   ...   } 
}; 

 在CAtlModule   中出现了熟悉的Lock和Unlock,都为虚函数。 
virtual   LONG   Lock()   throw() 

            return   CComGlobalsThreadModel::Increment(&m_nLockCnt); 


virtual   LONG   Unlock()   throw() 

            return   CComGlobalsThreadModel::Decrement(&m_nLockCnt); 
}

=======================   CAtlModuleT   ======================= 
template   
<class   T> 
class   ATL_NO_VTABLE   CAtlModuleT   :   public   CAtlModule 

public   : 
CAtlModuleT()   
throw(){   ...   } 
static   void   InitLibId()   throw(){   ...   } 
HRESULT   RegisterServer(BOOL   bRegTypeLib   
=   FALSE,   const   CLSID*   pCLSID   =   NULL)   throw(){   ...   } 
HRESULT   UnregisterServer(BOOL   bUnRegTypeLib,   
const   CLSID*   pCLSID   =   NULL)   throw
(){   ...   } 
static   HRESULT   WINAPI   UpdateRegistryAppId(BOOL   /*bRegister*/)   throw
(){   ...   } 
HRESULT   RegisterAppId()   
throw
(){   ...   } 
HRESULT   UnregisterAppId()   
throw(){   ...   } 
virtual   HRESULT   AddCommonRGSReplacements(IRegistrarBase*   pRegistrar)   throw(){   ...   } 
static   LPCOLESTR   GetAppId()   throw(){   ...   } 
}; 

 在CAtlModuleT中实现了RegisterServer,UnregisterServer,   RegisterAppId,   UpdateRegistryAppId ,UnregisterAppId

HRESULT   RegisterServer(BOOL   bRegTypeLib   =   FALSE,   const   CLSID*   pCLSID   =   NULL)   throw() 

       pCLSID; 
       bRegTypeLib; 

       HRESULT   hr   =   S_OK; 

 #ifndef   _ATL_NO_COM_SUPPORT 

       hr   =   _AtlComModule.RegisterServer(bRegTypeLib,   pCLSID); 

 #endif //   _ATL_NO_COM_SUPPORT 


#ifndef   _ATL_NO_PERF_SUPPORT 

       if   (SUCCEEDED(hr)   &&   _pPerfRegFunc   !=   NULL) 
              hr   =   (*_pPerfRegFunc)(_AtlBaseModule.m_hInst); 

#endif 

        return   hr; 


HRESULT   UnregisterServer(BOOL   bUnRegTypeLib,   const   CLSID*   pCLSID   =   NULL)   throw() 

         bUnRegTypeLib; 
         pCLSID; 

         HRESULT   hr   =   S_OK; 

#ifndef   _ATL_NO_PERF_SUPPORT 

         if   (_pPerfUnRegFunc   !=   NULL) 
                    hr   =   (*_pPerfUnRegFunc)(); 

#endif 

#ifndef   _ATL_NO_COM_SUPPORT 

        if   (SUCCEEDED(hr)) 
                   hr   =   _AtlComModule.UnregisterServer(bUnRegTypeLib,   pCLSID); 

#endif //   _ATL_NO_COM_SUPPORT 

       return   hr; 



static   HRESULT   WINAPI   UpdateRegistryAppId(BOOL   /*bRegister*/)   throw() 

        return   S_OK; 

HRESULT   RegisterAppId()   throw() 

        return   T::UpdateRegistryAppId(TRUE); 


HRESULT   UnregisterAppId()   throw() 

       return   T::UpdateRegistryAppId(FALSE); 

它们会在CAtlDllModuleT,CAtlExeModuleT和CAtlServiceModuleT中使用: 
=======================   CAtlDllModuleT======================= 
HRESULT   DllRegisterServer(BOOL   bRegTypeLib   =   TRUE)   throw() 

       //   registers   object,   typelib   and   all   interfaces   in   typelib 
       T*   pT   =   static_cast <T*> (this); 
       HRESULT   hr   =   pT-> RegisterAppId(); 
       if   (SUCCEEDED(hr)) 
              hr   =   pT-> RegisterServer(bRegTypeLib); 
        return   hr; 


HRESULT   DllUnregisterServer(BOOL   bUnRegTypeLib   =   TRUE)   throw() 

        T*   pT   =   static_cast <T*> (this); 
        HRESULT   hr   =   pT-> UnregisterServer(bUnRegTypeLib); 
        if   (SUCCEEDED(hr)) 
              hr   =   pT-> UnregisterAppId(); 
        return   hr; 

=======================   CAtlExeModuleT   ======================= 
//   Parses   the   command   line   and   registers/unregisters   the   rgs   file   if   necessary 
bool   ParseCommandLine(LPCTSTR   lpCmdLine,   HRESULT*   pnRetCode)   throw() 

        *pnRetCode   =   S_OK; 

        TCHAR   szTokens[]   =   _T( "-/ "); 

        T*   pT   =   static_cast <T*> (this); 
        LPCTSTR   lpszToken   =   FindOneOf(lpCmdLine,   szTokens); 
        while   (lpszToken   !=   NULL) 
        { 
               if   (WordCmpI(lpszToken,   _T( "UnregServer "))==0) 
               { 
                       *pnRetCode   =   pT-> UnregisterServer(TRUE); 
                       if   (SUCCEEDED(*pnRetCode)) 
                             *pnRetCode   =   pT-> UnregisterAppId(); 
                       return   false; 
               } 

               //   Register   as   Local   Server 
               if   (WordCmpI(lpszToken,   _T( "RegServer "))==0) 
               { 
                       *pnRetCode   =   pT-> RegisterAppId(); 
                       if   (SUCCEEDED(*pnRetCode)) 
                                *pnRetCode   =   pT-> RegisterServer(TRUE); 
                       return   false; 
               } 

               lpszToken   =   FindOneOf(lpszToken,   szTokens); 
        } 

        return   true; 


=======================   CAtlServiceModuleT   ======================= 

//   Parses   the   command   line   and   registers/unregisters   the   rgs   file   if   necessary 
bool   ParseCommandLine(LPCTSTR   lpCmdLine,   HRESULT*   pnRetCode)   throw() 

         if   (!CAtlExeModuleT <T> ::ParseCommandLine(lpCmdLine,   pnRetCode)) 
               return   false; 

         TCHAR   szTokens[]   =   _T( "-/ "); 
         *pnRetCode   =   S_OK; 

         T*   pT   =   static_cast <T*> (this); 
         LPCTSTR   lpszToken   =   FindOneOf(lpCmdLine,   szTokens); 
         while   (lpszToken   !=   NULL) 
         { 
                if   (WordCmpI(lpszToken,   _T( "Service "))==0) 
                { 
                       *pnRetCode   =   pT-> RegisterAppId(true); 
                       if   (SUCCEEDED(*pnRetCode)) 
                              *pnRetCode   =   pT-> RegisterServer(TRUE); 
                       return   false; 
                } 
                lpszToken   =   FindOneOf(lpszToken,   szTokens); 
         } 
         return   true; 
}

 

=======================   CAtlDllModuleT   ======================= 
template   
<class   T> 
class   ATL_NO_VTABLE   CAtlDllModuleT   :   public   CAtlModuleT <T> 

public   : 
CAtlDllModuleT()   
throw(){   ...   } 
~CAtlDllModuleT()   throw(){   ...   } 
BOOL   WINAPI   DllMain(DWORD   dwReason,   LPVOID   
/*   lpReserved   */)   throw(){   ...   } 
HRESULT   DllCanUnloadNow()   
throw
(){   ...   } 
HRESULT   DllGetClassObject(REFCLSID   rclsid,   REFIID   riid,   LPVOID
*   ppv)   throw
(){   ...   } 
HRESULT   DllRegisterServer(BOOL   bRegTypeLib   
=   TRUE)   throw
(){   ...   } 
HRESULT   DllUnregisterServer(BOOL   bUnRegTypeLib   
=   TRUE)   throw
(){   ...   } 

//   Obtain   a   Class   Factory 

HRESULT   GetClassObject(REFCLSID   rclsid,   REFIID   riid,   LPVOID*   ppv)   throw(){   ...   } 
}; 

CAtlDllModuleT   在中实现了更加熟悉的DllMain,DllCanUnloadNow,   DllGetClassObject,   DllRegisterServer,   DllUnregisterServer和   GetClassObject

BOOL   WINAPI   DllMain(DWORD   dwReason,   LPVOID   /*   lpReserved   */)   throw() 

        if   (dwReason   ==   DLL_PROCESS_ATTACH) 
        { 
              if   (CAtlBaseModule::m_bInitFailed) 
              { 
                    ATLASSERT(0); 
                    return   FALSE; 
              } 
             _AtlBaseModule.m_eDllInitializationStage   =   Dll_Process_attached; 

#ifdef   _ATL_MIN_CRT 
            DisableThreadLibraryCalls(_AtlBaseModule.GetModuleInstance()); 
#endif 
      } 
      else   if   (dwReason   ==   DLL_PROCESS_DETACH) 
     { 
            _AtlBaseModule.m_eDllInitializationStage   =   Dll_Process_detached; 

#ifdef   _DEBUG 
           //   Prevent   false   memory   leak   reporting.   ~CAtlWinModule   may   be   too   late. 
           _AtlWinModule.Term(); 
#endif //   _DEBUG 

     } 

     return   TRUE;         //   ok 


HRESULT   DllCanUnloadNow()   throw() 

     T*   pT   =   static_cast <T*> (this); 
     return   (pT-> GetLockCount()==0)   ?   S_OK   :   S_FALSE; 


HRESULT   DllGetClassObject(REFCLSID   rclsid,   REFIID   riid,   LPVOID*   ppv)   throw() 

      T*   pT   =   static_cast <T*> (this); 
      return   pT-> GetClassObject(rclsid,   riid,   ppv); 


HRESULT   DllRegisterServer(BOOL   bRegTypeLib   =   TRUE)   throw() 

      //   registers   object,   typelib   and   all   interfaces   in   typelib 
      T*   pT   =   static_cast <T*> (this); 
      HRESULT   hr   =   pT-> RegisterAppId(); 
      if   (SUCCEEDED(hr)) 
      hr   =   pT-> RegisterServer(bRegTypeLib); 
      return   hr; 


HRESULT   DllUnregisterServer(BOOL   bUnRegTypeLib   =   TRUE)   throw() 

     T*   pT   =   static_cast <T*> (this); 
     HRESULT   hr   =   pT-> UnregisterServer(bUnRegTypeLib); 
     if   (SUCCEEDED(hr)) 
          hr   =   pT-> UnregisterAppId(); 
     return   hr; 


//   Obtain   a   Class   Factory 
HRESULT   GetClassObject(REFCLSID   rclsid,   REFIID   riid,   LPVOID*   ppv)   throw() 


#ifndef   _ATL_OLEDB_CONFORMANCE_TESTS 
      ATLASSERT(ppv   !=   NULL); 
#endif 

       return   AtlComModuleGetClassObject(&_AtlComModule,   rclsid,   riid,   ppv); 
}

=======================   CAtlExeModuleT   ======================= 
template   
<class   T> 
class   ATL_NO_VTABLE   CAtlExeModuleT   :   public   CAtlModuleT <T> 

public   : 
#ifndef   _ATL_NO_COM_SUPPORT 

DWORD   m_dwMainThreadID; 
HANDLE   m_hEventShutdown; 
DWORD   m_dwTimeOut; 
DWORD   m_dwPause; 
bool   m_bDelayShutdown; 
bool   m_bActivity; 

#endif   //   _ATL_NO_COM_SUPPORT 

CAtlExeModuleT()   
throw(){   ...   } 
~CAtlExeModuleT()   throw(){   ...   } 
static   HRESULT   InitializeCom()   throw(){   ...   } 
static   void   UninitializeCom()   throw(){   ...   } 
LONG   Unlock()   
throw(){   ...   } 
void   MonitorShutdown()   throw(){   ...   } 
HANDLE   StartMonitor()   
throw(){   ...   } 
static   DWORD   WINAPI   MonitorProc(void*   pv)   throw(){   ...   } 
int   WinMain(int   nShowCmd)   throw(){   ...   } 

//   Scan   command   line   and   perform   registration 
//   Return   value   specifies   if   server   should   run 

//   Parses   the   command   line   and   registers/unregisters   the   rgs   file   if   necessary 
bool   ParseCommandLine(LPCTSTR   lpCmdLine,   HRESULT*   pnRetCode)   throw(){   ...   } 
HRESULT   PreMessageLoop(
int   /*nShowCmd*/)   throw(){   ...   } 
HRESULT   PostMessageLoop()   
throw(){   ...   } 
void   RunMessageLoop()   throw(){   ...   } 
HRESULT   Run(
int   nShowCmd   =   SW_HIDE)   throw(){   ...   } 

//   Register/Revoke   All   Class   Factories   with   the   OS   (EXE   only) 
HRESULT   RegisterClassObjects(DWORD   dwClsContext,   DWORD   dwFlags)   throw(){   ...   } 
HRESULT   RevokeClassObjects()   
throw(){   ...   } 
}; 

 CAtlExeModuleT重新实现了自己的Unlock 
LONG   Unlock()   throw() 

         LONG   lRet   =   CComGlobalsThreadModel::Decrement(&m_nLockCnt); 

         #ifndef   _ATL_NO_COM_SUPPORT 

         if   (lRet   ==   0) 
         { 
                if   (m_bDelayShutdown) 
               { 
                         m_bActivity   =   true; 
                         ::SetEvent(m_hEventShutdown);   //   tell   monitor   that   we   transitioned   to   zero 
               } 
               else 
               { 
                        ::PostThreadMessage(m_dwMainThreadID,   WM_QUIT,   0,   0); 
                } 
         } 

        #endif //   _ATL_NO_COM_SUPPORT 

          return   lRet; 
}

=======================   CAtlServiceModuleT   ======================= 
template   
<class   T,   UINT   nServiceNameID> 
class   ATL_NO_VTABLE   CAtlServiceModuleT   :   public   CAtlExeModuleT <T> 

public   : 

CAtlServiceModuleT()   
throw(){   ...   } 
int   WinMain(int   nShowCmd)   throw(){   ...   } 
HRESULT   Start(
int   nShowCmd)   throw(){   ...   } 
inline   HRESULT   RegisterAppId(
bool   bService   =   false)   throw(){   ...   } 
HRESULT   UnregisterAppId()   
throw(){   ...   } 

//   Parses   the   command   line   and   registers/unregisters   the   rgs   file   if   necessary 
bool   ParseCommandLine(LPCTSTR   lpCmdLine,   HRESULT*   pnRetCode)   throw(){   ...   } 
void   ServiceMain(DWORD   dwArgc,   LPTSTR*   lpszArgv)   throw(){   ...   } 
HRESULT   Run(
int   nShowCmd   =   SW_HIDE)   throw(){   ...   } 
HRESULT   PreMessageLoop(
int   nShowCmd)   throw(){   ...   } 

//   This   function   provides   the   default   security   settings   for   your   service, 
//   you   should   overide   this   in   your   specific   service   module   class   to   change 
//   as   appropriate.     By   default,   this   will   allow   any   caller   and   calls   will   be 
//   on   the   callers   security   token   (impersonated). 
HRESULT   InitializeSecurity()   throw(){   ...   } 

void   OnStop()   throw(){   } 

void   OnPause()   throw(){   } 
void   OnContinue()   throw(){   } 
void   OnInterrogate()   throw(){   } 
void   OnShutdown()   throw(){   } 
void   OnUnknownRequest(DWORD   /*dwOpcode*/)   throw(){   ...   } 
void   Handler(DWORD   dwOpcode)   throw(){   ...   } 
BOOL   IsInstalled()   
throw(){   ...   } 
BOOL   Install()   
throw(){   ...   } 
BOOL   Uninstall()   
throw(){   ...   } 
LONG   Unlock()   
throw(){   ...   } 
void   LogEventEx(int   id,   LPCTSTR   pszMessage=NULL,   WORD   type   =   EVENTLOG_INFORMATION_TYPE)   throw
(){   ...   } 
void   __cdecl   LogEvent(LPCTSTR   pszFormat,   ...)   throw
(){   ...   } 
void   SetServiceStatus(DWORD   dwState)   throw
(){   ...   } 

//Implementation 
protected
static   void   WINAPI   _ServiceMain(DWORD   dwArgc,   LPTSTR*   lpszArgv)   throw(){   ...   } 
static   void   WINAPI   _Handler(DWORD   dwOpcode)   throw(){   ...   } 

//   data   members 
public
TCHAR   m_szServiceName[
256]; 
SERVICE_STATUS_HANDLE   m_hServiceStatus; 
SERVICE_STATUS   m_status; 
BOOL   m_bService; 
DWORD   m_dwThreadID; 
}; 

CAtlServiceModuleT也重新实现了自己的Unlock 
LONG   Unlock()   throw() 

       LONG   lRet; 
       if   (m_bService) 
      { 
            //   We   are   running   as   a   service,   therefore   transition   to   zero   does   not 
            //   unload   the   process 
           lRet   =   CAtlModuleT <T> ::Unlock(); 
      } 
      else 
      { 
          //   We   are   running   as   EXE,   use   MonitorShutdown   logic   provided   by   CExeModule 
          lRet   =   CAtlExeModuleT <T> ::Unlock(); 
      } 
      return   lRet; 
}

在CAtlServiceModuleT中实现了InitializeSecurity,   LogEventEx和SetServiceStatus 
//   This   function   provides   the   default   security   settings   for   your   service, 
//   you   should   overide   this   in   your   specific   service   module   class   to   change 
//   as   appropriate.     By   default,   this   will   allow   any   caller   and   calls   will   be 
//   on   the   callers   security   token   (impersonated). 
HRESULT   InitializeSecurity()   throw() 

        return   CoInitializeSecurity(NULL,   -1,   NULL,   NULL,   RPC_C_AUTHN_LEVEL_PKT,   RPC_C_IMP_LEVEL_IMPERSONATE,   NULL,   EOAC_NONE,   NULL); 
}

void   LogEventEx(int   id,   LPCTSTR   pszMessage=NULL,   WORD   type   =   EVENTLOG_INFORMATION_TYPE)   throw() 

       HANDLE   hEventSource; 
       if   (m_szServiceName) 
       { 
              /*   Get   a   handle   to   use   with   ReportEvent().   */ 
              hEventSource   =   RegisterEventSource(NULL,   m_szServiceName); 
              if   (hEventSource   !=   NULL) 
              { 
                    /*   Write   to   event   log.   */ 
                   ReportEvent(hEventSource,   
                   type, 
                   (WORD)0, 
                   id, 
                   NULL, 
                   (WORD)(pszMessage   !=   NULL   ?   1   :   0), 
                   0, 
                   pszMessage   !=   NULL   ?   &pszMessage   :   NULL, 
                   NULL); 
                   DeregisterEventSource(hEventSource); 
             } 
      } 


void   __cdecl   LogEvent(LPCTSTR   pszFormat,   ...)   throw() 

       TCHAR   chMsg[256]; 
       HANDLE   hEventSource; 
       LPTSTR   lpszStrings[1]; 
       va_list   pArg; 

       va_start(pArg,   pszFormat); 
       _vstprintf(chMsg,   pszFormat,   pArg); 
       va_end(pArg); 

       lpszStrings[0]   =   chMsg; 

       if   (!m_bService) 
       { 
             //   Not   running   as   a   service,   so   print   out   the   error   message   
             //   to   the   console   if   possible 
             _putts(chMsg); 
       } 

       /*   Get   a   handle   to   use   with   ReportEvent().   */ 
       hEventSource   =   RegisterEventSource(NULL,   m_szServiceName); 
       if   (hEventSource   !=   NULL) 
       { 
              /*   Write   to   event   log.   */ 
              ReportEvent(hEventSource,   EVENTLOG_INFORMATION_TYPE,   0,   0,   NULL,   1,   0,   (LPCTSTR*)   &lpszStrings[0],   NULL); 
              DeregisterEventSource(hEventSource); 
       } 

void   SetServiceStatus(DWORD   dwState)   throw() 

       m_status.dwCurrentState   =   dwState; 
       ::SetServiceStatus(m_hServiceStatus,   &m_status); 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值