在delphi线程中实现消息循环

Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供.花了两天的事件研究了一下win32的消息系统,写了一个线程内消息循环的测试.但是没有具体应用过,贴出来给有这方面需求的DFW参考一下.
希望大家和我讨论.

{-----------------------------------------------------------------------------
Unit Name: uMsgThread
Author:    xwing
eMail :    xwing@263.net ; MSN : xwing1979@hotmail.com
Purpose:   Thread with message Loop
History:

2003-6-19, add function to Send Thread Message.            ver 1.0
           use Event List and waitforsingleObject
           your can use WindowMessage or ThreadMessage
2003-6-18, Change to create a window to Recving message
2003-6-17, Begin.
-----------------------------------------------------------------------------}
unit uMsgThread;

interface
{$WARN SYMBOL_DEPRECATED OFF}
{$DEFINE USE_WINDOW_MESSAGE}
uses
   Classes, windows, messages, forms, sysutils;

type
   TMsgThread = class(TThread)
   private
       {$IFDEF USE_WINDOW_MESSAGE}
       FWinName    : string;
       FMSGWin     : HWND;
       {$ELSE}
       FEventList  : TList;
       FCtlSect    : TRTLCriticalSection;
       {$ENDIF}
       FException  : Exception;
       fDoLoop     : Boolean;
       FWaitHandle : THandle;
       {$IFDEF USE_WINDOW_MESSAGE}
       procedure MSGWinProc(var Message: TMessage);
       {$ELSE}
       procedure ClearSendMsgEvent;
       {$ENDIF}
       procedure SetDoLoop(const Value: Boolean);
       procedure WaitTerminate;

   protected
       Msg         :tagMSG;
       
       procedure Execute; override;
       procedure HandleException;
       procedure DoHandleException;virtual;
       //Inherited the Method to process your own Message
       procedure DoProcessMsg(var Msg:TMessage);virtual;
       //if DoLoop = true then loop this procedure
       //Your can use the method to do some work needed loop.        
       procedure DoMsgLoop;virtual;
       //Initialize Thread before begin message loop        
       procedure DoInit;virtual;
       procedure DoUnInit;virtual;

       procedure PostMsg(Msg:Cardinal;wParam:Integer;lParam:Integer);
       //When Use SendMsg method Must not use Synchronize Method in ThreadLoop !!!
       //otherwise will caurse DeadLock
       procedure SendMsg(Msg:Cardinal;wParam:Integer;lParam:Integer);
       
   public
       constructor Create(Loop:Boolean=False;ThreadName: string='');
       destructor destroy;override;
       procedure AfterConstruction;override;

       //postMessage to Quit,and Free(if FreeOnTerminater = true)
       //can call this in thread loop, don't use terminate property.
       procedure QuitThread;
       //PostMessage to Quit and Wait, only call in MAIN THREAD
       procedure QuitThreadWait;
       //just like Application.processmessage.
       procedure ProcessMessage;
       //enable thread loop, no waitfor message
       property DoLoop: Boolean read fDoLoop Write SetDoLoop;

   end;

implementation

