简介:面向使用 Visual Studio 2017 进行 GIS 或 C++ 开发的工程师,这份已编译完成的 GDAL 库免去了 CMake 配置、依赖处理与驱动裁剪等繁琐编译环节,适合需要快速验证栅格/矢量读写能力、搭建原型或排查环境问题的开发者。压缩包约 103.88MB,共 6305 个文件,以 obj、cpp、h、c 及 vc 工程文件为主,同时包含 lib、dll、exp 等库文件与 html、txt、readme 等说明文档,并附带 ogr2ogr、gdalwarp、gdal_translate 等命令行工具及测试数据,便于核对版本与功能。已有 2110 人学习下载。包内保留较完整的源码与工程结构,可辅助定位编译选项和依赖关系,减少重复构建成本,也为理解 GDAL 模块组成与后续扩展提供参考。
1. 编译好的 GDAL 落在 VS2017 上:先搞清楚它绑死了什么
上周接手一个老项目,交接文档只有一行字:编译好的 GDAL,仅适用于 VS2017。压缩包解压出来是 include、lib、bin 三个目录,看着干净,直接加进工程却一路 LNK2019。问题不在 GDAL 本身,而在“编译好”这三个字背后藏着三条硬约束:工具集是不是 v141、运行库是 /MD 还是 /MT、以及 x86 还是 x64。任意一条对不上,这份库就完全用不了。这份资源适合手里有 VS2017 工程、要读栅格影像或做坐标转换、又不想花半天从源码编 GDAL 的人;也适合用 VS2019/2022 但愿意装回 v141 工具集的人。下面按拆包核对、接进工程、跑通场景、排错、自检的顺序走一遍,每一步都给出可抄的命令和代码。
2. 拆包对号:工具集、运行库、架构三处必须对上
2.1 为什么“编译好”必须绑定编译器版本
MSVC 的 C++ ABI 不跨大版本兼容。v140、v141、v142、v143 各自带一套 CRT 和一套 STL 实现,连std::string的内部布局都可能不一样。GDAL 虽然对外主要暴露 C 函数(GDALOpenEx、GDALRasterIO这一层),但它同时导出了大量 C++ 类:GDALDataset、OGRGeometry、OGRSpatialReference。这些类有虚函数表、有成员布局,跨工具集去 new 一个再 delete,等于在堆的边界上冒险。所以“仅适用于 VS2017”翻译过来就是三条:v141 工具集、某个固定的运行库选项、某个固定架构。
第二条约束是运行库。VS 工程里「C/C++ → 代码生成 → 运行库」有四个可选值:/MD、/MDd、/MT、/MTd。GDAL 的二进制包如果是/MD编的,你的工程也必须/MD;用/MT去链,报的错通常不是“运行库不匹配”这么直白,而是LNK2038: 检测到“RuntimeLibrary”的不匹配项,或者更隐蔽的:能链上,运行到某个CPLFree就崩。Debug 配置还有第三条:_ITERATOR_DEBUG_LEVEL,Debug 是 2、Release 是 0,混用同样是 LNK2038。
如果手上是 VS2019 或 VS2022,不一定非要重新编 GDAL。VS Installer 的「单个组件」里能找到旧版 MSVC 生成工具,装上 v141 之后,在工程属性「常规 → 平台工具集」里切回 Visual Studio 2017 (v141),这份包就能直接用。这条路比从源码编 GDAL 划算太多——编 GDAL 要先把 PROJ、SQLite、TIFF、JPEG、PNG、zlib 一串依赖凑齐,光依赖版本对齐就够折腾半天。需要注意的是 v141 工具集在新版 VS 里只是“能用”,不再更新,长期项目还是要规划迁移。
判断包里到底是/MD还是/MT,不用猜,dumpbin能直接读出来:
:: 读 .drectve 段里的默认库指令 :: MSVCRT / MSVCRTD 出现 = /MD 系;LIBCMT / LIBCMTD 出现 = /MT 系 dumpbin /directives gdal_i.lib | findstr /i "MSVCRT LIBCMT"这条命令的逻辑是:MSVC 在编译每个 obj 时会把运行库选择写进.drectve段,最终汇总到 lib 里。如果输出里同时出现MSVCRT和LIBCMT,说明这个包是混着编的(第三方依赖用了不同选项),这种包最容易在运行期出堆相关的怪问题,遇到就要谨慎。
2.2 三分钟看清包里每个目录的职责
别急着配工程,先把目录结构过一遍。一份完整的预编译 GDAL 包,目录职责大致如下:
| 目录 | 典型内容 | 漏掉之后的症状 |
|---|---|---|
include | gdal.h、gdal_priv.h、ogr_*.h、cpl_*.h | 编译期直接找不到头文件 |
lib | gdal_i.lib(导入库)、gdal.lib(静态库) | 编译过、链接报 LNK2019 |
bin | gdal.dll以及proj_*.dll、sqlite3.dll、geos*.dll等依赖 | 双击 exe 闪退,错误码 0xc0000135 |
data或share | proj.db、gcs.csv、pcs.csv、各种.xsd | 能打开影像,但坐标转换返回空 |
这里有个顺序问题值得强调:先确认这个包本身是完整的,再动自己的工程。包里一般会带gdalinfo.exe,直接在命令行跑一下:
:: 先证包,再证工程 gdalinfo --version gdalinfo --formats | findstr /i "GTiff HFA ENVI"如果gdalinfo自己都跑不起来,后面所有链接错误都是伪问题,先解决 dll 和 data 目录。如果gdalinfo --version正常,但gdalinfo your.tif报坐标相关的错,那问题一定在data目录,跟 lib、include 一点关系都没有。
2.3 用 dumpbin 确认架构和导出符号
架构对不上是最容易被忽略的一条。x64 工程链了 x86 的 lib,报错信息是LNK1112: 模块计算机类型“x86”与目标计算机类型“x64”冲突,还算好认;但如果链接器目录里同时有 x86 和 x64 两份 lib,工程又只写了一个gdal_i.lib,那就得靠工具确认到底链进去的是哪一份。
:: 看导入库的目标架构:x64 会显示 machine (x64),32 位显示 machine (x86) dumpbin /headers lib\gdal_i.lib | findstr /i "machine" :: 看 dll 的运行期依赖,缺哪个第三方 dll 在这里能直接看出来 dumpbin /dependents bin\gdal.dll :: 确认导入库里真的有目标符号,排除拿到空壳 lib 的可能 dumpbin /exports bin\gdal.dll | findstr /i "GDALOpenEx GDALRasterIO OGRCreateCoordinateTransformation"/dependents这条的输出要逐个核对:bin目录里必须能找到列出来的每一个 dll,或者它们已经在系统目录里。老机器上最常见的坑是:某台机器装了别家软件自带的proj_6.dll,版本比包里的旧,系统按 PATH 顺序先加载了那个,结果坐标转换行为诡异。这类问题查起来极其费劲,所以后面我会建议把 dll 全部拷到 exe 同级目录,绕开 PATH 竞争。
3. 接进 VS2017 工程:四行属性配置加一段能跑的读取代码
3.1 工程属性四项配置与运行库一致性检查
真正要改的属性就四组,但每一组都有一个对应的“后悔药”做法,我一般按下面这样配:
第一组,包含目录和库目录。不建议写死绝对路径,在「VC++ 目录」里新建一个用户宏,比如GDAL_ROOT = D:\sdk\gdal-vs2017,然后包含目录填$(GDAL_ROOT)\include,库目录填$(GDAL_ROOT)\lib。换机器、换盘符只改宏一处。
第二组,链接器附加依赖项填gdal_i.lib。注意 Windows 下 GDAL 的导入库通常叫gdal_i.lib,静态库叫gdal.lib,具体以包里实际文件名为准,别照着别人的博客硬写。
第三组,运行库必须和包一致,见 2.1 的检测结果。
第四组,调试时让 exe 找到 dll。「调试 → 环境」里填PATH=$(GDAL_ROOT)\bin;%PATH%,这只在 VS 里 F5 有效。真正发布时,把bin下所有 dll 拷到 exe 同级目录——这是最省心的做法,比让用户改系统 PATH 靠谱一百倍,也顺带解决了上面说的 dll 版本竞争问题。
如果手上有十几个工程都要接,别一个个点属性页,做一个.props属性表,在「属性管理器」里添加,一次性固化上面四组配置。我吃过这个亏:某个工程单独改过运行库,没人记得,半年后重新编一次全线崩。
3.2 一段最小可跑的读取代码
配置好之后先别上业务逻辑,跑个最小验证:打开影像、读尺寸、读地理变换、读一行像素。
#include <gdal_priv.h> #include <cpl_conv.h> #include <cpl_error.h> #include <vector> #include <iostream> int main(int argc, char** argv) { if (argc < 2) return 2; // 进程内只需要注册一次,重复调用会拖慢启动 GDALAllRegister(); GDALDataset* ds = static_cast<GDALDataset*>( GDALOpenEx(argv[1], GDAL_OF_RASTER | GDAL_OF_READONLY, // 只读打开,避免锁文件 nullptr, nullptr, nullptr)); if (ds == nullptr) { // 打不开时一定要打错误栈,光看返回值什么都看不出来 std::cerr << "open failed: " << CPLGetLastErrorMsg() << std::endl; return 1; } const int w = ds->GetRasterXSize(); const int h = ds->GetRasterYSize(); const int bands = ds->GetRasterCount(); std::cout << w << " x " << h << " bands=" << bands << std::endl; // gt[0], gt[3] 是左上角坐标;gt[1], gt[5] 是像素尺寸 // gt[2], gt[4] 是旋转项,绝大多数影像为 0,不为 0 说明是带姿态的影像 double gt[6] = {0}; if (ds->GetGeoTransform(gt) == CE_None) { std::cout << "origin=" << gt[0] << "," << gt[3] << " pixel=" << gt[1] << "x" << gt[5] << std::endl; } else { std::cout << "no geotransform, 可能缺 .aux.xml 或投影信息" << std::endl; } // 只读第一行,验证 RasterIO 的数据通路 std::vector<float> line(static_cast<size_t>(w), 0.0f); GDALRasterBand* band = ds->GetRasterBand(1); if (band != nullptr) { CPLErr err = band->RasterIO(GF_Read, 0, 0, w, 1, // 源窗口:x, y, 宽, 高 line.data(), w, 1, // 目标缓冲与宽高 GDT_Float32, // 目标类型,会做自动转换 0, 0); std::cout << "RasterIO=" << (err == CE_None ? "ok" : "fail") << std::endl; } GDALClose(ds); // 官方方式是 GDALClose,不要 delete return 0; }几个参数值得单独说清楚。GDALOpenEx的第二个参数是打开标志,GDAL_OF_RASTER表示只要栅格驱动、GDAL_OF_READONLY表示只读,两个一起用能显著减少驱动探测时间。第五个参数是options字符串数组,常用的是"NUM_THREADS=ALL_CPUS"。GetGeoTransform返回CE_None才算有仿射变换;返回CE_Failure不代表打不开,只是缺地理参考信息。RasterIO的源窗口和目标缓冲宽高可以不同,它内部会做最近邻或双线性重采样(由GDALRasterIOExtraArg控制,默认最近邻)。最后,GDALDataset是 C++ 类但内部用引用计数管理,用delete在部分版本上会漏掉 flush,GDALClose是唯一稳妥的关法。
3.3 GDAL_DATA 与 PROJ_LIB:两个必须显式设置的环境变量
这两个变量不设,代码能编能链,跑到坐标转换才炸,属于典型的“前面全绿、最后一步死”。GDAL_DATA指向data目录,GDAL 在里面找投影 WKT 模板和各种 csv;PROJ_LIB指向同一个目录,PROJ 在里面找proj.db。GDAL 3 之后坐标转换全部走 PROJ,proj.db找不到时,OGRCreateCoordinateTransformation会直接返回nullptr,而不会给你任何提示。
稳妥的设法是进程启动时、GDALAllRegister之前,把真实环境变量写进去,路径从 exe 位置反推,不要写死绝对路径:
#include <windows.h> #include <string> void SetupGdalEnv() { wchar_t exePath[MAX_PATH] = {0}; GetModuleFileNameW(nullptr, exePath, MAX_PATH); // 取 exe 所在目录,再拼上随程序一起发布的 gdal-data 目录 std::wstring dir(exePath); dir = dir.substr(0, dir.find_last_of(L"\\/")); std::wstring dataDir = dir + L"\\gdal-data"; // 两个变量都要设:GDAL_DATA 给 GDAL,PROJ_LIB 给 PROJ // 必须在 GDALAllRegister() 之前调用,注册之后再设不生效 SetEnvironmentVariableW(L"GDAL_DATA", dataDir.c_str()); SetEnvironmentVariableW(L"PROJ_LIB", dataDir.c_str()); }这里有个顺序细节容易翻车:环境变量要在GDALAllRegister()之前设。GDAL 内部有个配置缓存,驱动注册时会读一次,之后再改环境变量,部分路径不会重新读。如果确实需要在运行时改,用CPLSetConfigOption("GDAL_DATA", 绝对路径)走 GDAL 自己的配置层,这条一定能生效,但同样建议在注册前调。
3.4 CMake 工程怎么接这份包
老工程用属性页,新一点的工程多半是 CMake。别写find_package(GDAL)——它会去系统里找 GDALConfig.cmake,跟这份预编译包没关系。直接指定根目录更干脆:
cmake_minimum_required(VERSION 3.15) project(gdal_smoke CXX) # 用 cache 变量,命令行可以覆盖:cmake -DGDAL_ROOT=E:/sdk/gdal .. set(GDAL_ROOT "D:/sdk/gdal-vs2017" CACHE PATH "prebuilt gdal root") add_executable(gdal_smoke main.cpp) target_include_directories(gdal_smoke PRIVATE ${GDAL_ROOT}/include) target_link_directories(gdal_smoke PRIVATE ${GDAL_ROOT}/lib) target_link_libraries(gdal_smoke PRIVATE gdal_i) # 编完自动把 dll 拷到 exe 旁边,省掉手动复制这一步 add_custom_command(TARGET gdal_smoke POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GDAL_ROOT}/bin/gdal.dll $<TARGET_FILE_DIR:gdal_smoke>)target_link_directories要求 CMake 3.13 以上。POST_BUILD那段只拷贝了主 dll,实际项目里用file(GLOB ...)把bin下所有 dll 列出来循环拷更省事;如果不做这一步,每次改完代码 F5 都提示找不到 dll,半天时间就这么耗没了。
4. 中文路径与坐标转换:两个最容易翻车的日常场景
4.1 中文路径:别信“设成 NO 就能解决”
国内项目里十个文件路径有八个带中文。GDAL 处理路径的规则是:GDAL_FILENAME_IS_UTF8配置项为YES时,它认为传进来的char*是 UTF-8,内部在 Windows 上转成宽字符走_wfopen;为NO时按本地编码(GBK)处理。关键是——这个选项在新版 GDAL 里默认就是YES。网上大量老帖子教人设成NO,照着做,路径里带非 ASCII 字符时直接打不开,而且错误信息还很模糊。
正确做法只有一条:源文件统一用 UTF-8 保存,路径字符串是 UTF-8,保持这个选项为默认的YES。如果路径来自 Windows API 或某个只在 Windows 下工作的 UI 框架,拿到的是wchar_t*,那就先转成 UTF-8:
#include <windows.h> #include <string> // 宽字符 -> UTF-8,GDAL 的 char* 接口期望的就是这个编码 std::string WideToUtf8(const std::wstring& w) { if (w.empty()) return std::string(); int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), nullptr, 0, nullptr, nullptr); std::string s(static_cast<size_t>(n), '\0'); WideCharToMultiByte(CP_UTF8, 0, w.c_str(), (int)w.size(), &s[0], n, nullptr, nullptr); return s; } GDALDataset* OpenRaster(const std::wstring& widePath) { // 保持默认的 UTF-8 语义,不要设成 NO CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"); std::string utf8Path = WideToUtf8(widePath); return static_cast<GDALDataset*>( GDALOpenEx(utf8Path.c_str(), GDAL_OF_RASTER | GDAL_OF_READONLY, nullptr, nullptr, nullptr)); }再补一个容易被忽略的点:VS 工程属性「C/C++ → 命令行」加/utf-8,等价于同时加/source-charset:utf-8和/execution-charset:utf-8。不加这个开关,源文件里的中文字面量在 GBK 代码页的机器上会被编成 GBK 字节流,传给 GDAL 就变成乱码路径。这个坑我在两个项目上踩过,症状都是“英文路径能开,中文路径返回 nullptr”。
4.2 坐标转换:轴序和 proj.db 是两个独立的问题
GDAL 3 之后OGRSpatialReference引入了轴序策略,这是最容易翻车的地方。EPSG:4326 的权威轴序是纬度在前、经度在后,而绝大多数业务代码习惯传(经度, 纬度)。不设OAMS_TRADITIONAL_GIS_ORDER,转换出来的点会跑到地球另一边去,看着像坐标系选错了,实际只是 x/y 反了。
#include <ogr_spatialref.h> #include <ogr_geometry.h> // 经纬度转 3 度带高斯克吕格投影(EPSG:4547,中央经线 117E) bool LonLatToProjected(double lon, double lat, double& x, double& y) { OGRSpatialReference src; src.importFromEPSG(4326); // 关键一行:改回“经度在前”的传统顺序,否则 x/y 直接互换 src.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRSpatialReference dst; dst.importFromEPSG(4547); dst.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); OGRCoordinateTransformation* ct = OGRCreateCoordinateTransformation(&src, &dst); if (ct == nullptr) { // 这里返回 nullptr 有两种可能:EPSG 代码库里没有,或 proj.db 没找到 // 先查 PROJ_LIB 指向的目录下有没有 proj.db,再查代码 return false; } int ok = ct->Transform(1, &lon, &lat); OCTDestroyCoordinateTransformation(ct); // 必须手动释放 if (!ok) return false; x = lon; y = lat; return true; }OGRCreateCoordinateTransformation必须配OCTDestroyCoordinateTransformation,它不归 GDAL 的对象池管,忘了释放就是纯泄漏,循环里调用几次内存就上去了。另外importFromEPSG失败时返回的是OGRERR_UNSUPPORTED_SRS,很多人不检查返回值,后面一路空指针。判断到底是 EPSG 码不支持还是proj.db缺失,最快的办法是在命令行跑一次gdalinfo带投影的影像——如果它也报 SRS 解析失败,那 100% 是PROJ_LIB的问题。
4.3 驱动注册与按需裁剪
GDALAllRegister()会把包里所有驱动全注册一遍,几百个驱动,冷启动要几十毫秒。桌面端每次开影像都调一次,用户能感觉到卡。常见的两种裁剪方式:
一种是只注册必要的驱动,用各驱动自带的注册函数:
// 只要 GeoTIFF 和 Shapefile 时,就别全量注册了 GDALRegister_GTiff(); RegisterOGRShape();另一种是留着GDALAllRegister(),但用GDAL_SKIP排除掉肯定用不到的重驱动,注意这一步必须在注册之前:
// 排除掉 NetCDF/HDF 这类体积大、初始化慢的驱动 CPLSetConfigOption("GDAL_SKIP", "NetCDF HDF4 HDF5"); GDALAllRegister();还有一种情况:包里把部分驱动编成了插件(单独的 dll,不在主 dll 里)。这时候除了bin目录要给对,还要设GDAL_DRIVER_PATH指向插件目录,否则GDALAllRegister之后你会发现可用驱动列表比预期少一大截。判断方法就是跑gdalinfo --formats,跟官方完整版的格式列表比一比。
5. 排查清单:LNK2019、缺 proj.db 与运行期崩溃的五种现场
5.1 链接期:找不到符号,或者运行库不匹配
现象:编译通过,链接报LNK2019: 无法解析的外部符号 __imp_GDALOpenEx,被引用的位置在main.obj。
原因:三种可能。一是附加依赖项里没写gdal_i.lib;二是库目录指错了,链接器在别的目录里找到同名但不同架构的 lib;三是 C/C++ 里加了extern "C"缺失——GDAL 的头文件自己带了extern "C"保护,除非你手工前向声明了函数原型。
解决:先在「链接器 → 命令行」里看最终展开的命令行,确认gdal_i.lib的完整路径在不在;再dumpbin /exports gdal.dll | findstr GDALOpenEx确认符号确实导出;最后检查自己有没有绕过头文件手写声明。
现象:链接报LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MD_DynamicRelease”不匹配值“MT_StaticRelease”。
原因:工程运行库选项和 GDAL 包的编译选项不一致,obj 里带着运行库标记,链接器一比对就报。
解决:按 2.1 的dumpbin /directives结果,把工程「代码生成 → 运行库」改成一致的值。注意 Debug 和 Release 是两套配置,很多工程只改了 Release,Debug 一编就炸。
5.2 运行期第一类:坐标转换返回空
现象:程序跑起来,影像能打开、像素能读,一到坐标转换就返回nullptr或者转换结果全是0。
原因:PROJ_LIB没设,或者设的目录里没有proj.db。GDAL 3 起坐标转换全部走 PROJ,proj.db是硬依赖,缺了不会报错,只是静默失败。
解决:在代码里把PROJ_LIB打出来,用资源管理器确认该目录下确实有proj.db文件(不是一个同名目录)。发布时把整个data目录随程序一起打进去,别只拷 dll。
5.3 运行期第二类:dll 版本串了
现象:开发机上跑得好好的,拷到测试机就闪退,事件查看器里是0xc0000135或者0xc000007b。
原因:0xc0000135是缺 dll;0xc000007b通常是加载了位数不对的 dll。还有一种更隐蔽的:目标机器上装了别的软件,PATH 里有同名但版本不同的proj_8.dll,加载顺序在你控制之外。
解决:把bin下所有 dll 拷到 exe 同级目录。Windows 的 dll 搜索顺序里,exe 所在目录优先于 PATH,这样能彻底绕开版本竞争。做完之后用dumpbin /dependents your.exe核对一遍清单。
5.4 运行期第三类:Debug 能跑,Release 崩
现象:Debug 配置下一切正常,切 Release 之后,打开影像那一步直接崩,或者GDALClose时崩在堆上。
原因:一般还是运行库或_ITERATOR_DEBUG_LEVEL不一致,只是 Debug/Release 各有一套配置,改了一个忘了另一个。另一种可能是代码里用new创建的GDALDataset用delete释放,跨模块释放堆内存,Release 下 CRT 不是同一份,直接崩。
解决:两套配置的运行库都对齐;所有 GDAL 对象统一用GDALClose/CPLFree/OCTDestroyCoordinateTransformation释放,绝不混用delete和free。
5.5 中文路径与只读目录
现象:路径里带中文,GDALOpenEx返回空,CPLGetLastErrorMsg()只说文件不存在。
原因:GDAL_FILENAME_IS_UTF8被设成NO,或者源文件编码没统一,字面量被编成了 GBK 字节流。
解决:保持该选项为YES,源文件统一 UTF-8 并加/utf-8编译开关,路径从 Windows API 拿到时先转 UTF-8。另外注意程序装在Program Files下时,GDAL 想在同目录写.aux.xml会被拒绝,表现为“能读不能写”,把工作目录或GDAL_PAM_PROXY_DIR指到可写位置即可。
6. 封装与自检:换包前后各跑一遍的两张表
GDAL 的 C 风格资源管理在业务代码里散着写迟早出事,我一般先包一层 RAII 再说:
#include <memory> #include <gdal_priv.h> // 自定义删除器,把 GDALClose 绑到智能指针上 struct DatasetDeleter { void operator()(GDALDataset* d) const { if (d) GDALClose(d); } }; using DatasetPtr = std::unique_ptr<GDALDataset, DatasetDeleter>; // 用法:DatasetPtr ds(static_cast<GDALDataset*>(GDALOpenEx(...))); // 任何返回路径上都不用再关心什么时候关,异常安全也保住了这层封装的价值不在代码量,而在于消除“哪个分支忘了关”的可能性。同一套思路可以推广到OGRCoordinateTransformation(删除器调OCTDestroyCoordinateTransformation)和GDALDriver之外的所有OGR*对象。
换包、升级 VS、换机器之后,我固定跑一遍这张表,五分钟能省下半天:
| 检查项 | 动作 | 通过标准 |
|---|---|---|
| 架构 | dumpbin /headers gdal_i.lib | machine 与工程平台一致 |
| 运行库 | dumpbin /directives gdal_i.lib | 只有一类 MSVCRT 或 LIBCMT |
| 依赖完整性 | dumpbin /dependents gdal.dll | 列出的 dll 都在 bin 里 |
| 数据目录 | 打开带投影的影像做一次转换 | 不报 SRS 解析失败 |
| 符号 | dumpbin /exports gdal.dll | 用到的函数全部存在 |
| 中文路径 | 用一个中文名文件跑一遍 | 正常打开且能读像素 |
血泪经验是:不要等业务代码写完了才验证包。我现在的习惯是新建工程第一件事就是把 3.2 那段最小代码贴进去跑通,跑不通就不往下写。因为一旦业务代码堆上去,同一个 LNK2019 你分不清是自己写错还是包不对,排查成本翻好几倍。这份编译好的包在取的时候顺手看一眼目录名里的架构和运行库标记,再对着第 5 章的五条现场过一遍,基本就能一次接上。希望帮到你。
本文还有配套的精品资源,点击获取