简介:面向 MSVC2017 环境编译的 OSG 3.6.3 与 osgEarth 2.10 开发库整合包,同时附带一个基于 Qt 5.14 的简单示例工程,适用于三维地形渲染、虚拟仿真、GIS 可视化等场景的开发者。整合后的库文件可直接用于搭建 QtCreator 下的 OSG/osgEarth 开发环境,省去自行源码编译、依赖配置的繁琐环节。压缩包约 530MB,共 2000 个文件,主体为 hpp/h 头文件、dll 动态库与 lib 导入库,另含 exe 演示工具、earth 场景描述文件、osg 模型文件、着色器与纹理图片等,已覆盖库引用、运行依赖、示例场景所需的典型文件类型。示例 Qt 工程展示了 QtWidget 与三维地球窗口的集成方式,可帮助理解图形窗口适配与渲染线程的基本写法。已有 532 人学习下载,适合需要快速搭建 OSG+osgEarth 开发环境并上手三维可视化开发的中高级读者。
1. MSVC2017+OSG3.6.3+osgEarth2.10+Qt5.14:Windows 三维 GIS 开发可以直接落地的环境快照
拿到这个打包好的 msvc2017+osg3.63+osgearth2.10+qt5.14.7z,第一反应是省事。Windows 上做三维 GIS 开发,最折磨人的不是业务代码,而是把 OSG、osgEarth 和 Qt 三个库的版本、编译器、插件路径对齐。这个压缩包把三套库用同一套 MSVC2017 工具链编好,解压后在 Qt Creator 里配置一下就能用,省去自己编译 OSG 和 osgEarth 的半天时间。适合两类人:一类是要在 Qt 界面里快速嵌入三维地图的从业者,另一类是刚接手 osgEarth 2.x 项目、需要补环境的开发新手。它解决的核心问题是 ABI 一致性和插件配套,这也是后续所有排错的主线。
2. 版本选型逻辑:为什么这套组合要绑在 MSVC2017 上
2.1 工具链决定 ABI,ABI 决定能不能混用
Windows 上做 C++ 三维开发,最忌讳把不同编译器编出来的库混在一起。MSVC 的 C++ ABI 和 MinGW 的 ABI 完全不同,前者基于 MSVC CRT,后者依赖 libstdc++。就算你把头文件都指对了,链接阶段也可能没报错,但运行时就会因为堆管理方式不同而崩溃。这个包把 MSVC2017 作为统一工具链,OSG、osgEarth、Qt 三者的库都是同一套 CRT 编出来的,这是“能用”的底线。
另一个现实问题是 Qt 官方二进制发行版和工具链的对应关系。Qt 5.14 时代,官方同时提供 msvc2017_64 和 msvc2019_64 两套预编译包,但 osgEarth 2.10 编译时大多对齐的是 VS2017 工具链。如果你拿 msvc2019 的 Qt 去链 MSVC2017 编出来的 osgEarth,大多数情况下能过,可一旦遇到跨模块传递 STL 容器或 QString,就开始出怪问题。所以这个包把版本钉在 MSVC2017 上,不是保守,是自洽。
提示:解压完先检查机器上有没有 VS2017 对应的 VC++ 运行库(vcruntime140.dll、msvcp140.dll 那一套)。缺了这个,任何库的 DLL 起来第一步就会失败。
2.2 OSG 3.6.3:为什么是这个版本而不是 3.4 或 3.7
OSG 的 3.6 系列相比 3.4 变化不小:支持 OpenGL 核心模式、多重采样、更严格的状态管理。3.6.3 是这一代里修 bug 比较充分的版本,社区里很多 osgEarth 2.x 项目默认就搭在 3.6.x 上。3.7 之后的设计方向更偏向新渲染管线,很多老项目的插件接口开始不对齐。只是做三维 GIS 场景展示的话,3.6.3 的稳定性比追新更划算。
还有一个实际因素:osgEarth 2.10 在编译时对 OSG 版本有隐式依赖。你去看 osgEarth 2.10 的源码会发现,它的 CMake 默认查找的 OSG 库名和接口范围是以 3.6 为基准的。虽然源码也允许在更高版本 OSG 上编译,但部分回调函数签名、StateSet 行为有差异,新手排错时很难分清是 osgEarth 的坑还是 OSG 版本差异的坑。用预编译好的 3.6.3 组合,至少能把变量先固定住。
2.3 osgEarth 2.10 与 3.x 的分水岭
osgEarth 2.10 是 2.x 时代的成熟版本,它把地图数据抽象成 Map 和 MapNode,图层用 ImageLayer、ElevationLayer 表示,模型非常直白。2.10 在加载本地 TMS、WMS 或离线影像时,驱动逻辑稳定、公开资料多。二次开发时主要跟 osgEarth::Util 里的类打交道,学习曲线平缓。
3.x 系列把底层地形引擎彻底重写了,渲染方式、线程模型都不同。新功能确实多,比如更精细的剔除、更现代的地形纹理管理,但接口变动大,编译和迁移成本高。如果你的项目目标是离线三维 GIS、智慧城市这类,并没有用到 3.x 新增特性的必要,留在 2.10 是合理的。反过来说,如果就是奔着新架构去的,那这个包不适合你,别勉强。
2.4 包里的物理内容:头文件、库、插件和工具
这类 7z 包解压后通常能看到三个根目录:osg-3.6.3、osgearth-2.10、Qt/5.14.x。每个目录下是 include、lib、bin 三件套。bin 里除了库本身的 DLL,还有 OSG 的插件目录 osgPlugins-3.6.3,里面躺着 osgdb_osg、osgdb_tiff、osgdb_qt 等几十个格式插件。osgEarth 的 bin 目录下则是 osgdb_earth、osgdb_osgearth_tms、osgdb_osgearth_image 这类插件,负责把 .earth 文件和影像、高程驱动接进 OSG 的节点读取体系。
注意:插件目录名必须和 OSG 版本严格一致。osgPlugins-3.6.3 里混入 3.4 的插件,osgDB 会直接拒绝加载,因为插件接口对不上。
3. 解压与套件配置:把 7z 包变成可用的构建环境
3.1 目录规划与解压检查
先规划一个纯 ASCII 且没有空格的路径,比如 D:\dev\geo3d。不要解压到带中文或空格的路径,Windows 下这一条能帮你避开一半的玄学问题。解压完成后,核对三层目录结构:osg-3.6.3、osgearth-2.10、Qt/5.14.x。
重点检查 osg-3.6.3\bin 下有没有 osgPlugins-3.6.3 这个目录。我见过一些分包方式会把插件单独拆出来,结果用户只拷贝了 DLL,插件目录漏了,运行时 osgDB 找不到任何格式读取器。最好解压完先用 dir /s /b 把目录树导出一份存着,后面排错经常要对照。
3.2 环境变量注入
环境变量只加三个:OSG_DIR、OSGEARTH_DIR 和 PATH。在管理员命令行里执行:
setx OSG_DIR "D:\dev\geo3d\osg-3.6.3" setx OSGEARTH_DIR "D:\dev\geo3d\osgearth-2.10" setx PATH "D:\dev\geo3d\osg-3.6.3\bin;D:\dev\geo3d\osgearth-2.10\bin;D:\dev\geo3d\Qt\5.14.x\msvc2017_64\bin;%PATH%"这段的作用是把三个 bin 目录塞进 PATH。OSG 和 osgEarth 的 DLL 相互依赖,运行时是在 PATH 里找对方的;Qt 的 DLL 同样在 PATH 里找。setx 写入的是用户级环境变量,设置完必须重开终端才生效。
我一般不建议把 Qt 的 bin 永久塞进系统 PATH,因为机器上有多个 Qt 版本时会互相干扰。更稳妥的做法是只在 Qt Creator 的构建环境里给当前项目注入这些路径,不影响全局。下面套件配置里就是这么干的。
3.3 Qt Creator 套件绑定:MSVC2017 工具链与 qmake 的选择
打开 Qt Creator 的 Tools > Options > Kits,按顺序做四步:
- 在 Qt Versions 选项卡点击 Add,定位到 D:\dev\geo3d\Qt\5.14.x\msvc2017_64\bin\qmake.exe,让 Creator 识别出这个 Qt 版本。
- 切到 Compilers 选项卡,确认有 Microsoft Visual C++ Compiler 15.x(x86_64)。没有的话手动添加,编译器路径指向 VS2017 安装目录下的 cl.exe,通常需要配合 vcvars64.bat 使用。
- 在 Kits 选项卡新建套件,编译器选 MSVC2017 64 位,Qt version 选刚添加的 5.14.x 版本,CMake generator 选 Ninja 或 Visual Studio 15 2017 Win64。
- 调试器可以先选 none,不影响编译和运行。
关键点是套件的 ABI 标签要和 Qt 的目录名一致:MSVC2017 套件配 msvc2017_64 的 Qt。如果 Creator 里残留了别的 Qt 版本,比如之前装过 5.15.2/msvc2019_64,一定要在 Kits 里手动指定当前项目使用哪个 Qt,否则 qmake 会把错误的头文件路径写进构建系统。这一步做错,就会出现后面避坑笔记里的 dependent error。
3.4 第一个最小验证程序
新建一个纯 C++ 项目,关掉 Qt 模块依赖,先验证 OSG 本身能起来。main.cpp 写:
#include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <osg/Geode> int main(int argc, char** argv) { osgViewer::Viewer viewer; // 优先加载包内自带的模型,没有就创建一个空场景兜底 osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("D:/dev/geo3d/osg-3.6.3/data/cow.osg"); if (!root.valid()) { root = new osg::Geode(); } viewer.setSceneData(root.get()); viewer.setUpViewInWindow(100, 100, 1280, 800); return viewer.run(); }readNodeFile 如果失败会返回空指针,这里用空 Geode 兜底,保证窗口一定能弹出来。setUpViewInWindow 会在 Windows 上自动创建原生窗口,如果这步连窗口都不弹,基本可以断定是 DLL 缺依赖或显卡上下文初始化失败,先回到 3.2 检查 PATH。
4. 工程对接:CMake 模板、库清单与运行时 DLL 部署
4.1 直接吃这个包的 CMakeLists 模板
预编译包的价值在于工程能直接引用。我习惯用 CMake,模板如下:
cmake_minimum_required(VERSION 3.16) project(Geo3dDemo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_PREFIX_PATH "D:/dev/geo3d/osg-3.6.3" "D:/dev/geo3d/osgearth-2.10" "D:/dev/geo3d/Qt/5.14.x/msvc2017_64" ) find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgGA osgUtil osgViewer osgText) find_package(Qt5 5.14 REQUIRED COMPONENTS Widgets Gui Core) find_package(osgEarth REQUIRED) add_executable(Geo3dDemo main.cpp) target_link_libraries(Geo3dDemo PRIVATE ${OPENSCENEGRAPH_LIBRARIES} ${OSGEARTH_LIBRARIES} Qt5::Widgets )CMAKE_PREFIX_PATH 同时指向三套库的根目录,find_package 就能在这三个前缀下搜索各自的配置文件。OSG 的 CMake 模块比较规范,OpenSceneGraph 这个 find_package 一般没问题。Qt5 的模块也稳定。
osgEarth 2.10 的 CMake 配置文件不如 OSG 规范,find_package(osgEarth) 在某些发行版里根本找不到。常见做法是放弃 find_package,直接手工指定:
include_directories("D:/dev/geo3d/osgearth-2.10/include") link_directories("D:/dev/geo3d/osgearth-2.10/lib") target_link_libraries(Geo3dDemo PRIVATE osgEarth osgEarthUtil osgEarthFeatures osgEarthSymbology )手工指定时要注意库的依赖顺序,osgEarth 依赖 osgEarthUtil,osgEarthUtil 又依赖 osgEarthFeatures,链接器是从右往左解析符号的,顺序反了会出现 unresolved external symbol。
4.2 参数与库清单
这套环境里常用的库名,我列一张表对照着看:
| 模块 | 库名 | 用途 |
|---|---|---|
| OSG 核心 | osg、osgUtil | 场景图与算法基础 |
| OSG 渲染 | osgViewer、osgGA | 窗口管理和交互控制 |
| OSG 读写 | osgDB | 节点/纹理/模型加载 |
| OSG 文本 | osgText | 屏幕文字 |
| osgEarth 主库 | osgEarth | Map、图层、驱动核心 |
| osgEarth 工具 | osgEarthUtil | 操纵器、测量、几何工具 |
| osgEarth 要素 | osgEarthFeatures、osgEarthSymbology | 矢量和符号渲染 |
| Qt 基础 | Qt5::Core、Qt5::Gui、Qt5::Widgets | 界面框架 |
lib 目录下的导入库命名有规律。MSVC 环境下,osg 系列是 osg.lib、osgViewer.lib 这种,osgEarth 是 osgEarth.lib、osgEarthUtil.lib。Debug 版本会带 d 后缀,比如 osgd.lib、osgEarthd.lib。但我强烈建议只用 Release 库,原因在避坑笔记 5.3 里说。
4.3 运行时 DLL 部署
编译通过只是第一步。把程序拷到别的机器,或者在本机换目录运行,DLL 找不到是最常见的翻车点。我一般用这种批处理把依赖打到一个 deploy 目录:
set SRC=D:\dev\geo3d set DST=D:\dev\demo\deploy if not exist %DST% mkdir %DST% REM OSG 主 DLL copy /Y %SRC%\osg-3.6.3\bin\*.dll %DST%\ REM osgEarth 及其依赖(含 GDAL、GEOS、libcurl 等) copy /Y %SRC%\osgearth-2.10\bin\*.dll %DST%\ REM Qt5 核心 copy /Y %SRC%\Qt\5.14.x\msvc2017_64\bin\Qt5Core.dll %DST%\ copy /Y %SRC%\Qt\5.14.x\msvc2017_64\bin\Qt5Gui.dll %DST%\ copy /Y %SRC%\Qt\5.14.x\msvc2017_64\bin\Qt5Widgets.dll %DST%\ REM OSG 插件整目录,必须和版本号对应 xcopy /E /I /Y %SRC%\osg-3.6.3\bin\osgPlugins-3.6.3 %DST%\osgPlugins-3.6.3OSG 的插件目录必须和 osgDB 期望的版本号一致。程序运行时 osgDB 会去 PATH 和程序目录下找 osgPlugins-3.6.3,找不到就报 Warning: Cannot find plugin to read objects from file。osgEarth 的插件 osgdb_earth.dll、osgdb_osgearth_tms.dll 在 osgearth 的 bin 里,要一并复制。
提示:如果程序仍然报 Qt 平台插件找不到,错误通常是“could not find or load the Qt platform plugin windows”,把 D:\dev\geo3d\Qt\5.14.x\msvc2017_64\plugins\platforms 整个目录拷到程序目录,程序会在 platforms 子目录下找 qwindows.dll。
4.4 Qt 窗口嵌入 OSG 场景的两种写法
把 OSG 场景嵌进 Qt 窗口,方案取决于包里有没有 osgQt 这个桥接库。有就用 GraphicsWindowQt:
#include <QApplication> #include <QMainWindow> #include <osgViewer/Viewer> #include <osgQt/GraphicsWindowQt> #include <osgDB/ReadFile> #include <osgEarth/EarthManipulator> int main(int argc, char** argv) { QApplication app(argc, argv); osgViewer::Viewer viewer; osg::ref_ptr<osgQt::GraphicsWindowQt> gw = new osgQt::GraphicsWindowQt(0, 0, 1024, 768); viewer.getCamera()->setGraphicsContext(gw.get()); viewer.getCamera()->setViewport(0, 0, 1024, 768); osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("D:/data/local.earth"); viewer.setSceneData(node.get()); viewer.setCameraManipulator(new osgEarth::Util::EarthManipulator()); QMainWindow win; win.setCentralWidget(gw->getGLWidget()); win.resize(1024, 768); win.show(); return app.exec(); }GraphicsWindowQt 把 GL 上下文封装成 Qt 窗口控件,viewer 的相机直接挂在这个控件上,事件循环由 Qt 接管。这种写法的好处是鼠标键盘事件天然融合,缺点是 osgQt 本身要跟着这套工具链编译,包里不一定带。
如果包内没有 osgQt,退一步用 QTimer 驱动 viewer.frame():
QTimer timer; QObject::connect(&timer, &QTimer::timeout, [&viewer]() { viewer.frame(); }); timer.start(16);这种轮询方式的坑在于:16ms 固定刷新会拖累界面线程,场景复杂时性能下降明显。我一般会把 timer 间隔改成 30ms,并设置 viewer.setRunMaxFrameRate(30),避免 CPU 空转。两种方式选哪种,取决于包内是否预制了 osgQt 的导入库,没有就别硬上,轮询法足够应付大多数展示型项目。
5. 避坑笔记:Qt 版本串扰、Debug/Release 混用与插件加载失败
5.1 dependent error 指向不存在的 Qt 路径
现象:在 Qt Creator 里编译项目,报错信息是:-1: error: dependent '..\..\..\..\..\..\qt\5.15.2\msvc2019_64\include\qtwidgets/qtwidgetsglobal.h' does not exist.,而且这个路径在你的工程里根本不存在。
原因:这是 Qt Creator 的构建系统残影。项目之前在某台机器上用 Qt 5.15.2/msvc2019_64 构建过,.pro.user 文件或 CMakeCache.txt 里记住了旧的头文件路径。换到这台机器后,qmake 虽然指向了 5.14,但旧缓存文件还在,构建系统把两套路径混在一起了。
解决:第一步,删除构建目录,在 Qt Creator 里清掉 shadow build 目录。第二步,删除项目根目录下的 .pro.user 文件;如果是 CMake 工程,删掉 CMakeCache.txt 和 CMakeFiles 目录。第三步,重新在 Kits 里确认 Qt version 指向 msvc2017_64 的 qmake,然后重新构建。这套组合拳能把绝大多数 dependent error 清掉。
5.2 osgEarth 加载 .earth 文件报找不到插件
现象:程序编译通过,运行后窗口是黑的,控制台打印Warning: Cannot find plugin to read objects from file "D:/data/local.earth",或者加载影像层时瓦片始终不出来,日志里有 osgdb_qt 相关提示。
原因:osgDB 是插件式架构,.earth 文件由 osgdb_earth.dll 读取,TMS 影像由 osgdb_osgearth_tms.dll 读取,这些插件 DLL 没在程序目录、也没在 PATH 里。另一种可能是 Qt 图像插件缺失,osgEarth 2.10 通过 osgdb_qt.dll 读 Qt 支持的图片格式,这个插件依赖 Qt 的 qwindows.dll。
解决:在系统环境变量里强制指定插件路径:
set OSG_LIBRARY_PATH=D:\dev\geo3d\osg-3.6.3\bin set QT_QPA_PLATFORM_PLUGIN_PATH=D:\dev\geo3d\Qt\5.14.x\msvc2017_64\plugins\platforms set PATH=D:\dev\geo3d\osg-3.6.3\bin;D:\dev\geo3d\osgearth-2.10\bin;%PATH%设置后重开程序,看 osgEarth 的加载日志,确认 osgdb_earth.dll 被真正加载。别迷信程序目录里已经有部分 DLL,插件目录和主 DLL 目录经常是分离的。
5.3 Debug/Release 混用导致运行期崩溃
现象:用 Debug 配置构建 Qt 项目,启动后几秒内崩溃,调用栈随机出现在 QString 析构、osg::ref_ptr 释放或 std::vector 扩容的位置。有时报错是HEAP: Free Heap block ... modified at ... after it was freed。
原因:预编译包是 Release 版,CRT 用的是 release 运行时。Debug 版项目链接了 Release 库,两套 CRT 的堆管理机制不同,跨模块分配和释放内存必然出问题。这是 MSVC 平台的老规矩,不是 OSG 或 osgEarth 的 bug。
解决:项目构建配置强制用 Release。CMake 工程在配置时写明:
set(CMAKE_BUILD_TYPE Release)Qt Creator 里把构建步骤中的 Build configuration 选成 Release。如果确实需要 Debug 版调试,唯一的正路是自己用同一套 MSVC2017 工具链把 OSG、osgEarth 全部重新编译成 Debug 库,没有捷径。
5.4 中文路径导致 TMS 瓦片黑屏
现象:TMS 影像数据放在D:\项目数据\某地\这种中文路径下,osgEarth 启动不报错,但场景里地形没有纹理,或者影像层加载进度一直为零。
原因:Windows 文件系统底层是 UTF-16,而 OSG 和 osgEarth 的窄字符串路径处理依赖本地代码页。MSVC2017 下,程序把路径当作 GBK 处理,但 TMS 瓦片文件名在驱动内部被解析成 UTF-8,两者对不上,文件打开失败,而且这种失败往往只留一条极简的 debug 日志,看起来无声无息。
解决:最省心的方案是数据路径和工程路径全部用 ASCII。公司内部如果强制中文目录,临时方案是把瓦片打包成 zip,用 osgEarth 的 zip driver 读取,绕开文件系统路径解析。另一个备用方案是把路径统一转成 file:// URL 格式再传给驱动,但实测仍有版本兼容问题,不如直接改路径来得干净。
5.5 本地 TMS 与地图 Profile 不匹配
现象:添加本地 TMS 影像和高程后,场景出现大范围偏移,或者瓦片加载后出现缝隙,控制台报 profile 相关错误。
原因:osgEarth 2.10 的 Map 有一个全局 profile,所有图层必须落在同一个 profile 里。如果本地 TMS 瓦片是 EPSG:3857 即球形墨卡托生成,但 .earth 文件里默认用的是 global-geodetic,两者坐标基准不一致,瓦片自然对不上。
解决:在 .earth 文件的 options 里显式声明 profile:
<options> <profile>spherical-mercator</profile> </options>如果数据源是经纬度切片,就用 global-geodetic。拿不准的时候,打开 TMS 目录下的 tileset.xml 或 tilemapresource.xml,里边的 proj 节点会写明坐标系统,照着填就行。这事不解决,后面所有排错都是白费功夫。
6. 进阶验证:用本地影像与高程数据跑通离线 osgEarth 场景
6.1 用命令行工具快速验证环境
环境配好之后,别急着写业务代码,先用 osgEarth 自带的 osgearth_viewer 命令行工具验证整条链路。这个工具是 osgEarth 打包出来的独立可执行程序,直接读取 .earth 文件并渲染场景:
osgearth_viewer D:/data/local.earth --lat 30.5 --lon 104.04 --elev 500参数 --lat、--lon 是初始视角的经纬度,--elev 是视点海拔。如果这条命令能弹出三维窗口并显示地形,说明 OSG、osgEarth、Qt 插件、显卡上下文全部正常。如果失败,按上一章的排查顺序逐项检查,不要急着写代码。
6.2 本地影像与高程的 .earth 配置
离线项目最稳的做法是把瓦片数据下载到本地,再用 .earth 文件组织起来。一套典型配置是这样的:
<map name="local" type="geocentric" version="2"> <options> <profile>spherical-mercator</profile> <cache type="filesystem"> <path>D:/data/cache</path> </cache> </options> <image name="img" driver="tms"> <url>D:/data/landsat_tms</url> </image> <elevation name="dem" driver="tms"> <url>D:/data/srtm_tms</url> </elevation> </map>cache 节点的作用是建立本地瓦片缓存,第二次加载同一区域直接从缓存读,能省掉大量磁盘 IO。经度 104.04、纬度 30.5 对应的是成都附近,你要是做别的区域,把经纬度换成自己的测试点就行。type 用 geocentric 表示全球场景,单区域项目改成 local 也可以,但 local 模式下 profile 必须更精确,否则区域外会黑屏。
6.3 验证技巧与调试习惯
场景跑起来以后,验证是否真的加载成功有三个办法。第一,看瓦片加载日志,osgEarth 里控制日志级别的方式是设置 notify level,信息输出能看到每个瓦片请求的文件路径和耗时。第二,在 osgearth_viewer 里按键盘上的统计键,屏幕会显示帧率和场景节点数量,帧率稳定在 30 以上基本可接受。第三,手动缩放视角,快速从全球级别拉到建筑物级别,重点观察瓦片切换时是否出现白块或长时间空白,白块说明对应层级的瓦片缺失。
我在这套环境上栽过最大的一个跟头,就是 Qt 5.15 的旧目录残留在 CMakeCache 里,折腾了半天全是那个 dependent error。从那以后每次换机器,我都强制走一遍同样的流程:删干净构建目录、让 Qt Creator 重新自动扫描 qmake 路径、用 osgearth_viewer 验证一次 .earth 文件、再开正式工程。这套流程走完五分钟,收益是省掉后面一晚上的玄学排错。这个习惯也建议你保留,希望帮到你。
本文还有配套的精品资源,点击获取