mingw64 编译 GDAL 1.11.5 包:老项目 Qt 集成与避坑指南
简介:这份资源是作者在 Windows 下用 msys2 配合 mingw64 工具链预先编译好的 GDAL 1.11.5 库,面向使用 Qt(MinGW 版本)进行 C++ 桌面 GIS 开发、又不愿在本地折腾编译环境的开发者,可省去从源码配置依赖、处理编译错误的繁琐过程。压缩包共 149 个文件,约 70.71MB,包含 63 个 C++ 头文件、22 个可执行程序、28 个 CSV 格式的坐标与投影参数数据,以及静态库、导入库、DLL、gdal-config 配置脚本和若干 WKT、XML、XSD 等辅助文件,覆盖开发所需的头文件、链接库与运行时数据。已有 1522 人学习下载。解压后将 bin 目录加入系统环境变量,并在 .pro 文件中配置 GDAL 路径即可直接调用;作者还针对程序异常结束的常见问题给出了排查思路,便于读者快速定位运行时库缺失或路径配置错误,适合需要稳定 GDAL 开发环境的中级 C++ 开发者参考使用。
1. 老项目卡在 GDAL 1.11.5:为什么还要碰这份 mingw64 编译包
手上有个 Qt 桌面端的老项目,地图数据读写全靠 GDAL,版本锁死在 1.11.5,换新版接口对不上,换编译器又得重编一堆依赖。这种场景下,一份已经用 mingw64 编译好的 GDAL 1.11.5 二进制包,价值不在于“新”,而在于“能直接塞进现有工具链跑起来”。它解决的是老项目维护、遗留系统迁移、教学环境复现这三类人的真实痛点:不想从零啃编译脚本,只想拿到头文件、库文件和运行时 DLL,把工程跑通。
这份资源适合谁?一是维护 Qt + C++ 老工程的开发者,二是需要在 Windows 上快速验证 GDAL 读写逻辑的算法同学,三是课程设计里要用 C++ 调 GDAL 但不想被编译环境劝退的新手。不适合谁?指望用最新 EPSG 数据库、最新驱动格式的人,1.11.5 的驱动列表和坐标系库都停在那个年代,这点后面会反复提。先把“这是什么、边界在哪”说清楚,再谈怎么用。
2. 拆包看结构:mingw64 版 GDAL 的目录与依赖关系
2.1 一个合格的 mingw64 GDAL 包应该长什么样
拿到压缩包先别急着解压到工程目录,找个干净路径展开,按目录结构判断这份包是否完整。常见做法是看四类东西:include/下的头文件、lib/下的导入库(.a或.dll.a)、bin/下的运行时 DLL、以及share/gdal/下的数据文件(gdal-data、proj相关)。缺任何一类,后面都会以各种玄学方式翻车。
| 目录/文件 | 作用 | 缺失后的典型症状 |
|---|---|---|
include/gdal*.h | 编译期头文件 | 编译报找不到gdal.h |
lib/libgdal.a或libgdal.dll.a | 链接期导入库 | 链接报 undefined reference |
bin/libgdal-1.dll等 | 运行时动态库 | 程序启动即闪退或报缺 DLL |
share/gdal/ | 驱动与坐标系数据 | 能打开文件但坐标转换结果离谱 |
mingw64 的坑在于“运行时 DLL 不止一个”。GDAL 自身依赖 zlib、libpng、libjpeg、libtiff、libcurl、sqlite3、expat 等一串库,这些要么静态编进libgdal-1.dll,要么以独立 DLL 形式躺在bin/里。判断方法很直接:用依赖查看工具扫一遍libgdal-1.dll的导入表,看它引用了哪些 DLL,再对照bin/里有没有。这一步花五分钟,能省后面两小时的“为什么我这边能跑你那边不能跑”。
2.2 头文件、导入库、DLL 三者的对应关系
C++ 工程接 GDAL 本质是三件事对齐:编译时找头文件、链接时找导入库、运行时找 DLL。mingw64 下导入库通常是libgdal.dll.a,链接参数写-lgdal,链接器会在库搜索路径里找libgdal.dll.a或libgdal.a。如果包里同时有静态库和导入库,优先用导入库配 DLL,体积小、替换方便;只有确定要单文件分发时才考虑静态链接,而 GDAL 静态链接的依赖地狱不建议新手碰。
Qt 工程里还要注意一点:.pro文件的INCLUDEPATH和LIBS是两套路径,别只配了头文件路径忘了库路径。下面这段是常见写法,路径按你实际解压位置改:
# 假设 GDAL 解压在 D:/sdk/gdal-1.11.5-mingw64 GDAL_ROOT = D:/sdk/gdal-1.11.5-mingw64 INCLUDEPATH += $$GDAL_ROOT/include LIBS += -L$$GDAL_ROOT/lib -lgdal # 运行时 DLL 不在系统 PATH 时,把 bin 目录加进构建输出或部署脚本 # 这里仅作注释提醒,qmake 不负责拷贝 DLL逻辑说明:INCLUDEPATH让编译器找到gdal.h、ogr_api.h等;LIBS的-L指定库目录、-lgdal让链接器找libgdal.dll.a。参数上唯一要改的是GDAL_ROOT,其余保持。注意路径里别用反斜杠混写,qmake 对\的处理容易出问题,统一正斜杠最稳。
2.3 用一个小程序验证包是否可用
配好工程前,先写个最小验证程序,别一上来就往大工程里塞。这段代码只做两件事:打印 GDAL 版本、注册全部驱动并数一下驱动数量。能跑通说明头文件、链接、DLL、数据目录四件事基本对齐。
#include <cstdio> #include "gdal.h" int main() { // 打印编译期与运行期版本,二者不一致说明 DLL 拿错了 printf("GDAL_VERSION_NUM (compile): %d\n", GDAL_VERSION_NUM); GDALAllRegister(); // 注册所有驱动,必须在任何 GDAL 调用前执行 printf("GDALVersionInfo (runtime): %s\n", GDALVersionInfo("RELEASE_NAME")); int count = GDALGetDriverCount(); printf("driver count: %d\n", count); GDALDestroyDriverManager(); return 0; }编译命令用 mingw64 的 g++,把 include 和 lib 路径显式写出来,避免依赖环境变量:
g++ verify_gdal.cpp -o verify_gdal.exe \ -ID:/sdk/gdal-1.11.5-mingw64/include \ -LD:/sdk/gdal-1.11.5-mingw64/lib -lgdal跑之前把D:/sdk/gdal-1.11.5-mingw64/bin加进当前终端 PATH,或者把需要的 DLL 拷到 exe 同目录。输出里compile和runtime版本号必须一致,驱动数量一般在 100 以上(1.11.5 时代的具体数字随编译选项浮动,别拿固定值卡)。如果驱动数是 0,八成是GDAL_DATA没指对,下一节专门说。
3. 把 GDAL 接进 Qt 工程:路径、链接与运行时三件事
3.1 环境变量与数据目录:GDAL_DATA 和 PROJ_LIB
GDAL 1.11.5 时代坐标系转换还主要靠 PROJ.4,数据目录通过GDAL_DATA环境变量定位,部分构建还会读PROJ_LIB。这两个变量不设,程序不会立刻崩,但会在做投影转换、读某些格式时给出莫名其妙的结果,属于典型黑匣子问题。常见做法是在程序启动最前面用代码设死,别指望用户环境:
#include <cstdlib> #include "gdal.h" int main(int argc, char *argv[]) { // 用相对 exe 的路径,部署时把 share/gdal 一起带上 _putenv_s("GDAL_DATA", "share/gdal"); _putenv_s("PROJ_LIB", "share/gdal"); GDALAllRegister(); // ... 后续业务代码 return 0; }逻辑说明:_putenv_s是 Windows 下设置环境变量的常用写法,必须在GDALAllRegister()之前调用,因为驱动注册时会读数据目录。参数上,路径建议用相对路径配部署脚本,绝对路径只适合本机调试。注意share/gdal里应该有gcs.csv、pcs.csv、proj_def.dat这类文件,没有就说明包不完整,别硬跑。
3.2 Qt .pro 与 CMake 两种接法的差异
老 Qt 工程多用 qmake,新一点的用 CMake,两种接法思路一致但写法不同。qmake 前面给过,这里补 CMake 版本,方便你在混合工程里对照:
# CMakeLists.txt 片段 set(GDAL_ROOT "D:/sdk/gdal-1.11.5-mingw64") find_path(GDAL_INCLUDE_DIR gdal.h PATHS ${GDAL_ROOT}/include NO_DEFAULT_PATH) find_library(GDAL_LIBRARY NAMES gdal libgdal.dll PATHS ${GDAL_ROOT}/lib NO_DEFAULT_PATH) if(NOT GDAL_INCLUDE_DIR OR NOT GDAL_LIBRARY) message(FATAL_ERROR "GDAL not found under ${GDAL_ROOT}") endif() target_include_directories(your_target PRIVATE ${GDAL_INCLUDE_DIR}) target_link_libraries(your_target PRIVATE ${GDAL_LIBRARY})逻辑说明:NO_DEFAULT_PATH强制只在指定目录找,避免系统里另一份 GDAL 混进来——这是 mingw64 环境最常见的“链接到了别的版本”事故来源。find_library的NAMES同时列gdal和libgdal.dll,是因为不同打包方式命名不一致。参数上,GDAL_ROOT改成你的解压路径即可,其余不用动。
3.3 运行时 DLL 的部署:别让程序在别人机器上闪退
本机跑通不等于能交付。mingw64 编译的程序依赖libgcc_s_seh-1.dll、libstdc++-6.dll、libwinpthread-1.dll这几个运行时,加上 GDAL 自己的一串依赖。交付前用依赖扫描工具把 exe 和libgdal-1.dll的导入表都过一遍,把缺的 DLL 全部拷到输出目录。一个可复现的做法是写个部署脚本,构建后自动拷贝:
# deploy.sh,在 mingw64 终端里跑 OUT=build/deploy mkdir -p "$OUT" cp build/release/your_app.exe "$OUT/" cp D:/sdk/gdal-1.11.5-mingw64/bin/*.dll "$OUT/" cp -r D:/sdk/gdal-1.11.5-mingw64/share "$OUT/" # mingw64 运行时 DLL 从编译器目录取,路径按你的安装改 cp /mingw64/bin/libgcc_s_seh-1.dll "$OUT/" cp /mingw64/bin/libstdc++-6.dll "$OUT/" cp /mingw64/bin/libwinpthread-1.dll "$OUT/"逻辑说明:先拷 exe,再拷 GDAL 的 DLL 和数据目录,最后补编译器运行时。参数上唯一要确认的是 mingw64 安装路径,/mingw64/bin是常见布局。注意share目录要整体拷,别只挑几个文件,驱动依赖的数据文件比你想的多。
4. 避坑与排查:mingw64 下 GDAL 1.11.5 的五条血泪记录
4.1 编译过了链接报一堆 undefined reference
现象:头文件能找到,编译无错,链接阶段报undefined reference to 'GDALAllRegister'之类。原因通常是-lgdal写在了源文件前面,或者库目录里只有静态库而你没带全依赖。解决:把-lgdal放到编译命令末尾(链接顺序敏感),并确认lib/下确实是libgdal.dll.a;若只有libgdal.a,需要额外链接 zlib、tiff 等依赖,建议换回导入库版本。
4.2 程序启动就闪退,事件查看器只说缺模块
现象:双击 exe 无窗口,或弹“找不到 libgdal-1.dll”。原因:DLL 不在 exe 同目录也不在 PATH。解决:把bin/下所有 DLL 拷到 exe 旁边,再用依赖扫描工具确认二级依赖也齐。别只拷libgdal-1.dll一个,它的依赖缺一个照样起不来。
4.3 能打开文件但坐标转换结果差出几百米
现象:读栅格正常,做投影转换后坐标明显偏移。原因:GDAL_DATA没设或指向了错误目录,PROJ 找不到基准面参数,退化成近似转换。解决:启动时显式设GDAL_DATA和PROJ_LIB指向包内share/gdal,并确认目录里有gcs.csv、proj_def.dat。这是最容易被忽略的一条,玄学程度最高。
4.4 Qt 工程里混进了系统另一份 GDAL
现象:本机调试正常,换台机器行为不同,或版本号打印出来不是 1.11.5。原因:PATH 或库搜索路径里有别的 GDAL 抢先被加载。解决:CMake 用NO_DEFAULT_PATH锁死路径,qmake 里LIBS的-L放最前,运行时把包内bin放在 PATH 最前。交付时干脆不依赖 PATH,全用同目录 DLL。
4.5 中文路径下打开数据集失败
现象:英文路径正常,中文路径GDALOpen返回空。原因:1.11.5 时代部分构建对宽字符路径支持不完整,底层仍走窄字符接口。解决:工程内统一用 UTF-8 转本地编码处理路径,或先把文件拷到纯英文临时目录再打开。这条没有优雅解,属于版本边界,提前在设计上规避比事后修更省事。
5. 进阶验证:用驱动清单和读写回环确认这份包真能用
拿到任何一份预编译 GDAL 包,我都会做两件事再决定是否投入工程:数驱动、跑读写回环。数驱动看的是功能覆盖面,读写回环看的是“能写能读、数据不丢”。先看驱动清单,用GDALGetDriverCount配合GDALGetDriver遍历,把支持 Create 的驱动单独列出来,因为很多包能读不能写:
#include <cstdio> #include "gdal.h" int main() { GDALAllRegister(); int total = GDALGetDriverCount(); int writable = 0; for (int i = 0; i < total; ++i) { GDALDriverH drv = GDALGetDriver(i); // 只统计支持创建的驱动,判断能否落盘 if (GDALGetMetadataItem(drv, GDAL_DCAP_CREATE, nullptr) != nullptr) { ++writable; } } printf("total=%d writable=%d\n", total, writable); GDALDestroyDriverManager(); return 0; }逻辑说明:GDAL_DCAP_CREATE是驱动能力元数据键,有值说明支持GDALCreate。参数不用改,输出里writable为 0 说明这份包只读,做导出功能就得换包。这一步能提前暴露“包不完整”的问题,比在业务代码里报错强。
读写回环用 GTiff 最省事:建一个 256x256 的单波段内存数据集,写个渐变值,落盘成 tif,再重新打开读回第一个像素比对。能写能读且值一致,说明驱动、数据目录、DLL 链路全通。这一步我一般写成固定脚本,换任何 GDAL 包都先跑一遍,跑不过就不往下接。血泪经验是:预编译包的问题八成在“缺数据文件”和“DLL 版本错配”,这两个用回环测试都能逼出来。
最后说个习惯。从那以后我每次接预编译 GDAL 包,都强制走一遍“版本打印 → 驱动计数 → 读写回环 → 依赖扫描”四步,四步全绿才往工程里接,宁可多花二十分钟,也不在业务代码里跟玄学闪退纠缠。这份 mingw64 编译的 GDAL 1.11.5 对老项目维护来说够用,边界也清楚:别指望新格式、新坐标系库,把它当稳定底座,把数据目录和 DLL 部署做扎实,剩下的就是正常 C++ 调用。希望帮到你。
本文还有配套的精品资源,点击获取