使用多个第三方类库的问题(DLL内存分配与共享)

C++类库开发详解 前言:这是一篇总结性的文章,需要有一点C++和dll基本知识的基础,在网上查阅了很多资料感觉没有一篇详细、具体、全面的dll开发介绍,我这是根据最近项目和网上资料整理出来的,并附带实例的一个总结性的文章(由于篇幅较长故不附带源码解释)。另外,个人愚昧地认为以后C++的开发会更多地面向库的开发,所以学会库的开发必不可少。 阅读详情

1.       无论这些第三方库是静态库还是动态库,或者同时有静态库和动态库(可以同时使用),必须保证在生成这些库时,使用的C Runtime库是同一个版本(/MT, /MTd, /MD,或者/MDd)【如果是VC的话,在项目属性->配置属性->C/C++->Code Generate->Runtime Library中设置】。 否则,在编译可执行文件时,会出现重复定义的问题,这是因为C Runtime库的不同版本之间的冲突造成的。所以,如果你要提供一个第三方库给别人使用,最好提供使用了不同C Runtime库的多个版本(/MT, /MTd, /MD,或者/MDd),以供选择。我推荐使用/MD和/MDd,原因下面第4节中讲。

 

2.       一旦DLL的文件映像被映射到调用进程的地址空间中,DLL的函数就可以供进程中运行的所有线程使用。实际上,DLL几乎将失去它作为DLL的全部特征。 对于进程中的线程来说,DLL的代码和数据看上去就像恰巧是在进程的地址空间中的额外代码和数据一样。当一个线程调用DLL函数时,该DLL函数要查看线程的堆栈,以便检索它传递的参数,并将线程的堆栈用于它需要的任何局部变量。此外, DLL中函数的代码创建的任何对象均由调用线程所拥有,而DLL本身从来不拥有任何东西。 

  如你所知,可执行文件的全局变量和静态变量不能被同一个可执行文件的多个运行实例共享。Windows98能够确保这一点,方法是在可执行文件被映射到进程的地址空间时为可执行文件的全局变量和静态变量分配相应的存储器。Windows2000确保这一点的方法是使用第13章介绍的写入时拷贝 (copy-on-write)机制。DLL中的全局变量和静态变量的处理方法是完全相同的。当一个进程将DLL的映像文件映射到它的地址空间中去时,系统将同时创建全局数据变量和静态数据变量的实例。

必须注意的是,单个地址空间是由一个可执行模块和若干个DLL模块组成的。这些模块中,有些可以链接到静态版本的C/C++运行期库,有些可以链接到一个DLL版本的C/C++运行期库,而有些模块(如果不是用C/C++编写
的话)则根本不需要C/C++运行期库。许多开发人员经常会犯一个常见的错误,因为他们忘记了若干个C/C++运行期库可以存在于单个地址空间中。请看下面的代码:  

VOID   EXEFunc(){  PVOID pv = DLLFunc();  free(pv);}  
PVOID  DLLFunc() {  return(malloc(100)); }

  那么你是怎么看待这个问题的呢?上面这个代码能够正确运行吗?DLL函数分配的内存块是由EXE的函数释放的吗?答案是可能的。上面显示的代码并没有 为你提供足够的信息。如果EXE和DLL都链接到DLL的C/C++运行时库(/MD),那么上面的代码将能够很好地运行。  但是,如果两个模块中的一个或者两个都链接到静态C/C++运行期库(/MT),那么对free函数的调用就会失败(原因下面第三节解释)。 我经常看到编程人员编写这样的代码,结果都失败了。

  有一个很方便的方法可以解决这个问题。当一个模块提供一个用于分配内存块的函数时,该模块也必须提供释放内存的函数。让我们将上面的代码改写成下面的样子:  
