CMake学习笔记(三)——以笔者的Robosub竞赛为例

CMake学习笔记(三)——以笔者的Robosub竞赛为例

继笔者认真学习了CMake语法之后,便开始尝试自己用CMake将以前用Qt写的软件框架程序改编为CMake指令生成模式。现已成功,在此奉上一系列CMakeLists.txt的源码。

一. 前言

1. 比赛项目简要介绍

笔者曾经参加过美国海军作为主办方的竞赛,竞赛名称叫IAUVC(International Autonomous Underwater Vehicle Competiton),即国际水下无人航行器竞赛。笔者在2016年作为团队的图像及总控负责人,2017年作为团队的技术顾问。
由于2016年团队的控制系统仍有很大的改进空间,所以笔者就写了新的软件框架,主要思想基于多进程通信。

2. 本文思路

本文并不系统的解释语法,而是从根目录的CMakeLists.txt开始,按照指令执行流程进行讲解。
注:
关于语法的总结,笔者前面的文章《CMake学习笔记(二)——CMake语法》中,也对CMake语法做了较为系统的总结。

3. 文件列表

在该CMake项目下使用Linux的tree指令,得到如下的文件列表:

.
├── CMakeLists.txt
├── CustomizeFunctions
│   ├── CMakeLists.txt
│   ├── CustomizeStructs
│   │   └── imageprocessheader.h
│   ├── GeneralImageProcess
│   │   ├── CMakeLists.txt
│   │   ├── contours_fun.cpp
│   │   ├── contours_fun.h
│   │   ├── imageprocessing_fun.cpp
│   │   └── imageprocessing_fun.h
│   └── SupportFunctions
│       ├── CMakeLists.txt
│       ├── string_fun.cpp
│       ├── string_fun.h
│       ├── sys_fun.cpp
│       └── sys_fun.h
├── IPCClients
│   ├── CMakeLists.txt
│   ├── IPCImageClient
│   │   └── ncclient_image_main.cpp
│   ├── IPCSendClient
│   │   └── ncclient_send_main.cpp
│   └── IPCSurfClient
│       └── NCClient_Surf_Main.cpp
├── IPCServer
│   ├── CMakeLists.txt
│   └── main.cpp
└── NCFunctions
    ├── CMakeLists.txt
    ├── NCClient
    │   ├── CMakeLists.txt
    │   ├── ncclient.cpp
    │   ├── ncclient.h
    │   ├── ncclient_image.cpp
    │   ├── ncclient_image.h
    │   ├── ncclient_imagemacros.h
    │   ├── ncclient_macros.h
    │   ├── ncclient_send.cpp
    │   ├── ncclient_send.h
    │   ├── ncclient_surface.cpp
    │   └── ncclient_surface.h
    ├── NCServer
    │   ├── CMakeLists.txt
    │   ├── ncserver_dataproc.cpp
    │   ├── ncserver.h
    │   ├── ncserver_link.cpp
    │   ├── ncserver_macros.h
    │   ├── ncserver_record.cpp
    │   ├── ncserver_stage.cpp
    │   └── ncserver_strategy.cpp
    └── NCStage
        ├── Basic
        │   ├── ncstage_basic.cpp
        │   ├── ncstage_basic.h
        │   ├── ncstage.cpp
        │   ├── ncstage.h
        │   └── ncstage_macro.h
        ├── CMakeLists.txt
        ├── ncstage_test.cpp
        └── ncstage_test.h

该文件列表中,下列路径存在CMakeLists.txt:

  • IPCSocket,根目录
    • CustomizeFunctions,自定义底层函数源码
    • NCFunctions,针对笔者一系列类与函数的源码
      • NCClient,客户端及其派生类源码
      • NCServer,服务器端源码
      • NCStage,任务阶段的类实现源码
    • IPCClients,不同客户端二进制文件生成源码
    • IPCServer,服务器端文件生成源码

笔者编译生成整个项目,是在根目录IPCSocket下创造文件夹build,在build文件夹内执行cmake与make指令而生成的。指令如下:

mkdir build
cd ./build
cmake ../
make

GitHub源码地址:
https://github.com/upcAutoLang/Framework-for-NACIT2017

下面笔者将按照整个cmake的生成流程逐步分析讲解。存在重复的指令时,笔者会偷懒略过。

二. CMakeLists.txt详解

下面笔者讲从根目录向下逐层讲解:

1. 根目录/CMakeLists.txt

此处根目录为IPCSocket。该目录下的CMakeLists.txt如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# CMake项目名称
PROJECT(IPCSocket)

# 设置二进制目标文件生成路径
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# 设置库文件生成路径
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

# 添加四个CMake子目录
ADD_SUBDIRECTORY(./CustomizeFunctions)
ADD_SUBDIRECTORY(./NCFunctions)
ADD_SUBDIRECTORY(./IPCClients)
ADD_SUBDIRECTORY(./IPCServer)

此段代码下,定义了项目名称IPCSocket后,CMake便自动生成了两个变量:

// 二进制文件保存路径;
PROJECT_BINARY_DIR = IPCSocket_BINARY_DIR
// 源代码路径;
SOURCE_DIR = IPCSocket_BINARY_DIR

随后又定义了二进制目标文件与库文件的生成路径。此处两行SET代码是指将这两个路径设置为PROJECT_BINARY_DIR(即执行cmake指令的路径)下的bin, lib路径中。

后面的紧接的四个ADD_SUBDIRECTORY指令,是指CMake指令顺序进入四个路径中,顺序执行几个路径中的CMakeLists.txt文件。
这里笔者认为可以将其理解成C++的四个函数。四个函数顺序执行,按先后顺序依次处理./CustomizeFunctions ./NCFunctions ./IPCClients ./IPCServer中的CMakeLists.txt文件。如果这些CMakeLists.txt文件中也存在ADD_SUBDIRECTORY指令也同理。

下面按照ADD_SUBDIRECTORY的顺序进行说明。

2. /CustomizeFunctions/CMakeLists.txt

/CustomizeFunctions文件夹中存储了笔者的自定义函数,笔者此处想要将其封装为库文件。

/CustomizeFunctions/CMakeLists.txt文件内容如下所示:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 添加两个CMake子目录
ADD_SUBDIRECTORY(./GeneralImageProcess)
ADD_SUBDIRECTORY(./SupportFunctions)

即进入./GeneralImageProcess, ./SupportFunctions继续寻找CMakeLists.txt。

(1) /CustomizeFunctions/GeneralImageProcess/CMakeLists.txt

GeneralImageProcess文件夹中存储了笔者按照自己的使用习惯而对OpenCV做的自定义函数封装,笔者准备将其封装成库文件。
主要被定义为两部分内容:

  • contours_fun:笔者自定义的基于轮廓处理的函数;
  • imageprocessing_fun:笔者自定义的图像预处理函数;

文件内容如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)
# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../CustomizeStructs)
# 设置路径变量
SET(LIB_CONTOUR_SOURCE 
    ../CustomizeStructs/imageprocessheader.h
    ./contours_fun.h
    ./contours_fun.cpp)
SET(LIB_IMAGEPROCESS_SOURCE 
    ../CustomizeStructs/imageprocessheader.h
    ./imageprocessing_fun.h
    ./imageprocessing_fun.cpp)
# 生成库文件
ADD_LIBRARY(contours_fun SHARED ${LIB_CONTOUR_SOURCE})
ADD_LIBRARY(imageprocessing_fun SHARED ${LIB_IMAGEPROCESS_SOURCE})

其中,FIND_PACKAGE(OpenCV REQUIRED)在系统中寻找OpenCV的安装路径。
平常的软件,CMake的FIND_PACKAGE路径是/usr/share/cmake-2.8/Modules
这里写图片描述
但对于OpenCV的FIND_PACKAGE指令,寻找路径是/usr/local/share/OpenCV/OpenCVConfig.cmake。其中, /usr/local/share/OpenCV/ 是笔者在使用源码编译安装OpenCV时设置的安装地址OpenCV_INSTALL_DIR。

关于FIND_PACKAGE的工作原理,请参考地址:
http://blog.csdn.net/bytxl/article/details/50637277

两条SET指令设置了两个路径,在ADD_LIBRARY指令中使用。
指令ADD_LIBRARY(contours_fun SHARED ${LIB_CONTOUR_SOURCE})中,有三个部分如下:

  • contours_fun:生成的库文件基准名称
    • 此处笔者生成的是共享库,所以加上前缀lib,后缀.so,完整的库文件名称应该为libcontours_fun.so
  • SHARED:生成库的属性为共享库;此处若为STATIC则为静态库
  • ${LIB_CONTOUR_SOURCE}:此处为生成库文件的源码路径,该变量在前面已经被定义。