{ TMsgThread }
{//////////////////////////////////////////////////////////////////////////////}
constructor TMsgThread.Create(Loop:Boolean;ThreadName:string);
begin
   {$IFDEF USE_WINDOW_MESSAGE}
   if ThreadName <> '' then
       FWinName := ThreadName
   else
       FWinName := 'Thread Window';
   {$ELSE}
   FEventList := TList.Create;
   InitializeCriticalSection(fCtlSect);
   {$ENDIF}

   FWaitHandle := CreateEvent(nil, True, False, nil);

   FDoLoop := Loop;            //default disable thread loop
   inherited Create(False);    //Create thread
   FreeOnTerminate := True;    //Thread quit and free object

   //Call resume Method in Constructor Method
   Resume;
   //Wait until thread Message Loop started    
   WaitForSingleObject(FWaitHandle,INFINITE);
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.AfterConstruction;
begin
end;

{------------------------------------------------------------------------------}
destructor TMsgThread.destroy;
begin
   {$IFDEF USE_WINDOW_MESSAGE}
   {$ELSE}
   FEventList.Free;
   DeleteCriticalSection(FCtlSect);
   {$ENDIF}
   
   inherited;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.Execute;
var
   mRet:Boolean;
   aRet:Boolean;
   {$IFNDEF USE_WINDOW_MESSAGE}
   uMsg:TMessage;
   {$ENDIF}
begin
{$IFDEF USE_WINDOW_MESSAGE}
   FMSGWin := CreateWindow('STATIC',PChar(FWinName),WS_POPUP,0,0,0,0,0,0,hInstance,nil);
   SetWindowLong(FMSGWin, GWL_WNDPROC, Longint(MakeObjectInstance(MSGWinProc)));
{$ELSE}
   PeekMessage(Msg,0,WM_USER,WM_USER,PM_NOREMOVE); //Force system alloc a msgQueue
{$ENDIF}

   //notify Conctructor can returen.
   SetEvent(FWaitHandle);
   CloseHandle(FWaitHandle);

   mRet := True;
   try
       DoInit;
       while mRet do   //Message Loop
       begin
           if fDoLoop then
           begin
               aRet := PeekMessage(Msg,0,0,0,PM_REMOVE);
               if aRet and (Msg.message <> WM_QUIT) then
               begin
                   {$IFDEF USE_WINDOW_MESSAGE}
                   TranslateMessage(Msg);
                   DispatchMessage(Msg);
                   {$ELSE}
                   uMsg.Msg := Msg.message;
                   uMsg.wParam := Msg.wParam;
                   uMsg.lParam := Msg.lParam;
                   DoProcessMsg(uMsg);
                   {$ENDIF}

                   if Msg.message = WM_QUIT then
                       mRet := False;
               end;
               {$IFNDEF USE_WINDOW_MESSAGE}
               ClearSendMsgEvent;      //Clear SendMessage Event                
               {$ENDIF}
               DoMsgLoop;
           end
           else begin
               mRet := GetMessage(Msg,0,0,0);
               if mRet then
               begin
                   {$IFDEF USE_WINDOW_MESSAGE}
                   TranslateMessage(Msg);
                   DispatchMessage(Msg);
                   {$ELSE}
                   uMsg.Msg := Msg.message;
                   uMsg.wParam := Msg.wParam;
                   uMsg.lParam := Msg.lParam;
                   DoProcessMsg(uMsg);
                   ClearSendMsgEvent;      //Clear SendMessage Event
                   {$ENDIF}
               end;
           end;
       end;
       DoUnInit;
       {$IFDEF USE_WINDOW_MESSAGE}
       DestroyWindow(FMSGWin);
       FreeObjectInstance(Pointer(GetWindowLong(FMSGWin, GWL_WNDPROC)));
       {$ENDIF}
   except
       HandleException;
   end;
end;

{------------------------------------------------------------------------------}
{$IFNDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.ClearSendMsgEvent;
var
   aEvent:PHandle;
begin
   EnterCriticalSection(FCtlSect);
   try
       if FEventList.Count <> 0 then
       begin
           aEvent := FEventList.Items[0];
           if aEvent <> nil then
           begin
               SetEvent(aEvent^);
               CloseHandle(aEvent^);
               Dispose(aEvent);
           end;
           FEventList.Delete(0);
       end;
   finally
       LeaveCriticalSection(FCtlSect);
   end;
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.HandleException;
begin
   FException := Exception(ExceptObject);  //Get Current Exception object
   try
       if not (FException is EAbort) then
           inherited Synchronize(DoHandleException);
   finally
       FException := nil;
   end;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.DoHandleException;
begin
   if FException is Exception then
       Application.ShowException(FException)
   else
       SysUtils.ShowException(FException, nil);
end;

{//////////////////////////////////////////////////////////////////////////////}
{$IFDEF USE_WINDOW_MESSAGE}
procedure TMsgThread.MSGWinProc(var Message: TMessage);
begin
   DoProcessMsg(Message);
   with Message do
       Result:=DefWindowProc(FMSGWin,Msg,wParam,lParam);
end;
{$ENDIF}

{------------------------------------------------------------------------------}
procedure TMsgThread.DoProcessMsg(var Msg:TMessage);
begin
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.ProcessMessage;
{$IFNDEF USE_WINDOW_MESSAGE}
var
   uMsg:TMessage;
{$ENDIF}
begin
   while PeekMessage(Msg,0,0,0,PM_REMOVE) do
   if Msg.message <> WM_QUIT then
   begin
       {$IFDEF USE_WINDOW_MESSAGE}
       TranslateMessage(Msg);
       DispatchMessage(msg);
       {$ELSE}
       uMsg.Msg := Msg.message;
       uMsg.wParam := Msg.wParam;
       uMsg.lParam := Msg.lParam;
       DoProcessMsg(uMsg);
       {$ENDIF}
   end;
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.DoInit;
begin
end;

procedure TMsgThread.DoUnInit;
begin
end;

procedure TMsgThread.DoMsgLoop;
begin
   Sleep(1);
end;

{//////////////////////////////////////////////////////////////////////////////}
procedure TMsgThread.QuitThread;
begin
   {$IFDEF USE_WINDOW_MESSAGE}
   PostMessage(FMSGWin,WM_QUIT,0,0);
   {$ELSE}
   PostThreadMessage(ThreadID,WM_QUIT,0,0);
   {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.QuitThreadWait;
begin
   QuitThread;
   WaitTerminate;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SetDoLoop(const Value: Boolean);
begin
   if Value = fDoLoop then Exit;
   fDoLoop := Value;
   if fDoLoop then
       PostMsg(WM_USER,0,0);
end;

{------------------------------------------------------------------------------}
//Can only call this method in MAIN Thread!!
procedure TMsgThread.WaitTerminate;
var
   xStart:Cardinal;
begin
   xStart:=GetTickCount;
   try
       //EnableWindow(Application.Handle,False);
       while WaitForSingleObject(Handle, 10) = WAIT_TIMEOUT do
       begin
           Application.ProcessMessages;
           if GetTickCount > (xStart + 4000) then
           begin
               TerminateThread(Handle, 0);
               Beep;
               Break;
           end;
       end;
   finally
       //EnableWindow(Application.Handle,True);
   end;
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.PostMsg(Msg: Cardinal; wParam, lParam: Integer);
begin
   {$IFDEF USE_WINDOW_MESSAGE}
   postMessage(FMSGWin,Msg,wParam,lParam);
   {$ELSE}
   EnterCriticalSection(FCtlSect);
   try
       FEventList.Add(nil);
       PostThreadMessage(ThreadID,Msg,wParam,lParam);
   finally
       LeaveCriticalSection(FCtlSect);
   end;
   {$ENDIF}
end;

{------------------------------------------------------------------------------}
procedure TMsgThread.SendMsg(Msg: Cardinal; wParam, lParam: Integer);
{$IFNDEF USE_WINDOW_MESSAGE}
var
   aEvent:PHandle;
{$ENDIF}
begin
   {$IFDEF USE_WINDOW_MESSAGE}
   SendMessage(FMSGWin,Msg,wParam,lParam);
   {$ELSE}
   EnterCriticalSection(FCtlSect);
   try
       New(aEvent);
       aEvent^ := CreateEvent(nil, True, False, nil);
       FEventList.Add(aEvent);
       PostThreadMessage(ThreadID,Msg,wParam,lParam);
   finally
       LeaveCriticalSection(FCtlSect);
   end;
   WaitForSingleObject(aEvent^,INFINITE);
   {$ENDIF}
end;


end.


我参考了一下msdn,还有windows核心编程.
写了一个类来封装这个功能,不知道对不对.
里面使用了两个方法,一个使用一个隐含窗体来处理消息
还有一个是直接使用thread的消息队列来处理,但是这个时候sendmessage无法工作,所以我自己设想了一个方法,虽然不完全达到了要求但是我简单测试了一下,好像还能工作.

切换两种工作方式要修改编译条件
{$DEFINE USE_WINDOW_MESSAGE} 使用隐含窗体来处理消息
{-$DEFINE USE_WINDOW_MESSAGE} 使用线程消息队列来处理消息

还有我想要等待线程开始进行消息循环的时候create函数才返回.但是现在好像还没有这样(用一个事件来处理).只是开始进入了threadexecute函数,线程的create就返回了.可能会出问题.

 
通过设置 DoLoop属性可以设定线程是否循环(不阻塞等待消息),这样派生类线程在循环做一些其他事情的同时还可以接受消息. 例如:派生类里面循环发送缓冲区的数据,还可以响应其他线程发送过来的消息(如停止,启动,退出,等等)


我一般在线程中需要使用消息循环时是直接用
if (PeekMessage(msg,0,0,0,PM_REMOVE)) then
 begin
   // 这里对特定的已知消息进行处理
 end
else
 begin
   TranslateMessage(Msg);
   DispatchMessage(Msg);
 end;
这样进行,实践证明是可行的。你的代码好象也是这样进行,而且更详细,我觉得肯定不错。    

来源:http://www.delphibbs.com/keylife/iblog_show.asp?xid=26346

转载于:https://www.cnblogs.com/railgunman/archive/2010/08/25/1808554.html

Delphi中高效实现延时与消息处理的方法 本文还有配套的精品资源,点击获取 简介:在Delphi编程中,延时函数是实现程序暂停的重要工具,广泛应用于动画、定时任务等场景。本文介绍了使用内置 Sleep 函数和自定义消息循环(如 Application.ProcessMessages )的延时方法,强调了在多线程环境下保持UI响应性和程序高效运行的重要性。对于Delphi开发者来说,理解W... 阅读详情

相关推荐

delphi2010实现windows系统MSNPopup弹窗

实现了在Windows XP/7 操作系统的桌面右下角弹出类似MSN的popup的提示框的功能,压缩包文件是完整的delphi2010工程代码。可以在delphi2010中直接打开编译。弹窗支持自动定时关闭、自动堆叠显示,自定义背景图片等功能。 直接使用了DELPHI盒子(2ccc)的MSNPopup 5.0的组件。修改了背景颜色(图片)不能填满窗体的bug。希望能够帮到您!

Delphi中的Windows消息处理

这里采用自定义消息的方法。注意:delphi中自定义消息值范围为:WM_USER到$7FFF之间。 1.应用程序中的消息(在同一个应用程序的不同Form间进行消息处理)示例:Form1:unit Unit1;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,

【昆山人在上海】 868

Delphi TMsnPopup 控件代码,仿MSN提示窗口.rar

Delphi TMsnPopup 控件代码,仿MSN提示窗口,用来在Delphi中实现提示框的小控件,下载包内是代码,没有包括示例。文件列表:MsnPopup.dcr、MsnPopup.pas。

类似MSN、Foxmail消息提示控件

类似MSN、Foxmail消息提示控件 整理日期:2004年10月25日 运行环境:Windows环境 授权形式:共享软件 推荐程度:  下载次数:519 文件尺寸:231K 下载类型:源码下载--Delphi源码 作    者:未知  下载链接:链接1   下载简介:类似于MSN、Foxmail的消息提示控件源代码,可以定时消失,和MSN或Foxmail的一样,送给广大有需要的朋友。

净心编程 1603

TMsnPopup v5.0 (提示窗)

风格与MSN提示窗相似的小控件,偶从国外的站上拉下来的组件,很好用哦,可以设置漂亮的背景图片.

【验证技能树】UVM 源码解读01 -- UVM本质上就是软件工程方法论在芯片验证领域的应用

文章摘要: UVM(通用验证方法学)是基于SystemVerilog的芯片验证框架,采用组件化、面向对象和事务级抽象设计。其核心在于职责分离与数据流,通过Driver、Monitor、Sequencer等模块协作实现高效验证。UVM强调复用性、可扩展性和可维护性,类似软件工程的模块化与低耦合原则。理解其体系结构比记忆API更重要,有助于灵活组合组件并提升验证效率。UVM不仅是工具,更融合了软件工程思维,推动验证工作向自动化、系统化发展。掌握UVM底层逻辑能优化验证环境设计,适用于从IP到SoC的多层级验证场

qq_45869788的博客 1040

独立线程实现消息循环的Delphi定时器类

Delphi的定时器类TTimer内部是创建不可见的窗口,调用SetTimer这个Win32 API 来实现的。TTimer要求使用它的线程实现了消息循环。所以,除非控制台程序中实现消息循环,否则TTimer在控制台程序中是无效的。不过为了使用方便,可以我们写一个线程类来实现消息循环,并且不必创建隐藏窗口便可以使用定时器。下面面的TSfTimerThread就是这样的类,它可以在不终止线程的情况下

6052

stm32控制舵机_使用URT-1控制飞特舵机上手教程(SM40BL舵机为例)

使用URT-1控制飞特舵机上手教程(SM40BL舵机为例):1,参阅URT-1使用说明.pdf2, 材料清单:① URT-1驱动板② 连接驱动板与电脑的MINI USB 连接线③ 给舵机供电的电源④ 舵机与舵机连接线⑤ 杜邦线(可选)3,将URT-1驱动板与电脑连接4,安装驱动,参阅文件CH340驱动,检查设备管理器串口号。5,舵机连接URT-1驱动板,驱动板接电源,参考下图:6,打开FD软件进行...

weixin_39564510的博客 3432

TMsgThread, TCommThread -- 在delphi线程中实现消息循环

http://delphi.cjcsoft.net//viewthread.php?tid=635 在delphi线程中实现消息循环 在delphi线程中实现消息循环Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供. 花了两天的事件研究了一下win32的消息系统,写了一个线程内消息循环的测试. 但是没有具体应用过,贴出来给...

weixin_30335575的博客 191

从零构建C++ Gerber解析器:以PCB缺陷检测为目标的实战指南

本文是一份面向PCB缺陷检测项目的实战指南,详细介绍了如何从零开始使用C++构建高性能的Gerber文件解析器。文章深入剖析了Gerber RS-274X格式,并提供了完整的开发环境搭建、核心数据结构设计、解析步骤实现以及为缺陷检测优化的具体方案,旨在帮助开发者获得对PCB制造数据的深度控制与极致性能。

weixin_29291185的博客 184

【零基础】手把手教你使用Docker部署Springboot项目(详细版)

还不会使用Docker部署Springboot项目的小伙伴,不妨进来看看呗,希望文中内容对你有所帮助!

fengprogrammer的博客 2万+

TCommThread -- 在delphi线程中实现消息循环

http://www.techques.com/question/1-4073197/How-do-I-send-and-handle-message-between-TService-parent-thread-and-child-thread? I took a look at OmniThreadLibrary and it looked like overkill for my purp...

weixin_34007020的博客 429

Unable to negotiate with XXXX port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss

打开ssh_config此文件,滑动到最下面,新增自己git主机地址(这里我直接默认*)执行命令 git clone git@xxxxxxxxxxxxx.git。代码仓库已经添加了ssh公钥之后,克隆代码到本地时就报了这个问题。首先是找到git安装目录(根据自己安装时选项,找到这个目录)再进入ssh目录,找到 ssh_config 配置文件。配置完成后,保存配置文件后再次去clone代码。直接复制下面这一段到文件中最底部位置即可。然后进入 etc目录,找到ssh目录。终于可以克隆下来了。

萌兔兔MMQ!! 3355

Delphi中使用Win32 API创建内建消息循环的线程函数

Delphi中使用Win32 API创建内建消息循环的线程函数   1.线程创建    HANDLE CreateThread(     LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes       DWORD dwStackSize, // initial thre

gencheng的专栏 3801

Hypermesh到Abaqus CEL网格转换全攻略:解决欧拉体导入难题

本文详细解析了Hypermesh到Abaqus CEL网格转换的全流程,重点解决欧拉体导入难题。通过单元类型转换、INP文件编辑和多组件合并技术,实现流固耦合仿真的高效前处理。文章包含实用脚本和工程案例,帮助工程师突破ABAQUS CEL分析中的网格转换瓶颈。

weixin_30832143的博客 190

EMD (经验模态分解) 在 C++ 中的实现指南

EMD 是由 Norden E. Huang 等人在1998年提出的一种适用于非线性和非平稳信号的分析方法。对称性:IMF 的上下包络对称。零交叉点:IMF 的零交叉点数量与极值点数量相同或相差一个。本文详细介绍了如何在C++中实现EMD,包括理论背景、算法步骤、代码实现和优化等内容。通过丰富的代码示例和详细的解释,希望能够帮助读者掌握EMD的基本原理,并能够在实际项目中灵活应用。未来,随着信号处理技术的不断发展,EMD及其变种(如EEMD、CEEMDAN等)将继续在各个领域发挥重要作用。

qq_38334677的博客 1749

给线程发送消息让它执行不同的处理

unit Unit2; interface uses System.Classes, Windows, Messages; const WM_DO = WM_USER + 1; type TDemoThread = class(TThread) private { Private declarations } protected procedure Execute; over...

weixin_34008933的博客 174

【小程序抓取】解决痛点:如何用 AI-RPA 工具抓取无法获取 URL 的微信小程序数据?

本文探讨了传统爬虫技术在微信小程序数据采集中的局限性,并提出了AI-RPA(人工智能驱动的机器人流程自动化)这一前沿解决方案。随着微信小程序安全防护体系的全面升级(包括证书锁定、加密传输和三级防御机制),传统"抓包-模拟请求"的方式已基本失效。文章对比了Reqable、Fiddler等主流抓包工具的优劣,通过某程旅行小程序案例展示了传统抓包的技术流程和动态参数处理的难点。最终指出,2026年最有效的解决方案是采用AI-RPA技术,通过视觉识别、鼠标键盘模拟、OCR和AI智能体编排的组合方案,直接从小程序UI

ZDQ58818的博客 123

学习win32API-消息处理

消息队列与消息循环(摘自win32API参考) delphi通过TApplication对象自动对消息进行处理,每一个线程都有自己的消息队列,通过TApplication.ProcessMessages方法实现程序的消息循环: {Forms} procedure TApplication.ProcessMessages; var Msg: TMsg; begin whi...

weixin_34247299的博客 84

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Delphi Thread 线程代码分析(转载)

====================================================== 注:重要!程序员如何有效的放松身心!下班后做什么? ====================================================== delphi中有一个线程类tthread是用来实现多线程编程的,这个绝大多数delphi书藉都有说到,但基本上都是对

93

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Delphi - TTimer的源码

====================================================== 注:重要!用眼过度需要养眼,点此养眼! ====================================================== china-pub 计算机绝版图书按需印刷服务 =======================================

90

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

上一篇: Fastreport使用经验(转)在Delphi程序中访问报表对象
下一篇: 等待外部应用程序的执行结果
aowo7144
博客等级 码龄10年 5粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值