VOID   EXEFunc(){   PVOID   pv   =   DLLFunc();   DLLFreeFunc(pv);}
PVOID   DLLFunc(){   PVOID   pv   =   malloc(100);   return(pv);}  
BOOL   DLLFreeFunc(PVOID   pv){   return(free(pv));}  

  上面这个代码是正确的,它始终都能正确地运行。当你编写一个模块时,不要忘记其他模块中的函数也许没有使用C/C++来编写,因此可能无法使用malloc和free函数进行内存的分配。应该注意不要在代码中使用这些假设条件。

另外,在内部调用malloc和free函数时,这个原则对于C++的new和delete操作符也是适用的。

3.       问题:
1.“如果两个模块中的一个或者两个都链接到静态C/C++运行期库,那么对free函数的调用就会失败”,为什么?   静态CRT library和动态CRT library有什么区别?

答:因为malloc/free, new/delete   都是调用HeapAlloc/HeapFree   来实现来实现内存分配是释放的。查看Windows的API可以看到,这两个函数都需要一个Heap的HANDLE做为参数。CRT   库采用了全局变量来保存这个HANDLE。如果是静态链接, CRT库的代码会链接到各个dll中去,也包括这个全局变量。也就是说,每个静态链接的dll都有一个自己的全局堆句柄,他们自己都在这个句柄上使用内存。当一个dll释放另外一个dll分配的内存时由于使用的堆句柄不一致于是出错。当使用动态链接时,有于每个dll 都是去调用CRT库的dll函数来分配和释放内存的,使用的是同一个句柄,所以就没有这个问题。
2.   “DLL中函数的代码创建的任何对象均由调用线程所拥有,而DLL本身从来不拥有任何东西”, DLL中的全局变量怎么解释?由多个应用程序使用该DLL时,DLL中的全局变量需要加上同步机制吗?

答:不同进程之间对   dll   全局变量的访问操作系统可以区分的,平时大家共用一份,当某个进程改了某个全局变量时,操作系统会自动为这个进程生成一份这个变量的拷贝,(copy- on-write)让这个进程访问拷贝的内容而不会影响其它进程中这个变量的值,所以不需要手动做同步。

 

4.       总结

综上所述,要在同一个DLL中对变量进行内存分配和释放的根本原因在于:每个DLL都要保存一份全局变量和静态变量的拷贝;CRT库中将堆的句柄保存为一个全局变量,而堆中内存的分配和释放都要使用这个句柄。

如果可执行程序和DLL都使用动态链接的CRT库(/MD,/MDd),那么CRT库只初始化一次(在其DLL中),每次内存分配和释放也只在CRT的DLL库中进行,这样使用唯一一个堆的句柄,就不会导致错误。

如果有一个DLL(称为D1)使用静态链接的CRT库(/MT,/MTd),可执行程序也使用静态链接的,那么D1中有一个堆的句柄,可执行程序中也有一个堆的句柄,两者进行内存分配和释放时使用各自的堆的句柄。那么就必须保证使用D1分配的内存,必须由D1释放,否则就会出错。这也就是1中我推荐使用/MD的原因。

 

 

////////////////////////////////////////////////////////////////////////////////////////////////////

//文章被翻来覆去的。。。

//上面可能是原文,下面可能是转载人加上的,不过感谢他们的劳动成果,

//拿下来慢慢看~

////////////////////////////////////////////////////////////////////////////////////////////////////

1.因为   malloc/free,   new/delete   都是调用   HeapAlloc/HeapFree   来实现来实现内存分配是释放的。查看   Windows   的   API   可以看到,这两个函数都需要一个   Heap   的   HANDLE   做为参数。CRT   库采用了全局变量来保存这个   HANDLE。如果是静态链接,   CRT   库的代码会链接到各个   dll   中去,也包括这个全局变量。也就是说,每个静态链接的   dll   都有一个自己的全局堆句柄,他们自己都在这个句柄上使用内存。当一个   dll   释放另外一个   dll   分配的内存时由于使用的堆句柄不一致于是出错。当使用动态链接时,有于每个   dll   都是去调用   CRT   库的   dll   函数来分配和释放内存的,使用的是同一个句柄,所以就没有这个问题。

