CSerialPort Class

  1. #include "stdafx.h"   
  2. #include "SerialPort.h"   
  3. #include <assert.h>   
  4.     
  5. //   
  6. // 构造函数   
  7. //   
  8. CSerialPort::CSerialPort()   
  9. {   
  10.     m_hComm = NULL;   
  11.    
  12.     // 重叠结构成员置0   
  13.     m_ov.Offset = 0;   
  14.     m_ov.OffsetHigh = 0;   
  15.    
  16.     // 创建事件   
  17.     m_ov.hEvent = NULL;   
  18.     m_hWriteEvent = NULL;   
  19.     m_hShutdownEvent = NULL;   
  20.    
  21.     m_szWriteBuffer = NULL;   
  22.    
  23.     m_bThreadAlive = FALSE;   
  24. }   
  25.    
  26. //   
  27. // 析构函数   
  28. //   
  29. CSerialPort::~CSerialPort()   
  30. {   
  31.     do   
  32.     {   
  33.         SetEvent(m_hShutdownEvent);   
  34.     } while (m_bThreadAlive);   
  35.    
  36.     TRACE("Thread ended\n");   
  37.     delete [] m_szWriteBuffer;   
  38. }   
  39.    
  40. //   
  41. // 初始化串行口   
  42. //   
  43. BOOL CSerialPort::InitPort(CWnd* pPortOwner,    // 接受串行口信息的窗口   
  44.                            UINT  portnr,        // 串口数   
  45.                            UINT  baud,          // 波特率   
  46.                            char  parity,        // 奇偶    
  47.                            UINT  databits,      // 数据位    
  48.                            UINT  stopbits,      // 停止位    
  49.                            DWORD dwCommEvents,  // EV_RXCHAR, EV_CTS 等   
  50.                            UINT  writebuffersize)   // 写缓冲大小   
  51. {   
  52.     assert(portnr > 0 && portnr < 5);   
  53.     assert(pPortOwner != NULL);   
  54.    
  55.     // 线程是激活的则杀掉   
  56.     if (m_bThreadAlive)   
  57.     {   
  58.         do   
  59.         {   
  60.             SetEvent(m_hShutdownEvent);//设置关闭事件   
  61.         } while (m_bThreadAlive);   
  62.         TRACE("Thread ended\n");   
  63.     }   
  64.    
  65.     // 建立事件   
  66.     if (m_ov.hEvent != NULL)   
  67.         ResetEvent(m_ov.hEvent); //设置事件"m_ov.hEvent"为无信号状态   
  68.     m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);   
  69.    
  70.     if (m_hWriteEvent != NULL)   
  71.         ResetEvent(m_hWriteEvent);//设置事件"m_hWriteEvent"为无信号状态   
  72.     m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);   
  73.     //无继承,手动复位,无信号原始状态,无名的事件   
  74.        
  75.     if (m_hShutdownEvent != NULL)   
  76.         ResetEvent(m_hShutdownEvent);//设置事件"m_hShutdownEvent"为无信号状态   
  77.     m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);   
  78.    
  79.     // 事件初始化   
  80.     m_hEventArray[0] = m_hShutdownEvent;    //  优先级最高   
  81.     m_hEventArray[1] = m_ov.hEvent;//   
  82.     m_hEventArray[2] = m_hWriteEvent;//写事件   
  83.    
  84.     // 初始化临界区   
  85.     InitializeCriticalSection(&m_csCommunicationSync);   
  86.        
  87.     // 设置缓冲大小和保持串口拥有者   
  88.     m_pOwner = pPortOwner;   
  89.    
  90.     if (m_szWriteBuffer != NULL)   
  91.         delete [] m_szWriteBuffer;   
  92.     m_szWriteBuffer = new char[writebuffersize];   
  93.    
  94.     m_nPortNr = portnr;   
  95.    
  96.     m_nWriteBufferSize = writebuffersize;   
  97.     m_dwCommEvents = dwCommEvents;   
  98.    
  99.     BOOL bResult = FALSE;   
  100.     char *szPort = new char[50];   
  101.     char *szBaud = new char[50];   
  102.    
  103.     // 设置临界   
  104.     EnterCriticalSection(&m_csCommunicationSync);   
  105.    
  106.     // 串口已打开,先关掉   
  107.     if (m_hComm != NULL)   
  108.     {   
  109.         CloseHandle(m_hComm);   
  110.         m_hComm = NULL;   
  111.     }   
  112.    
  113.     // 串行口初始化   
  114.     sprintf(szPort, "COM%d", portnr);   
  115.     sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);   
  116.    
  117.     // 获得串口句柄   
  118.     m_hComm = CreateFile(szPort,                        // 串口名   
  119.                      GENERIC_READ | GENERIC_WRITE,  // 读写类型   
  120.                      0,                             // 设置为独占方式   
  121.                      NULL,                          // 无安全属性   
  122.                      OPEN_EXISTING,                 // OPEN_EXISTING   
  123.                      FILE_FLAG_OVERLAPPED,          // 异步I/O   
  124.                      0);                            //    
  125.    
  126.     if (m_hComm == INVALID_HANDLE_VALUE)   
  127.     {   
  128.         // 串口未发现   
  129.         delete [] szPort;   
  130.         delete [] szBaud;   
  131.         return FALSE;   
  132.     }   
  133.    
  134.     // 设置超时值   
  135.     m_CommTimeouts.ReadIntervalTimeout = 1000;   
  136.     m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;   
  137.     m_CommTimeouts.ReadTotalTimeoutConstant = 1000;   
  138.     m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;   
  139.     m_CommTimeouts.WriteTotalTimeoutConstant = 1000;   
  140.    
  141.     // 结构串口   
  142.     if (SetCommTimeouts(m_hComm, &m_CommTimeouts))   
  143.     {                             
  144.         if (SetCommMask(m_hComm, dwCommEvents))   
  145.         {   
  146.             if (GetCommState(m_hComm, &m_dcb))   
  147.             {   
  148.                 m_dcb.fRtsControl = RTS_CONTROL_ENABLE;// 设置RTS为高   
  149.                 if (BuildCommDCB(szBaud, &m_dcb))   
  150.                 {   
  151.                     if (SetCommState(m_hComm, &m_dcb))   
  152.                         ; // 正常操作...继续   
  153.                     else   
  154.                         ProcessErrorMessage("SetCommState()");   
  155.                 }   
  156.                 else   
  157.                     ProcessErrorMessage("BuildCommDCB()");   
  158.             }   
  159.             else   
  160.                 ProcessErrorMessage("GetCommState()");   
  161.         }   
  162.         else   
  163.             ProcessErrorMessage("SetCommMask()");   
  164.     }   
  165.     else   
  166.         ProcessErrorMessage("SetCommTimeouts()");   
  167.    
  168.     delete [] szPort;   
  169.     delete [] szBaud;   
  170.    
  171.     // 刷新串口   
  172.     PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);   
  173.    
  174.     // 释放临界区   
  175.     LeaveCriticalSection(&m_csCommunicationSync);   
  176.    
  177.     TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr);   
  178.    
  179.     return TRUE;   
  180. }   
  181.    
  182. //   
  183. //  串口线程函数   
  184. //   
  185. UINT CSerialPort::CommThread(LPVOID pParam)   
  186. {   
  187.     // 指派一个void 指针,使其找回类 CSerialPort   
  188.     CSerialPort *port = (CSerialPort*)pParam;   
  189.        
  190.     // 设置一状态变量为TRUE,指示线程已运行.   
  191.     port->m_bThreadAlive = TRUE;    
  192.            
  193.     // 其他变量   
  194.     DWORD BytesTransfered = 0;    
  195.     DWORD Event = 0;   
  196.     DWORD CommEvent = 0;   
  197.     DWORD dwError = 0;   
  198.     COMSTAT comstat;   
  199.     BOOL  bResult = TRUE;   
  200.            
  201.     // 开始时,清除串口缓冲区   
  202.     if (port->m_hComm)       // 检查端口是否打开   
  203.         PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);   
  204.    
  205.     // 死循环,线程存在一直循环   
  206.     for (;;)    
  207.     {    
  208.    
  209.         // 呼叫函数 WaitCommEvent().  这种呼叫立即返回,因为异步口设置   
  210.         //为(FILE_FLAG_OVERLAPPED) m_OverlappedStructerlapped 结构被指定.   
  211.         // This call will cause the    
  212.         // m_OverlappedStructerlapped element m_OverlappedStruct.hEvent,    
  213.         // which is part of the m_hEventArray to    
  214.         // be placed in a non-signeled state if there are no bytes available to be read,   
  215.         // or to a signeled state if there are bytes available.     
  216.         // 如果事件句柄被设置为无信号状态,在串口有一个字符到来时设置为信号状态   
  217.    
  218.         bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);   
  219.    
  220.         if (!bResult)     
  221.         {    
  222.             // 如果 WaitCommEvent() 返回为假, 处理错误   
  223.             switch (dwError = GetLastError())    
  224.             {    
  225.             case ERROR_IO_PENDING:     
  226.                 {    
  227.                     // 常返回值,指示在输入缓冲区中无字符   
  228.                     break;   
  229.                 }   
  230.             case 87:   
  231.                 {   
  232.                     // 这是NT下的返回值,此值为其他原因   
  233.                     break;   
  234.                 }   
  235.             default:   
  236.                 {   
  237.                     // 其他的错误码   
  238.                     //port->ProcessErrorMessage("WaitCommEvent()");   
  239.                     break;   
  240.                 }   
  241.             }   
  242.         }   
  243.         else   
  244.         {   
  245.         // 如果 WaitCommEvent() 返回真值,检查在读缓冲区是否真的   
  246.         // 有输入字符,   
  247.         //一次从缓冲区读取超过一个字符时,第一个字符到达时 会引起   
  248.         //函数WaitForMultipleObjects()停止等待,函数WaitForMultipleObjects()   
  249.         //返回时,将重置在m_OverlappedStruct.hEvent中的事件句柄为   
  250.         //无信号状态   
  251.         // If in the time between the reset of this event and the call to    
  252.         // ReadFile() more bytes arrive, the m_OverlappedStruct.hEvent handle will be set again   
  253.         // to the signeled state. When the call to ReadFile() occurs, it will    
  254.         // read all of the bytes from the buffer, and the program will   
  255.         // loop back around to WaitCommEvent().   
  256.         //    
  257.         // At this point you will be in the situation where m_OverlappedStruct.hEvent is set,   
  258.         // but there are no bytes available to read.  If you proceed and call   
  259.         // ReadFile(), it will return immediatly due to the async port setup, but   
  260.         // GetOverlappedResults() will not return until the next character arrives.   
  261.         //   
  262.         // It is not desirable for the GetOverlappedResults() function to be in    
  263.         // this state.  The thread shutdown event (event 0) and the WriteFile()   
  264.         // event (Event2) will not work if the thread is blocked by GetOverlappedResults().   
  265.         //   
  266.         // The solution to this is to check the buffer with a call to ClearCommError().   
  267.         // This call will reset the event handle, and if there are no bytes to read   
  268.         // we can loop back through WaitCommEvent() again, then proceed.   
  269.         // If there are really bytes to read, do nothing and proceed.   
  270.            
  271.             bResult = ClearCommError(port->m_hComm, &dwError, &comstat);   
  272.    
  273.             if (comstat.cbInQue == 0)//输入缓冲区无字符   
  274.                 continue;   
  275.         }   // bResult结束   
  276.    
  277.         // 主等待函数,等待事件发生,他正常地阻塞线程,直到要求的事件发生   
  278.         Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);   
  279.    
  280.         switch (Event)   
  281.         {   
  282.         case 0:   
  283.             {   
  284.                 // 关闭事件.  指示0为最高的优先级,最先被服务   
  285.                 port->m_bThreadAlive = FALSE;   
  286.                    
  287.                 // 释放线程   
  288.                 AfxEndThread(100);   
  289.                 break;   
  290.             }   
  291.         case 1: // 读事件   
  292.             {   
  293.                 GetCommMask(port->m_hComm, &CommEvent);   
  294.                 if (CommEvent & EV_CTS)   
  295.                     ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);   
  296.                 if (CommEvent & EV_RXFLAG)   
  297.                     ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);   
  298.                 if (CommEvent & EV_BREAK)   
  299.                     ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);   
  300.                 if (CommEvent & EV_ERR)   
  301.                     ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);   
  302.                 if (CommEvent & EV_RING)   
  303.                     ::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);   
  304.                 if (CommEvent & EV_RXCHAR)   
  305.                     // 从串口读字符   
  306.                     ReceiveChar(port, comstat);   
  307.                        
  308.                 break;   
  309.             }     
  310.         case 2: // 写事件   
  311.             {   
  312.                 WriteChar(port);   
  313.                 GetCommMask(port->m_hComm, &CommEvent);   
  314.                 if (CommEvent & EV_TXEMPTY)   
  315.                 {   
  316.                     MessageBox(NULL, NULL, "完成", MB_ICONSTOP);   
  317.                 }   
  318.                 // 从串口写字符事件   
  319.                    
  320.                 break;   
  321.             }   
  322.    
  323.         } // switch 结束   
  324.    
  325.     } // 关闭死循环   
  326.    
  327.     return 0;   
  328. }   
  329.    
  330. //   
  331. // 启动串口监测线程   
  332. //   
  333. BOOL CSerialPort::StartMonitoring()   
  334. {   
  335.     if (!(m_Thread = AfxBeginThread(CommThread, this)))   
  336.         return FALSE;   
  337.     TRACE("Thread started\n");   
  338.     return TRUE;       
  339. }   
  340.    
  341. //   
  342. // 重新启动串口线程   
  343. //   
  344. BOOL CSerialPort::RestartMonitoring()   
  345. {   
  346.     TRACE("Thread resumed\n");   
  347.     m_Thread->ResumeThread();   
  348.     return TRUE;       
  349. }   
  350.    
  351. //   
  352. //停止串口线程   
  353. //   
  354. BOOL CSerialPort::StopMonitoring()   
  355. {   
  356.     TRACE("Thread suspended\n");   
  357.     m_Thread->SuspendThread();    
  358.     return TRUE;       
  359. }   
  360.    
  361. //   
  362. // 显示错误信息   
  363. //   
  364. void CSerialPort::ProcessErrorMessage(char* ErrorText)   
  365. {   
  366.     char *Temp = new char[200];   
  367.        
  368.     LPVOID lpMsgBuf;   
  369.    
  370.     FormatMessage(    
  371.         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,   
  372.         NULL,   
  373.         GetLastError(),   
  374.         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 缺省语言   
  375.         (LPTSTR) &lpMsgBuf,   
  376.         0,   
  377.         NULL    
  378.     );   
  379.    
  380.     sprintf(Temp, "WARNING:  %s Failed with the following error: \n%s\nPort: %d\n", (char*)ErrorText, lpMsgBuf, m_nPortNr);    
  381.     MessageBox(NULL, Temp, "Application Error", MB_ICONSTOP);   
  382.    
  383.     LocalFree(lpMsgBuf);   
  384.     delete[] Temp;   
  385. }   
  386.    
  387. //   
  388. // 写一个字符   
  389. //   
  390. void CSerialPort::WriteChar(CSerialPort* port)   
  391. {   
  392.     BOOL bWrite = TRUE;   
  393.     BOOL bResult = TRUE;   
  394.    
  395.     DWORD BytesSent = 0;   
  396.    
  397.     ResetEvent(port->m_hWriteEvent);   
  398.    
  399.     // 获取临界区所有权   
  400.     EnterCriticalSection(&port->m_csCommunicationSync);   
  401.    
  402.     if (bWrite)   
  403.     {   
  404.         // 变量初始化   
  405.         port->m_ov.Offset = 0;   
  406.         port->m_ov.OffsetHigh = 0;   
  407.    
  408.         // 缓冲区刷新   
  409.         PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);   
  410.    
  411.         bResult = WriteFile(port->m_hComm,       // 串口句柄   
  412.                         port->m_szWriteBuffer,   // 写缓冲区   
  413.                         strlen((char*)port->m_szWriteBuffer),// 发送大小   
  414.                         &BytesSent,     // 被发送的字节   
  415.                         &port->m_ov);    // 重叠结构   
  416.    
  417.         // 处理错误码   
  418.         if (!bResult)     
  419.         {   
  420.             DWORD dwError = GetLastError();   
  421.             switch (dwError)   
  422.             {   
  423.                 case ERROR_IO_PENDING:   
  424.                     {   
  425.                         // GetOverlappedResults()函数继续   
  426.                         BytesSent = 0;   
  427.                         bWrite = FALSE;   
  428.                         break;   
  429.                     }   
  430.                 default:   
  431.                     {   
  432.                         // 其他错误码   
  433.                         port->ProcessErrorMessage("WriteFile()");   
  434.                     }   
  435.             }   
  436.         }    
  437.         else   
  438.         {   
  439.             LeaveCriticalSection(&port->m_csCommunicationSync);   
  440.         }   
  441.     } // bWrite结束   
  442.    
  443.     if (!bWrite)   
  444.     {   
  445.         bWrite = TRUE;   
  446.        
  447.         bResult = GetOverlappedResult(port->m_hComm,//串口句柄    
  448.                                       &port->m_ov,   //重叠结构   
  449.                                       &BytesSent,// 发送数量   
  450.                                       TRUE);    // 等待标志   
  451.    
  452.         LeaveCriticalSection(&port->m_csCommunicationSync);   
  453.    
  454.         // 处理错误码    
  455.         if (!bResult)     
  456.         {   
  457.             port->ProcessErrorMessage("GetOverlappedResults() in WriteFile()");   
  458.         }      
  459.     } // bWrite结束   
  460.    
  461.     // 校验发送的数量与要发送的是否一致   
  462.     if (BytesSent != strlen((char*)port->m_szWriteBuffer))   
  463.     {   
  464.         TRACE("WARNING: WriteFile() error.. Bytes Sent: %d; Message Length: %d\n", BytesSent, strlen((char*)port->m_szWriteBuffer));   
  465.     }   
  466. }   
  467.    
  468. //   
  469. // 字符收到,通知串口拥有者   
  470. //   
  471. void CSerialPort::ReceiveChar(CSerialPort* port, COMSTAT comstat)   
  472. {   
  473.     BOOL  bRead = TRUE;    
  474.     BOOL  bResult = TRUE;   
  475.     DWORD dwError = 0;   
  476.     DWORD BytesRead = 0;   
  477.     unsigned char RXBuff;   
  478.    
  479.     for (;;)    
  480.     {    
  481.         // 获取串口临界区的所有权,保证无其他的目标使用串口    
  482.            
  483.         EnterCriticalSection(&port->m_csCommunicationSync);   
  484.    
  485.         // ClearCommError() 更新结构 COMSTAT 清除其他的错误   
  486.            
  487.         bResult = ClearCommError(port->m_hComm, &dwError, &comstat);   
  488.    
  489.         LeaveCriticalSection(&port->m_csCommunicationSync);   
  490.    
  491.         // 死循环开始,直到读取所有字符   
  492.            
  493.         if (comstat.cbInQue == 0)   
  494.         {   
  495.             // 所有的字符读完,转出   
  496.             break;   
  497.         }   
  498.                            
  499.         EnterCriticalSection(&port->m_csCommunicationSync);   
  500.    
  501.         if (bRead)   
  502.         {   
  503.             bResult = ReadFile(port->m_hComm,//端口句柄    
  504.                                &RXBuff,     // 缓冲区   
  505.                                1,           // 读一个字符   
  506.                                &BytesRead,  // 发送数量   
  507.                                &port->m_ov);//重叠结构   
  508.             // 错误码处理    
  509.             if (!bResult)     
  510.             {    
  511.                 switch (dwError = GetLastError())    
  512.                 {    
  513.                     case ERROR_IO_PENDING:     
  514.                         {    
  515.                             // 异步在操作中    
  516.                             // 继续GetOverlappedResults()处理   
  517.                             bRead = FALSE;   
  518.                             break;   
  519.                         }   
  520.                     default:   
  521.                         {   
  522.                             // 其他错误码   
  523.                             port->ProcessErrorMessage("ReadFile()");   
  524.                             break;   
  525.                         }    
  526.                 }   
  527.             }   
  528.             else   
  529.             {   
  530.                 // ReadFile() 操作完毕 呼叫GetOverlappedResults()   
  531.                 //不一定必要   
  532.                 bRead = TRUE;   
  533.             }   
  534.         }  // bRead 结束   
  535.    
  536.         if (!bRead)   
  537.         {   
  538.             bRead = TRUE;   
  539.             bResult = GetOverlappedResult(port->m_hComm,//端口句柄    
  540.                                           &port->m_ov,   //重叠结构   
  541.                                           &BytesRead,   // 读字符数量   
  542.                                           TRUE); // 等待标志   
  543.    
  544.             // 处理错误码    
  545.             if (!bResult)     
  546.             {   
  547.                 port->ProcessErrorMessage("GetOverlappedResults() in ReadFile()");   
  548.             }      
  549.         }  // bRead结束   
  550.                    
  551.         LeaveCriticalSection(&port->m_csCommunicationSync);   
  552.    
  553.         // 通知父对象有字符收到   
  554.         ::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_RXCHAR, (WPARAM) RXBuff, (LPARAM) port->m_nPortNr);   
  555.     } // 死循环结束   
  556.    
  557. }   
  558.    
  559. //   
  560. // 向串口写字符串   
  561. //   
  562. void CSerialPort::WriteToPort(char* string)   
  563. {                                                         
  564.     assert(m_hComm != 0);   
  565.     memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));   
  566.     strcpy(m_szWriteBuffer, string);   
  567.    
  568.     // 设置写事件   
  569.     SetEvent(m_hWriteEvent);   
  570. }   
  571.    
  572. //   
  573. // 返回设备控制块   
  574. //   
  575. DCB CSerialPort::GetDCB()   
  576. {   
  577.     return m_dcb;   
  578. }   
  579.    
  580. //   
  581. // 返回通信事件屏蔽   
  582. //   
  583. DWORD CSerialPort::GetCommEvents()   
  584. {   
  585.     return m_dwCommEvents;   
  586. }   
  587.    
  588. //   
  589. // 返回写缓冲区大小   
  590. //   
  591. DWORD CSerialPort::GetWriteBufferSize()   
  592. {   
  593.     return m_nWriteBufferSize;   
  594. }   
  595.    
  596.    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值