所以该指令便依赖变量LIB_CONTOUR_SOURCE中的路径,生成了libcontours_fun.so库文件。

另外一条ADD_LIBRARY指令同理。

(2) /CustomizeFunctions/SupportFunctions/CMakeLists.txt

CustomizeFunctions/SupportFunctions路径中,保存了笔者按照操作习惯而定义的支持函数。主要有两部分:

  • string_fun:自定义字符串操作函数;
  • sys_fun:自定义系统操作函数;

CMakeLists.txt文件内容如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)

# 设置路径变量
SET(LIB_STRING_SOURCE 
    ./string_fun.h
    ./string_fun.cpp)
SET(LIB_SYS_SOURCE 
    ./string_fun.h
    ./string_fun.cpp
    ./sys_fun.h
    ./sys_fun.cpp)

# 生成库文件
ADD_LIBRARY(string_fun SHARED ${LIB_STRING_SOURCE})
ADD_LIBRARY(sys_fun SHARED ${LIB_SYS_SOURCE})

原理同2.(1),此处不再赘述。

3. /NCFunctions/CMakeLists.txt

/NCFunctions文件夹中,存放了专为笔者所属团队而写类的源码。CMakeLists.txt文件如下:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

ADD_SUBDIRECTORY(./NCStage)
ADD_SUBDIRECTORY(./NCClient)
ADD_SUBDIRECTORY(./NCServer)

进入./NCServer ./NCClient ./NCStage继续寻找CMakeLists.txt文件。

(1) /NCFunctions/NCStage/CMakeLists.txt

/NCFunctions/NCStage文件夹中存放的是笔者自定义任务阶段类的源码。主要有三部分:

  • ncstage:任务阶段类的基类源码;
  • ncstage_basic:由ncstage派生出的基础阶段类的源码;
  • ncstage_test:用于调试的由ncstage派生出的阶段类的源码;

CMakeLists.txt文件如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(./Basic)

# 设置路径变量
SET(LIB_STAGE_SOURCE
    ./Basic/ncstage.cpp)
SET(LIB_STAGEBASIC_SOURCE
    ${LIB_STAGE_SOURCE}
    ./Basic/ncstage_basic.cpp)
SET(LIB_STAGETEST_SOURCE
    ${LIB_STAGE_SOURCE}
    ./ncstage_test.cpp)

# 生成库文件
ADD_LIBRARY(ncstage SHARED ${LIB_STAGE_SOURCE})
ADD_LIBRARY(ncstage_basic SHARED ${LIB_STAGEBASIC_SOURCE})
ADD_LIBRARY(ncstage_test SHARED ${LIB_STAGETEST_SOURCE})

原理同2.(1),此处不再赘述。

(2) /NCFunctions/NCClient/CMakeLists.txt

笔者整个工程的框架,采用的是由socket实现进程间的通讯。/NCFunctions/NCClient文件夹中存放的是各个客户端的类源码。主要有四部分:

  • ncclient:客户端基类源码;
  • ncclient_image:图像客户端源码,用于图像信息处理,派生于客户端基类;
  • ncclient_send:串口通讯客户端源码,用于与下位机传递信息,派生于客户端基类;
  • ncclient_surface:界面客户端源码,用于服务器与其他客户端之间的通讯,派生于客户端基类;

/NCFunctions/NCClient/CMakeLists.txt内容如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)

# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/SupportFunctions)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS})

# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)

# 设置路径变量
SET(LIB_CLIENT_SOURCE
    ./ncclient.cpp
    ../../CustomizeFunctions/SupportFunctions/string_fun.cpp)
SET(LIB_IMAGE_SOURCE
    ${LIB_CLIENT_SOURCE}
    ./ncclient_image.cpp)
SET(LIB_SEND_SOURCE
    ${LIB_CLIENT_SOURCE}
    ./ncclient_send.cpp)
SET(LIB_SURF_SOURCE
    ${LIB_CLIENT_SOURCE}
    ./ncclient_surface.cpp)

# 生成库文件
ADD_LIBRARY(ncclient SHARED ${LIB_CLIENT_SOURCE})
ADD_LIBRARY(ncclient_image SHARED ${LIB_IMAGE_SOURCE}) 
# 连接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(ncclient_image 
    ${OpenCV_LIBS}
    imageprocessing_fun)