2.   不同进程之间对   dll   全局变量的访问操作系统可以区分的,平时大家共用一份,当某个进程改了某个全局变量时,操作系统会自动为这个进程生成一份这个变量的拷贝,(copy- on-write)让这个进程访问拷贝的内容而不会影响其它进程中这个变量的值,所以不需要手动做同步。

 

在网上找到一段文字,再结合adlay的解释基本上清楚了.
http://dev.youkuaiyun.com/author/houdy/9f4bb2dc376d437787032971ee0eff97.html
四.malloc/free
  这两个函数是使用频率最高的两个函数,由于他们是标准C库中的一部分,所以具有极高的移植性。这里的"移植性"指的是使用他们的代码可以在不同的平台下编译通过,而不同的平台下的C Run-Time Library的具体实现是平台相关的,在Windows平台的C Run-Time Library中的malloc()和free()是通过调用Heap Memory API来实现的。值得注意的是C Run-Time Library拥有独立的Heap对象,我们知道,当一个应用程序初始化的时候,首先被初始化的是C Run-Time Library,然后才是应用程序的入口函数,而Heap对象就是在C Run-Time Library被初始化的时候被创建的。对于动态链接的C Run-Time Library,运行库只被初始化一次,而对于静态连接的运行库,每链接一次就初始化一次,所以对于每个静态链接的运行库都拥有彼此不同的Heap 对象。这样在某种情况下就会出问题,导致程序崩溃,例如一个应用程序调用了多个DLL,除了一个DLL外,其他的DLL,包括应用程序本身动态连接运行库,这样他们就使用同一个Heap对象。而有一个DLL使用静态连接的运行库,它就拥有一个和其他DLL不同的Heap 对象,当在其他DLL中分配的内存在这个DLL中释放时,问题就出现了。

VS2022 C#调用外部DLL中的方法—详解 文章目录1. DLL2. 生成DLL类库3. 调用DLL3.1 添加DLL引用3.2 调用DLL中的方法 1. DLL 动态链接库(Dynamic Linked Library):将写好的函数存在库中,以供其他程序开发调用,调用方式为“动态的”。 2. 生成DLL类库 3. 调用DLL 3.1 添加DLL引用 3.2 调用DLL中的方法 using System; using System.Collections.Generic; using System.Linq; using Sys 阅读详情

相关推荐

【源码】实践客户端调用DLL类库并获取配置资源

本文还有配套的精品资源,点击获取 简介:在.NET框架下,DLL类库作为一种可重用代码组件,可以实现代码和数据的共享,提升执行效率。文章详细阐述了客户端如何调用DLL类库项目并获取其中的App.config配置文件资源,包括创建DLL类库、暴露接口、添加引用、调用DLL方法、处理配置文件以及测试与调试的完整流程。 1. .NET框架中DLL类库作用 1.1 ...

weixin_33245447的博客 1252

c++跨动态库DLL的内存分配与释放问题

先说结论: 1、如果两个DLL(或者EXE调用DLL)的CRT链接均为MD,则可以跨动态库分配和释放,如果一个是MT,另外一个是MD则会有问题。 2、利用虚函数的动态绑定技术,动态绑定分配释放内存的new和delete等,可以解决这个问题,例如shared_ptr。但如果shared_ptr包装是vector等类型,在调用和被调用中都涉及到vector的修改的话,仍然会有问题,因为两个...

weixin_30268921的博客 1130

C# DLL生成与调用操作详解

动态链接库(DLL)是Windows应用程序中使用的一种核心机制,它允许程序共享代码和资源。DLL包含可由多个程序同时使用的代码和数据,而无需将它们包含在每个程序的单独文件中。这样做不仅可以节省磁盘空间,还能减少内存消耗,并提高应用程序的性能和灵活性。命名空间是C#中用于组织代码的逻辑容器。它提供了一种将相关的类、接口、结构和其他命名空间分组的方法,以避免类名之间的冲突,并且使得代码更加易于管理和理解。定义命名空间:在C#中,使用关键字namespace来定义一个新的命名空间。

