简介:面向Visual Studio 2017开发者的预编译GDAL包,省去CMake配置、依赖处理与编译链接流程,适合在C++项目中读写栅格、矢量地理空间数据的GIS开发者与学习者。压缩包共6305个文件、约103.88MB,包含大量obj、cpp、h、c源码与头文件,以及lib、dll、exe、vc工程、gnumakefile等构建运行文件,并附带Python脚本、文档和测试数据,便于核对头文件、库文件及依赖。已有2110人学习下载,对VS2017环境配置具有参考价值。将头文件与gdal_i.lib等库文件加入项目后,可结合包内API变更说明、工具手册和示例数据,快速验证数据打开、转换与处理流程,减少环境排错时间。
1. 版本对不上的代价:为什么这份 GDAL 只能在 VS2017 下用
上周又有人拿着这包「gdal VS2017 编译」的产物来找我,说换了台机器就编不过,满屏 LNK2019。我打开工程属性一看,平台工具集写着 v142。库本身没问题,问题在于它是在 v141 下编出来的:VS2017 对应 MSVC 14.1,VS2019 是 14.2,两边的头文件和二进制不是一回事。C/C++ 的二进制兼容没有想象中那么宽松,尤其 GDAL 这种内部大量使用 STL 容器、异常和std::string的库,工具集差一级,链接期就会给你脸色看。这份资源适合手上锁死了 VS2017 的老项目、教学机房、还有一些历史工程不方便升级的团队。后面按环境核对、解压落盘、工程配置、排错、验证的顺序走一遍,照着做基本能跑通。
2. 装前把环境列清:v141 工具集、位数与运行库三件套
安装预编译库最容易犯的错,是把它当成「解压完加个路径」的活。实际上决定成败的是三个维度:编译器工具集版本、目标平台位数、C 运行库链接方式。这三项任意一项对不上,症状都不一样,有的在编译期就报错,有的要等你把程序拷到另一台机器上才炸。所以在解压之前,先花十分钟把这三件事确认清楚,比后面调半天链接错误划算得多。
2.1 确认你的 VS2017 真的带 v141 工具集
先别急着信「我装的是 VS2017」这句话。VS2017 的安装器允许你只装一部分工作负载,也允许后期把 v140 或 v141 的某个小版本单独勾上,甚至有人在 VS2019 里挂了个 v141 的兼容工具集来编译老代码。这两种情况表面都叫「能编 VS2017 的工程」,实际用的工具集未必一样。确认方法是打开工程属性页,看「配置属性 → 常规 → 平台工具集」,显示Visual Studio 2017 (v141)才算对。命令行下更直接,用 VS2017 自带的开发者命令提示符看cl版本,主版本号 19.16 附近才对应 MSVC 14.1。
除了编译器,还得确认这包库自己是什么成色。预编译库的 lib 文件里写了机器类型和运行库标记,用 dumpbin 一眼就能看出来,比猜靠谱:
:: 打开「VS2017 的 x64 本机工具命令提示符」,别用普通 cmd :: 1) 看 lib 的机器类型:x64 还是 x86 dumpbin /headers D:\sdk\gdal-v141\lib\gdal_i.lib | findstr /i "machine" :: 2) 看这个 lib 对运行库和迭代器调试级别的要求 dumpbin /directives D:\sdk\gdal-v141\lib\gdal_i.lib | findstr /i "FAILIFMISMATCH"第一条命令正常会输出8664 machine (x64)或14C machine (x86),这就是这个 lib 能接受的工程平台,写死在文件里,改不了。第二条命令会吐出RuntimeLibrary=MD_DynamicRelease和_ITERATOR_DEBUG_LEVEL=0这样的字符串,它们会被链接器当作硬性约束去比对你工程里的编译选项。注意dumpbin必须在开发者命令提示符里跑,普通 cmd 找不到它,环境变量 PATH 里没有 MSVC 的 bin 目录。
2.2 x64 还是 Win32,Debug 还是 Release
位数这一项反而是最好判断的,因为报错很直白。工程平台选 Win32 却链了 x64 的 lib,链接器会直接抛LNK1112: module machine type 'x64' conflicts with target machine type 'x86',反过来也一样。真正容易阴沟翻船的是 Debug/Release 这对组合。多数预编译包只提供 Release 版本的库,因为 Debug 版 lib 体积大、分发没意义;可很多人新建工程默认就是 Debug|x64,一编译就撞在_ITERATOR_DEBUG_LEVEL上。
这张表建议存下来,出问题时对着看一眼,比翻文档快:
| VS 版本 | MSVC 工具集 | 平台工具集标识 | 主版本号 |
|---|---|---|---|
| VS2015 | MSVC 14.0 | v140 | 19.0 |
| VS2017 | MSVC 14.1 | v141 | 19.1x |
| VS2019 | MSVC 14.2 | v142 | 19.2x |
| VS2022 | MSVC 14.3 | v143 | 19.3x |
同一个工具集内部,Debug 与 Release 的差别主要体现在两处编译开关:/MD对/MDd,以及_ITERATOR_DEBUG_LEVEL对 0 对 2。这两项都会被写进 lib 的指令段,链接时严格比对。所以「Debug 工程链 Release 库」这种混搭,编译器不会拦你,链接器会拦你。
我一般的处理方式是:前期开发阶段直接把解决方案配置切到 Release 调试,等代码稳了再单独处理 Debug 构建;如果确实要 Debug,就照着 2.3 的方式把运行库对齐,而不是硬塞。
2.3 /MD 与 /MT:运行库对不上的连锁反应
C 运行库的链接方式是三类问题里最隐蔽的。GDAL 编译时用的是动态运行库/MD(Release)或/MDd(Debug),如果你的工程用的是/MT,链接阶段就会报:
gdal_i.lib(cpl_conv.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease'这条报错看着吓人,其实信息量很足:左边是你工程里设的,右边是 lib 里要求的。解决方向只有一个——让工程服从 lib,而不是反过来。改法在「配置属性 → C/C++ → 代码生成 → 运行库」,四个配置组合分别对应:
- Release + 动态运行库 →
多线程 DLL (/MD) - Debug + 动态运行库 →
多线程调试 DLL (/MDd) - Release + 静态运行库 →
多线程 (/MT) - Debug + 静态运行库 →
多线程调试 (/MTd)
这里有个取舍值得说清楚:/MT的好处是发布时不依赖 VC 运行库再分发包,单文件扔过去就能跑;代价是你没法链接任何用/MD编的第三方库,包括这份 GDAL。既然资源已经定死了/MD,工程这边就别挣扎,跟着改成/MD,发布时把 VC 运行库一并带上或者让目标机器装一次运行库包。这一点在部署到内网机器时尤其要注意,缺运行库的表现是程序启动就闪退,事件查看器里能看到0xc000007b或者模块加载失败。
3. 解压与落盘:目录结构、路径规范与环境变量
环境核对完,接下来是把这包东西放到硬盘上。这一步看起来纯体力活,实际上落盘位置和路径写法决定了后面一半的玄学问题。我见过太多人把 SDK 解压在桌面、解压在「C:\Program Files」下、解压在一个叫「新建文件夹(2)」的目录里,然后在工程属性里写一长串带空格带中文的绝对路径,最后被编译器莫名其妙地截断。
3.1 include、lib、bin、data 四个目录各管什么
预编译包解开后,常见做法是这么几个目录,认清楚它们的分工,后面配置才不会漏:
| 目录 | 内容 | 谁在用 |
|---|---|---|
| include | gdal.h、gdal_priv.h、ogr_api.h、cpl_conv.h 等头文件 | 编译器,配置到「附加包含目录」 |
| lib | gdal_i.lib(部分包叫 gdal.lib)导入库 | 链接器,配置到「附加库目录」+「附加依赖项」 |
| bin | gdalXXX.dll 及 proj、geos、sqlite3 等运行时依赖 | 程序运行期,必须在 PATH 或输出目录里 |
| data | gcs.csv、pcs.csv、gdalvrt.xsd、proj.db 等数据文件 | 运行期,靠 GDAL_DATA 环境变量定位 |
include和lib是编译链接期的事,配错了当场报错,好排查。bin和data是运行期的事,配错了往往表现为「编译全过,一跑就崩」或者「能跑但读不了文件」,属于黑匣子型故障。我建议把这两类分开记:前者改工程属性,后者改系统环境变量。
顺带说一句版本差异。GDAL 3.x 之后坐标转换体系换成了 PROJ 6 以上,data目录里会出现proj.db,而 2.x 时代靠的是一堆 csv。如果你拿到的包里data目录很小、只有几个 csv,那大概率是老版本;如果里面有个几 MB 的proj.db,说明 proj 是内嵌依赖的,PROJ_LIB也得指过去。不确定就进去看一眼,别靠猜。
3.2 路径别带空格、别带中文、别太长
落盘位置我固定用D:\sdk\gdal-v141这种形式,短、纯 ASCII、层级浅。原因不是洁癖,是编译工具链对路径的处理确实有边界:MSBuild 的属性传递、命令行长度限制、以及某些脚本对空格的处理都可能出问题。路径里带空格时,包含目录在属性页里填进去看着正常,实际展开成命令行后可能被拆成两段,然后编译器报「找不到 gdal.h」,你在属性页里怎么看都是对的,这种时候特别容易怀疑人生。
中文路径的问题更微妙。MSVC 默认按系统 ANSI 代码页解析源文件和路径,如果系统区域设置和工作路径编码不一致,可能出现「文件明明存在,编译器说打不开」。这类问题在我这里属于典型的玄学故障,排查成本极高,收益极低,不如一开始就避开。
落盘之后建议做一次完整性检查,确认关键文件都在:
:: 三个关键文件,缺一个都别往下走 dir /b D:\sdk\gdal-v141\include\gdal.h dir /b D:\sdk\gdal-v141\lib\gdal_i.lib dir /b D:\sdk\gdal-v141\bin\*.dll :: data 目录里应该有投影和格式相关的数据文件 dir /b D:\sdk\gdal-v141\data | findstr /i "proj.db gcs.csv pcs.csv"第一条确认头文件在,第二条确认导入库在(名字可能是gdal.lib,按实际改),第三条看 DLL 有没有齐。第四条是很多人漏掉的:data目录如果空着或者不完整,程序能启动、能链接,但一打开带投影的影像就返回空指针,错误信息还特别含糊。
3.3 PATH、GDAL_DATA、PROJ_LIB 三个变量怎么设
运行期靠三个环境变量定位资源。PATH加bin目录,让系统能找到 DLL;GDAL_DATA指到data目录,让 GDAL 能找到坐标系统和格式定义;如果包里有独立的 proj 目录或者proj.db在别处,再加一个PROJ_LIB。设置方式有两种,我更推荐手工在「系统属性 → 环境变量」里加,原因下面会说。
:: 不推荐:setx 里直接展开 %PATH% :: 这会把当前进程的用户 PATH 和系统 PATH 拼在一起写回用户变量, :: 多跑几次就出现重复项,PATH 越来越长,最后超过长度限制 setx PATH "%PATH%;D:\sdk\gdal-v141\bin" :: 推荐:只追加自己的目录,或者干脆用图形界面手工加 setx GDAL_ROOT "D:\sdk\gdal-v141" setx GDAL_DATA "D:\sdk\gdal-v141\data" setx PROJ_LIB "D:\sdk\gdal-v141\data"setx的第一个坑就是%PATH%展开。它不会区分用户变量和系统变量,取的是当前进程合并后的值,写回去只写用户变量那一份,久而久之 PATH 就臃肿了,而且是不可逆的——只能手工去清。第二个坑是setx设置完对当前已经打开的窗口不生效,必须重开命令行或者重开 VS 才能读到新值;很多人设完变量直接 F5 调试,发现还是找不到 DLL,就是这个原因。第三个坑是setx有 1024 字符的长度限制,PATH 长的时候会被截断,截断之后前面那些系统路径就没了,症状是某些系统命令突然不可用。
设完之后验证一下,别嫌麻烦:
:: 新开一个 cmd 窗口执行,不要在当前窗口 echo %GDAL_DATA% where gdalinfo.exeecho能打出路径说明变量生效,where能找到 exe 说明 PATH 生效。两条都过,运行期环境就算齐了。
4. VS2017 工程配置:包含目录、库目录与附加依赖项的填法
环境变量管运行期,工程属性管编译链接期。这一章的四步配置是所有 C++ 接第三方库的通用套路,但 GDAL 有几个自己的细节:lib 名字不是gdal.lib就是gdal_i.lib,写错了报「无法打开输入文件」;再有就是配置的作用范围,很多人只在 Debug|x64 下配了一遍,切到 Release|x64 就全丢,白折腾。
4.1 附加包含目录与附加库目录
右键工程 → 属性,注意右上角的「配置」和「平台」两个下拉框,先选所有配置、平台选x64(或者按你实际的平台),这样一次改动对所有配置生效。然后:
- 配置属性 → C/C++ → 常规 → 附加包含目录:填
D:\sdk\gdal-v141\include - 配置属性 → 链接器 → 常规 → 附加库目录:填
D:\sdk\gdal-v141\lib
两个路径都建议用分号结尾或者追加在原有值后面,别把默认的$(IncludePath)覆盖掉,否则标准库头文件会找不到,报一堆cmath、cstdio打不开。属性页右侧有个下拉箭头,选「编辑」进去逐行加,比直接覆盖文本框安全。
4.2 附加依赖项写哪个 lib
链接器 → 输入 → 附加依赖项,填导入库文件名。这份资源里通常是gdal_i.lib,也有包直接给gdal.lib,去lib目录看一眼实际文件名,照着抄。别把 DLL 名写进去,也别把静态库和导入库搞混——导入库几百 KB 到几 MB,静态库通常几十 MB 起,体积差得明显。
// 一个最小可编译的验证片段,先确认头文件和链接都通了 #include "gdal_priv.h" #include "cpl_conv.h" int main() { GDALAllRegister(); // 注册所有驱动,必须最先调用 printf("%s\n", GDALVersionInfo("--version")); // 打印实际版本,确认链的是哪一份 return 0; }GDALAllRegister()是必须的,不调用它,后面GDALOpen一律返回空,因为驱动表是空的。这个函数内部会初始化驱动管理器,同时读取GDAL_DATA指向的目录,所以如果这个环境变量没设对,某些格式即使注册了也会在打开时报「not recognized as a supported file format」。GDALVersionInfo传"--version"拿到的是完整版本串,传"RELEASE_NAME"只拿版本号,用它来确认「我链的到底是哪份库」很有效——尤其是在机器上装过多个 GDAL 的时候。
4.3 用属性表把配置固化下来
一个工程配完不算完,同一个解决方案里还有三个项目、明年还有五个新项目,每次重配一遍纯属浪费。VS 的属性表(.props)就是干这个的,把 GDAL 相关的路径和依赖抽出来,以后右键「添加现有属性表」就行。
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Label="UserMacros"> <!-- 只在这里改一次路径,换机器只动这一行 --> <GDALRoot>D:\sdk\gdal-v141</GDALRoot> </PropertyGroup> <ItemDefinitionGroup> <ClCompile> <AdditionalIncludeDirectories>$(GDALRoot)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <AdditionalLibraryDirectories>$(GDALRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <AdditionalDependencies>gdal_i.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> </Project>这段 XML 存成gdal-v141.props,放在解决方案目录下,用「属性管理器」窗口(视图 → 其他窗口 → 属性管理器)加到对应配置里。%(...)是继承原值的写法,千万别删,删了就等于把默认的包含目录和依赖项全清了。GDALRoot定义成用户宏的好处是换机器、换盘符只改一处,其他项目引用同一个属性表自动跟着变。
再补一个生成后事件,把 DLL 拷到输出目录,省得每次都靠 PATH:
:: 生成事件 → 生成后事件 → 命令行 :: /Y 覆盖同名文件,/D 只拷比目标新的,避免每次都全量复制 xcopy /Y /D "$(GDALRoot)\bin\*.dll" "$(OutDir)"拷 DLL 到输出目录这招在调试期特别省事:不用改 PATH,不用重开 VS,程序在哪个目录跑就在哪个目录找 DLL。代价是发布时要记得把整个输出目录打包,别只拷 exe。
5. 排错与避坑:链接期和运行期最常见的几类翻车
配置配完,接下来大概率不是一次过,而是几种固定报错轮流出现。这一章按「现象 → 原因 → 解决」列几条我踩过次数最多的,基本都是血泪经验,对着症状找就行。
5.1 现象:一编译就是几十条 LNK2019 无法解析的外部符号
现象:报错里反复出现GDALAllRegister、GDALOpen、GDALClose这类符号,且全是LNK2019,有的还伴随LNK1120。
原因:链接器根本没拿到 GDAL 的导入库。可能是「附加依赖项」里 lib 名字写错了(写成了gdal.lib但实际是gdal_i.lib),可能是平台选错了只在 Win32 下配了但工程是 x64,也可能附加库目录路径末尾少了反斜杠导致拼出来的路径不对。
解决:先去lib目录确认文件名,一字不差地填进附加依赖项;然后确认属性页右上角的平台下拉框选的是当前实际编译的平台;最后检查链接器 → 常规 → 「显示进度」设为/VERBOSE:LIB,重新编译,输出窗口里能看到链接器实际搜索了哪些目录,路径对不对一眼就知道。
5.2 现象:LNK2038 mismatch detected for '_ITERATOR_DEBUG_LEVEL'
现象:value '2' doesn't match value '0',或者反过来。前面通常还有几条LNK2001。
原因:工程是 Debug 配置,用的是/MDd,_ITERATOR_DEBUG_LEVEL默认为 2;而链接的 GDAL 库是 Release 编的,要求该值为 0。STL 容器的内存布局在两种模式下不同,所以链接器必须拦住。
解决:三种走法。一,把工程配置切到 Release 调试,这是最省事的;二,在「C/C++ → 预处理器」里加_ITERATOR_DEBUG_LEVEL=0并把运行库改成/MD,等于强行伪装成 Release,能跑但 Debug 下的迭代器检查就没了;三,找一份 Debug 版 GDAL 库,多数预编译包不提供,得自己编。
5.3 现象:编译链接全过,双击 exe 直接闪退,错误码 0xc000007b
现象:VS 里 F5 能跑,拷到别的机器或者直接双击 exe 就闪退,事件查看器里能看到0xc000007b或者「无法找到 gdalXXX.dll」。
原因:0xc000007b通常是位数不匹配——64 位程序加载了 32 位 DLL,或者反过来。找不到 DLL 则是bin目录不在 PATH 里,或者依赖链上少了一个(GDAL 依赖 proj、sqlite3、zlib 等,缺一个就整条链断)。
解决:用 [Dependencies](原 Dependency Walker 的替代品)打开 exe,看是哪一级依赖标红。位数问题看红框模块的机器类型;缺 DLL 就把它从bin拷到 exe 同目录,比改 PATH 稳妥。发布前我习惯把bin下所有 DLL 一次性拷进输出目录,宁可多拷几个几十 MB,也别到了客户现场才发现少一个。
5.4 现象:能跑,但 GDALOpen 一直返回空,错误信息含糊
现象:GDALOpen或GDALOpenEx返回nullptr,CPLGetLastErrorMsg()打出「not recognized as a supported file format」,但文件明明是好的,用别的工具能打开。
原因:GDAL_DATA没设或者指错了。GDAL 判断格式靠的是驱动注册表,部分驱动在初始化时要读data目录里的定义文件;目录不对,驱动注册失败,表现就是「什么格式都不认识」。
解决:程序里打印一下CPLGetConfigOption("GDAL_DATA", "(unset)"),看实际取到什么值。如果是(unset),说明环境变量没生效,重开窗口或者直接在代码里用CPLSetConfigOption("GDAL_DATA", "D:\\sdk\\gdal-v141\\data")兜底。注意路径里的反斜杠要转义。
5.5 现象:中文路径的影像打不开,或者属性表读到乱码
现象:英文路径正常,换成中文目录就返回空指针;或者属性值里的中文显示成问号。
原因:Windows 上 GDAL 默认按 UTF-8 处理文件名,而 MSVC 工程里的窄字符串字面量默认按系统 ANSI 代码页编码,两边编码不一致。
解决:把源文件保存为带 BOM 的 UTF-8,或者在工程里加/utf-8编译开关统一编码;路径拼接尽量用std::filesystem::path或者宽字符接口转 UTF-8。临时兜底可以在初始化后加一行CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"),让它按本地编码解析,但这招在跨平台代码里别用。
6. 装完怎么验证:三步跑通读写与坐标转换
配置写完不算装完,得有一组能重复执行的验证动作。我的习惯是分三层:先验运行环境,再验链接,最后验功能。三层都过,这套环境才算真的落地。
第一层用随包的命令行工具,不涉及任何工程配置,纯粹确认 DLL 和 data 目录是好的:
:: 在任意目录执行,验证 PATH 和 GDAL_DATA 是否生效 gdalinfo --version gdalinfo --formats | findstr /i "GTiff"第一条能打出实际版本号,说明bin在 PATH 里且 DLL 依赖完整;第二条能列出 GTiff,说明驱动注册正常,data目录基本没问题。如果第一条就报「不是内部或外部命令」,回去查 PATH;如果第二条什么都没有,基本就是GDAL_DATA的事。
第二层验链接,用 4.2 那个最小片段编一个控制台程序,能打印出版本串就说明头文件、库目录、附加依赖项三处都对。第三层验功能,跑一个完整的打开—读信息—坐标转换流程:
#include "gdal_priv.h" #include "ogr_spatialref.h" #include "cpl_conv.h" int main(int argc, char** argv) { GDALAllRegister(); // 1) 确认链的是哪份库、data 目录在哪 printf("version : %s\n", GDALVersionInfo("--version")); printf("GDAL_DATA: %s\n", CPLGetConfigOption("GDAL_DATA", "(unset)")); // 2) 打开影像,只读模式,不给驱动名让 GDAL 自己识别 GDALDataset* ds = static_cast<GDALDataset*>( GDALOpenEx(argv[1], GDAL_OF_RASTER | GDAL_OF_READONLY, nullptr, nullptr, nullptr)); if (ds == nullptr) { printf("open failed: %s\n", CPLGetLastErrorMsg()); // 失败一定打这句,否则无从查起 return 1; } printf("size: %d x %d, bands: %d\n", ds->GetRasterXSize(), ds->GetRasterYSize(), ds->GetRasterCount()); // 3) 取投影并做一次坐标转换,验证 proj 数据可用 const OGRSpatialReference* srs = ds->GetSpatialRef(); if (srs != nullptr) { OGRSpatialReference dst; dst.importFromEPSG(4326); OGRCoordinateTransformation* ct = OGRCreateCoordinateTransformation( const_cast<OGRSpatialReference*>(srs), &dst); if (ct != nullptr) { double x = ds->GetGeoTransform()[0]; double y = ds->GetGeoTransform()[3]; if (ct->Transform(1, &x, &y)) printf("transform ok -> lon=%.6f lat=%.6f\n", x, y); OCTDestroyCoordinateTransformation(ct); } else { printf("create transform failed, check GDAL_DATA / PROJ_LIB\n"); } } GDALClose(ds); return 0; }这段代码三个检查点对应三类故障:GDALVersionInfo打不出来说明链接有问题;GDALOpenEx返回空说明驱动或数据目录有问题;OGRCreateCoordinateTransformation返回空说明 proj 那套数据没找到,通常是PROJ_LIB没指对。每一步失败都打CPLGetLastErrorMsg(),这是 GDAL 唯一的错误出口,不打就等于蒙着眼睛调。
真跑起来的时候,用一张带投影的 GeoTIFF 做输入,输出里能看到尺寸、波段数和转换后的经纬度,这套安装就算彻底通了。从那以后我每次配完 GDAL 都强制走一遍这三层验证,不管多赶时间——省下的那十分钟,往往要在后面用两小时找回来。希望帮到你。
本文还有配套的精品资源,点击获取