ADD_LIBRARY(ncclient_send SHARED ${LIB_SEND_SOURCE})
ADD_LIBRARY(ncclient_surf SHARED ${LIB_SURF_SOURCE})

这里值得提的是LINK_DIRECTORIES指令与TARGET_LINK_LIBRARIES指令的运用。
生成图像客户端目标文件,是基于之前自定义函数部分中的imageprocessing_fun的图像处理函数的,而之前这些函数已经被处理为库文件,被存储在工程生成路径下的lib路径中,所以指令LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)就是将库文件路径包含在工程中,以便后面库文件的链接。
后面TARGET_LINK_LIBRARIES命令便通过链接先前的库文件来生成目标文件,该条命令主要有几部分组成:

  • ncclient_image:生成的目标文件名称
    • 由于先前存在指令ADD_LIBRARY(ncclient_image SHARED ${LIB_IMAGE_SOURCE}),所以此处生成的目标文件是共享库文件;
  • ${OpenCV_LIBS}:依赖库文件列表
    • 此处的变量${OpenCV_LIBS}是在通过编译安装OpenCV源码后,系统中便已经存在了该变量。该变量是在OpenCV安装地址下的OpenCVConfig.cmake(笔者的路径是/usr/local/share/OpenCV/OpenCVConfig.cmake)中通过遍历而得到的。该变量获取的过程大致如下图所示:
    • 这里写图片描述
  • imageprocessing_fun:依赖库文件
    • 该库文件是由前面2.(1)中/CustomizeFunctions/GeneralImageProcess/CMakeLists.txt文件生成的;

注:TARGET_LINK_LIBRARIES指令一定要在ADD_LIBRARY指令之后。

其余部分在2.(1)中都有讲解,此处不再赘述。

(3) /NCFunctions/NCServer/CMakeLists.txt

前面是客户端类代码,此处的/NCFunctions/NCServer文件夹中存放的便是服务器端的类源码。服务器类只有一个,但依照完成不同功能的模块,被笔者分为五部分:

  • ncserver_link:服务器端socket网络通信部分的类函数源码
  • ncserver_dataproc:服务器端解算获得数据部分的类函数源码
  • ncserver_stage:服务器端判断当前任务进行状态的类函数源码
  • ncserver_strategy:服务器端依据当前各传感器数据与当前任务进行状态,判断下一步策略的类函数源码
  • ncserver_record:服务器端记录信息的类函数源码

/NCFunctions/NCServer/CMakeLists.txt内容如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)


# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../)
INCLUDE_DIRECTORIES(../NCClient)
INCLUDE_DIRECTORIES(../NCStage)
INCLUDE_DIRECTORIES(../NCStage/Basic)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/SupportFunctions)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../../CustomizeFunctions/GeneralImageProcess)

# 查找某个路径下的所有源文件,并将源文件列表存储到某个变量中 
AUX_SOURCE_DIRECTORY(./ LIB_SERVER_SOURCE)
SET(LIB_SERVER_SOURCE 
    ${LIB_SERVER_SOURCE}
    ../NCClient/ncclient_macros.h
    ../NCStage/Basic/ncstage.cpp
    ../../CustomizeFunctions/SupportFunctions/string_fun.cpp
    ../../CustomizeFunctions/SupportFunctions/sys_fun.cpp)

# 生成库文件
ADD_LIBRARY(ncserver SHARED ${LIB_SERVER_SOURCE})

该部分源码中存在AUX_SOURCE_DIRECTORY指令,有两个参数:

  • ./:查找的路径
  • LIB_SERVER_SOURCE:将上述路径中的源文件列表存入该变量

经过这条指令,可以查找当前CMakeLists文件所在路径下的所有源文件,并将整个源文件列表存入变量LIB_SERVER_SOURCE中。

其余部分在前文已经叙述,此处不再赘述。

经过前面的处理,${PROJECT_BINARY_DIR}/lib下的库文件已经生成完毕。生成文件如图所示:
这里写图片描述

4. /IPCClients/CMakeLists.txt

/IPCClients文件夹中,存放了所有客户端的实现源码。CMakeLists.txt文件如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 系统中寻找OpenCV的安装路径
FIND_PACKAGE(OpenCV REQUIRED)

# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)

# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(./IPCImageClient)
INCLUDE_DIRECTORIES(./IPCSendClient)
INCLUDE_DIRECTORIES(./IPCSurfClient)
INCLUDE_DIRECTORIES(../NCFunctions)
INCLUDE_DIRECTORIES(../NCFunctions/NCClient)
INCLUDE_DIRECTORIES(../CustomizeFunctions)
INCLUDE_DIRECTORIES(../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(../CustomizeFunctions/SupportFunctions)

# 设置路径变量
SET(LIBS_CLIENT
    ncclient)
SET(LIBS_IMAGE
    ${LIBS_CLIENT}
    ncclient_image)
SET(LIBS_SEND
    ${LIBS_CLIENT}
    ncclient_send)
SET(LIBS_SURF
    ${LIBS_CLIENT}
    ncclient_surf)

# 生成二进制可执行文件
ADD_EXECUTABLE(IPCImageClient 
    ./IPCImageClient/ncclient_image_main.cpp)
# 链接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(IPCImageClient 
    ${LIBS_IMAGE})

ADD_EXECUTABLE(IPCSendClient
    ./IPCSendClient/ncclient_send_main.cpp)
TARGET_LINK_LIBRARIES(IPCSendClient
    ${LIBS_SEND})

ADD_EXECUTABLE(IPCSurfClient
    ./IPCSurfClient/NCClient_Surf_Main.cpp)
TARGET_LINK_LIBRARIES(IPCSurfClient
    ${LIBS_SURF})

其中命令ADD_EXECUTABLE用于生成目标二进制可执行文件,有两个参数如下:

  • IPCImageClient:生成目标二进制可执行文件名称
  • ./IPCImageClient/ncclient_image_main.cpp:生成该可执行文件所依赖的源码

随后的TARGET_LINK_LIBRARIES指令,以指定的库文件为基础,生成目标文件。有两个参数如下:

  • IPCImageClient:先前在ADD_EXECUTABLE中指定的目标二进制可执行文件名称
  • ${LIBS_IMAGE}:依赖库文件列表

综上,指令ADD_EXECUTABLE(IPCImageClient ./IPCImageClient/ncclient_image_main.cpp)便以源码文件ncclient_image_main.cpp,以及变量LIB_IMAGE中包含的库文件为基础,生成了目标可执行文件IPCImageClient。

其余的两条ADD_EXECUTABLE, TARGET_LINK_LIBRARIES指令同理,不再赘述。
该部分CMakeLists.txt在${PROJECT_BINARY_DIR}/bin路径中,生成了所有客户端的可执行文件,如下图所示:
这里写图片描述

4. /IPCServer/CMakeLists.txt

/IPCServer文件夹中,存放了服务器端的实现源码。CMakeLists.txt文件如下:

# CMake最低版本要求
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

# 添加依赖库路径
LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/lib)

# 添加源文件中的#include路径
INCLUDE_DIRECTORIES(./)
INCLUDE_DIRECTORIES(../NCFunctions)
INCLUDE_DIRECTORIES(../NCFunctions/NCServer)
INCLUDE_DIRECTORIES(../NCFunctions/NCClient)
INCLUDE_DIRECTORIES(../NCFunctions/NCStage)
INCLUDE_DIRECTORIES(../NCFunctions/NCStage/Basic)
INCLUDE_DIRECTORIES(../CustomizeFunctions)
INCLUDE_DIRECTORIES(../CustomizeFunctions/CustomizeStructs)
INCLUDE_DIRECTORIES(../CustomizeFunctions/GeneralImageProcess)
INCLUDE_DIRECTORIES(../CustomizeFunctions/SupportFunctions)

# 设置路径变量
SET(LIBS_SERVER
    ncserver
    ncstage
    ncstage_basic
    ncstage_test
)

# 生成二进制可执行文件
ADD_EXECUTABLE(IPCServer
    ./main.cpp)
# 链接库文件,以这些库文件为基础生成目标文件
TARGET_LINK_LIBRARIES(IPCServer
    ${LIBS_SERVER})

该部分源码原理在2.(1), 4中都有讲解,此处不再赘述。

该部分CMakeLists.txt在${PROJECT_BINARY_DIR}/bin路径中,生成了服务器端的可执行文件,如下图所示:
这里写图片描述

至此,整个cmake与make流程全部结束。

后记:

该工程项目框架的源码,笔者已经将源码和文档整理并放在GitHub上,链接见前文。届时望各位大神指教~

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值