weixin_42126677的博客 1758

dll中的内存管理

C++编写可供C#调用的算法模块,算法有实时性要求,而且涉及大量的处理数据(摄像头采集的多帧大分辨率图像) 需要学习如何编写规范的、零缺陷的、低耦合的dll代码 符合规范的dll 别人的代码,不同的开发语言(C#、java等)可以通过标准的接口调用dll 零缺陷的dll 开发过程不要留下后遗症 注意细节,如动态内存的管理,不要产生野指针 低

Kelvin_Yan的专栏 3078

COM组件与DLL制作及调用示例教程

本文还有配套的精品资源,点击获取 简介:在Windows开发中,DLL是用于共享代码和资源的组件,而COM技术则提供了一种实现跨语言组件互操作性的标准。本教程将介绍DLL和COM组件的基础知识,包括如何创建和使用DLL,特别是通过COM接口的实例。教程内容包括FirstCom示例DLL的创建、MFC类库在DLL调用中的应用,以及控制台程序对DLL的调用方式。通过实例源码的分...

weixin_35899324的博客 2323

掌握C#中DLL的创建与调用

在类库项目中,定义公共类和方法需要遵循以下原则:最小化公开接口:仅公开必要的功能,避免过度公开内部实现细节。使用访问修饰符:合理使用publicinternal等访问修饰符,以控制类和成员的可见性。遵循单一职责原则:每个类只负责一项任务,确保类的简单和集中。编写详细的XML文档注释:为公共类、方法和属性编写XML文档注释,以提供必要的使用说明和维护性。例如,定义一个公共类和其公共方法:/// 数据访问类,提供数据获取和操作的方法。

weixin_42315569的博客 1282

vc 定位dll 内存占用_Windows下C语言程序的内存布局(内存模型)

在32位环境下,Windows 默认会将高地址的 2GB 空间分配给内核(也可以配置为1GB),而将剩下的 2GB 空间分配给用户程序。不像 Linux,Windows 是闭源的,有版权保护,资料较少,不好深入研究每一个细节,至今仍有一些内部原理不被大家知晓。关于 Windows 地址空间的内存分布,官网上只给出了简单的说明:对于32位程序,内核占用较高的 2GB,剩下的 2GB 分配给...

weixin_39637975的博客 434

共享内存(DLL)

使用动态库实现内存共享的例子,动态库的大小和共享内存空间大小有关,使用起来不是太方便。

C++运行时内存的分配(堆、栈等)、DLL有关的CRT堆

程序运行时分配的内存位置 栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等,其操作方法类似数据结构中的栈。存放的是局部变量(在方法和语句块内部定义的变量),当方法和语句块一结束,空间马上释放。 堆区(heap):一般由程序员分配释放,与数据结构中的堆毫无关系,分配方式类似于链表。存放的是成员变量(也叫属性,在方法外,在类里,定义的变量),随着对象而产生,随对象销毁而销毁...

chen_i 1252

两个DLL文件共享内存数据块

procedure TForm1.Button1Click(Sender: TObject); {设置键盘钩子} begin EnableKeyHook; //设置钩子 Button1.Enabled:=False; Button2.Enabled:=True; Button3.Enabled:=True; button4.Enabled:=True; Panel1.Caption:='键盘钩子已经设置'; end; procedure TForm1.Button2Click(Sender: TObject); begin DisableKeyHook; //卸载钩子 Button1.Enabled:=True; Button2.Enabled:=False; Button3.Enabled:=False; Button4.Enabled:=False; Panel1.Caption:='键盘钩子已经设置'; end;

STM32F103移植NuttX实战:从零搭建最小系统到串口Shell(附完整配置文件)

本文详细介绍了在STM32F103C8T6开发板上移植NuttX实时操作系统的完整过程,包括环境搭建、BSP配置、系统时钟初始化、驱动裁剪、编译烧录及串口Shell交互。通过实战指南帮助开发者快速掌握NuttX移植技巧,解决典型问题,适用于嵌入式系统开发。

tree8的博客 292

C++内存分配的三种方式

不同的内存分配方式,有不同的内存调度机制。C++ 语言的三种内存分配方式分别是:静态存储区分配、栈内存分配和堆内存分配。 静态存储区分配 静态分配方式下的内存在程序编译的时候就已经分配好了且存在于程序的整个运行过程。例如 static 变量,其生命周期随程序的结束而结束,而非像一般变量随函数或作用域的结束而结束。 除了 static 变量,还有一种全局对象 static object,也可以存放在静态存储区。 栈内存分配 栈内存分配方式下的内存是存在于某作用域的一块空间。例如调用某个函数时,函数内局部变量的

qq_35120460的博客 2108

手把手教你用SlowFast训练自定义手势识别数据集(附FFmpeg抽帧技巧)

本文提供了一份基于SlowFast架构训练自定义手势识别数据集的完整实战指南。文章重点剖析了数据准备的核心挑战,详细讲解了利用FFmpeg进行高、低帧率抽帧的技巧,并介绍了构建半自动标注流水线、生成符合SlowFast要求的AVA格式数据集文件的具体方法。最后,针对手势识别场景,给出了关键的模型训练配置与调优建议,帮助开发者高效完成从数据到模型的全流程。

s1t2u3的博客 909

C# 中 DLL 和 类库的使用与区别

是 Windows 操作系统中的一种可执行文件格式,用于存储共享代码和资源,供其他应用程序调用。

skvjw的博客 1179

RS485/CAN总线防护电路设计:TVS选型与ESD防护实战解析

本文深入解析了RS485与CAN总线防护电路设计中的TVS选型与ESD防护实战技巧。针对工业现场常见的静电放电、浪涌等干扰,文章详细阐述了TVS管关键参数的选择、多级防护体系的构建,并特别强调了在隔离电路设计中地回路处理的重要性,帮助工程师设计出更可靠的通信接口。

metaverse5vr的博客 386

PointMLP实战:5分钟用PyTorch搭建点云分类模型(附GitHub代码)

本文提供了基于ICLR 2022论文PointMLP的点云分类模型实战教程。通过PyTorch框架,详细介绍了从环境搭建、数据预处理、模型架构解析到训练推理的全流程,并分享了性能调优技巧。该模型利用残差MLP模块构建,结构清晰高效,适合开发者快速上手并应用于实际点云处理项目。

r2s3t4的博客 215

你对类库文件DLL了解多少呢?

DLL文件(Dynamic Linkable Library 即动态链接库文件),是一种不能单独运行的文件,它允许程序共享执行特殊任务所必需的代码和其他资源。比较大的应用程序都有很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其他软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件

u010891453的专栏 1809

通信工程学习:什么是ASK振幅键控、FSK频移键控、PSK相移键控

ASK、FSK和PSK是三种基础数字调制技术。ASK通过改变载波振幅传输信息,实现简单但抗干扰弱;FSK利用频率变化传递信号,抗干扰较强但频谱利用率一般;PSK通过相位变化编码数据,抗干扰和频谱效率最优但同步要求高。三种技术各具特点,分别适用于不同通信场景:ASK用于低速光纤通信,FSK常见于无线广播,PSK则广泛应用于Wi-Fi、蓝牙等现代通信系统。

limengshi138392的博客 8718

DLL共享库和.NET共享集

特性调用库函数节点(CLN)构造器节点(CIN)适用对象C/C++编写的DLL.NET程序集调用方式手动定义函数原型,支持stdcall和C call通过P/Invoke机制,自动创建类实例配置方式配置对话框手动定义参数和返回值选择类并创建实例性能高性能,适合C/C++调用依赖P/Invoke,适合.NET调用兼容性需要确保调用约定与DLL一致需要确保类和方法具有public修饰符开发难度较高,需要手动定义函数原型较低,依赖类型信息。

m0_61928968的博客 974

企业级智能问数开发的核心痛点与技术聚焦

本文介绍了企业级智能问数系统开发的三大核心问题和解决方案:语义解析不精准、SQL生成不健壮、企业适配难落地。文章从开发者视角出发,详细拆解了NLU语义解析、SQL生成、对话记忆、企业级适配四大核心模块的技术实现。 在技术选型方面,推荐了LangChain+DeepSeek-R1、FAISS/Milvus、SQLAlchemy等工具链组合。针对企业级需求,重点讲解了: 语义解析模块的意图识别优化和要素提取技术 SQL生成模块的语法校验与性能优化方案 对话记忆模块的Redis存储实现 企业级适配的三层防护体系和

lamehsl的专栏 1046

053、坏点校正(DPC)进阶:静态坏点标定与动态坏点检测算法对比

文章摘要: 本文深入探讨了图像传感器坏点校正(DPC)技术,重点对比静态坏点标定与动态坏点检测算法。作者结合真实案例,指出静态标定在高增益场景下的局限性,并详细解析动态检测的核心算法、参数调优及常见误检问题(如边缘/纹理干扰)。提出“静态为主+动态为辅”的混合方案,强调阈值需随增益、亮度和温度自适应调整。文末总结六条工程建议,包括避免固定阈值、优先使用中值替换、定期更新坏点表等,强调DPC是图像质量的基础防线,需结合传感器特性综合优化。

zzfmlxs的博客 263

MFC类库作为 静态链接库和 共享链接库(即 共享的dll) 的区别

MFC类库 可以 作为一个共享的库 (共享的dll)来使用,也可以作为 静态链接的库使用。  前者       优点: 编译生成的文件较少。          缺点: 以后将 可执行程序 移植到其他系统中时,有可能会因为缺陷好MFC类库 而导致 程序不能运行。 后者               编译 生成的 文件较大,但程序中已包含了 用到的MFC库函数。所以 移植性更好,不会出现上述问题

Just To Be 1679

Altium Designer应用技巧13: 四层板的拼版方法

本文介绍在Altium Designer中,进行4层板的拼版方法。Altium Designer有多种拼版方法,本文选择最直观的复制、删帖方法来完成,步骤如下: 1、看一下原始文件,如下图所示,我们要对该板进行拼版操作。 2、通过菜单“报告”-“板子信息”得到板子大小,在这里一般切换为mm比较好操作,我们可以使用快捷键"q"。 3、如果是水平拼版,我们需要关心水平长度;如果是垂直拼版...

snmplink的博客 1万+

DLL与MFC DLL

比较大的应用程序都是由很多模块组成的,这些模块彼此协作,以完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其他软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE 文件中,会产生一些问题。一是增加了应用程序的大小,这样会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另外,在编写大的EXE 程序时,每次修改重建时都必须调整编译所有源代码,不但增加了编译过程的复杂性,也不利于阶段性的单元测试。 Windows ...

顺其自然~专栏 1323

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

共享MFC DLL的规则DLL 、带静态链接MFC的规则DLL和MFC扩展DLL区别

言:在编写MFC程序的时候,通常需要编写dll库以供其他程序调用。关于MFC dll的相关知识很多很杂,这里特酷吧结合自己学习中遇到的问题专门整理了一些MFC dll的基础知识。本部分共上下两篇文章,本文为上篇,MFC DLL应用程序类型分为以下三种: (1)使用共享MFC DLL的规则DLL (2)带静态链接MFC的规则DLL (3)MFC扩展DLL 下面重点解释一下这些DLL的含义区别

qing666888的专栏 7862

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: 跨dll使用template/STL需要注意的问题
下一篇: DLL地狱及其解决方案
wang37921
博客等级 码龄18年 14粉丝 31原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值