GDAL 3.x 自定义驱动开发
本章介绍如何为 GDAL 3.x 开发自定义的栅格和矢量驱动。驱动是 GDAL 的核心扩展机制,每种支持的数据格式都有对应的驱动实现。开发自定义驱动需要理解 GDAL 的类继承体系、回调函数注册机制以及元数据声明方式。本章内容适用于 GDAL 3.x 系列,并涵盖了 3.7 至 3.13 版本中的重要 API 变更。
栅格驱动架构
GDAL 栅格驱动的核心思路是:子类化 GDALDataset 和 GDALRasterBand ,实现一组虚函数或回调,然后将驱动注册到 GDALDriverManager 。下面分别说明各个组成部分。
子类化 GDALDataset 和 GDALRasterBand
一个最简的只读栅格驱动需要两个类:
数据集类,继承自
GDALDataset(或GDALPamDataset以获得持久化元数据支持),负责管理文件句柄、元数据和波段集合。波段类,继承自
GDALRasterBand(或GDALPamRasterBand),负责读取实际的像素数据。
#include "gdal_pam.h"
class MyRasterBand;
// 数据集类
class MyDataset final : public GDALPamDataset
{
friend class MyRasterBand;
VSILFILE *m_fp = nullptr;
public:
MyDataset();
~MyDataset() override;
// 静态回调,用于驱动注册
static GDALDataset *Open(GDALOpenInfo *);
static int Identify(GDALOpenInfo *);
};
// 波段类
class MyRasterBand final : public GDALPamRasterBand
{
public:
MyRasterBand(MyDataset *poDS, int nBand);
~MyRasterBand() override;
// 必须实现:按块读取像素数据
CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override;
};
Identify() 和 Open() 回调
Identify() 是一个轻量级的文件格式识别函数,仅检查文件头的前几个字节,不执行任何 I/O 操作。它应该尽可能快,用于在驱动枚举阶段快速过滤不匹配的文件。返回值有三种:
TRUE(1):确定是该格式FALSE(0):确定不是该格式-1:不确定,需要调用Open()进一步判断
Open() 负责完整地打开文件、验证格式、创建数据集对象并设置波段。如果文件无法打开或格式不匹配,应返回 nullptr 。
// 轻量级格式识别,仅检查文件头
int MyDataset::Identify(GDALOpenInfo *poOpenInfo)
{
if (poOpenInfo->nHeaderBytes < 16)
return FALSE;
// 检查文件头的魔数(magic number)
if (memcmp(poOpenInfo->pabyHeader, "MYFMT", 5) != 0)
return FALSE;
return TRUE;
}
// 完整打开文件并构造数据集
GDALDataset *MyDataset::Open(GDALOpenInfo *poOpenInfo)
{
if (!Identify(poOpenInfo))
return nullptr;
// 不支持更新模式
if (poOpenInfo->eAccess == GA_Update)
{
ReportUpdateNotSupportedByDriver("MyFormat");
return nullptr;
}
// 检查文件指针
if (poOpenInfo->fpL == nullptr)
return nullptr;
auto poDS = std::make_unique<MyDataset>();
// 从 GDALOpenInfo 借用文件指针
std::swap(poDS->m_fp, poOpenInfo->fpL);
// 从文件头中读取尺寸信息
const GByte *pabyHeader = poOpenInfo->pabyHeader;
poDS->nRasterXSize = pabyHeader[8] | (pabyHeader[9] << 8);
poDS->nRasterYSize = pabyHeader[10] | (pabyHeader[11] << 8);
if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
return nullptr;
// 创建波段
poDS->SetBand(1, new MyRasterBand(poDS.get(), 1));
// 设置描述和加载 PAM 元数据
poDS->SetDescription(poOpenInfo->pszFilename);
poDS->TryLoadXML();
// 初始化概览管理器
poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
return poDS.release();
}
ReadBlock() 和 RasterIO()
IReadBlock() 是波段类的核心方法,负责读取一个数据块(block)的像素数据。GDAL 默认使用 256x256 的块大小,但可以通过设置 nBlockXSize 和 nBlockYSize 来自定义。对于逐行存储的格式,通常将 nBlockYSize 设为 1。
MyRasterBand::MyRasterBand(MyDataset *poDSIn, int nBandIn)
{
poDS = poDSIn;
nBand = nBandIn;
eDataType = GDT_Byte; // 像素数据类型
nBlockXSize = poDS->GetRasterXSize(); // 整行作为一个块
nBlockYSize = 1;
}
CPLErr MyRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
void *pImage)
{
MyDataset *poGDS = cpl::down_cast<MyDataset *>(poDS);
// 计算文件偏移量并读取一行数据
const vsi_l_offset nOffset = HEADER_SIZE +
static_cast<vsi_l_offset>(nBlockYOff) * nBlockXSize;
VSIFSeekL(poGDS->m_fp, nOffset, SEEK_SET);
if (VSIFReadL(pImage, 1, nBlockXSize, poGDS->m_fp) != nBlockXSize)
{
CPLError(CE_Failure, CPLE_AppDefined, "Cannot read scanline %d",
nBlockYOff);
return CE_Failure;
}
return CE_None;
}
如果需要更高效的随机访问或支持自定义重采样,可以重写 IRasterIO() 方法。该方法在 GDALDataset 和 GDALRasterBand 层都有对应版本,允许驱动直接处理任意矩形区域的读写请求,而不必经过分块机制。
驱动元数据项
驱动注册时需要声明一组元数据项,用于告知 GDAL 框架该驱动的能力和特征。常用的元数据项包括:
元数据常量 |
说明 |
|---|---|
|
声明为栅格驱动,值为 |
|
声明为矢量驱动,值为 |
|
驱动的完整名称,如 |
|
默认文件扩展名,如 |
|
多个扩展名,以空格分隔,如 |
|
是否支持 VSI 虚拟文件系统 |
|
文档页面路径 |
|
声明驱动支持 |
|
声明驱动支持 |
|
声明驱动支持 |
代码示例:最简只读栅格驱动
以下是一个完整的只读栅格驱动实现,参考了 GDAL 内置的 JDEM 驱动模式。该驱动读取一个假想的简单二进制格式,文件头包含尺寸信息,后续为逐行存储的像素数据。
#include "cpl_port.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "gdal_driver.h"
#include "gdal_drivermanager.h"
#include "gdal_openinfo.h"
constexpr int HEADER_SIZE = 16;
/************************************************************************/
/* MyRasterBand */
/************************************************************************/
class MyDataset;
class MyRasterBand final : public GDALPamRasterBand
{
friend class MyDataset;
public:
MyRasterBand(MyDataset *poDS, int nBand);
CPLErr IReadBlock(int, int, void *) override;
};
MyRasterBand::MyRasterBand(MyDataset *poDSIn, int nBandIn)
{
poDS = poDSIn;
nBand = nBandIn;
eDataType = GDT_Byte;
nBlockXSize = poDS->GetRasterXSize();
nBlockYSize = 1;
}
CPLErr MyRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff,
void *pImage)
{
MyDataset *poGDS = cpl::down_cast<MyDataset *>(poDS);
const vsi_l_offset nOffset =
HEADER_SIZE + static_cast<vsi_l_offset>(nBlockYOff) * nBlockXSize;
VSIFSeekL(poGDS->m_fp, nOffset, SEEK_SET);
if (VSIFReadL(pImage, 1, nBlockXSize, poGDS->m_fp) != nBlockXSize)
{
CPLError(CE_Failure, CPLE_AppDefined, "Cannot read scanline %d",
nBlockYOff);
return CE_Failure;
}
return CE_None;
}
/************************************************************************/
/* MyDataset */
/************************************************************************/
class MyDataset final : public GDALPamDataset
{
friend class MyRasterBand;
VSILFILE *m_fp = nullptr;
public:
MyDataset();
~MyDataset() override;
static GDALDataset *Open(GDALOpenInfo *);
static int Identify(GDALOpenInfo *);
};
MyDataset::MyDataset() = default;
MyDataset::~MyDataset()
{
FlushCache(true);
if (m_fp != nullptr)
CPL_IGNORE_RET_VAL(VSIFCloseL(m_fp));
}
int MyDataset::Identify(GDALOpenInfo *poOpenInfo)
{
if (poOpenInfo->nHeaderBytes < HEADER_SIZE)
return FALSE;
if (memcmp(poOpenInfo->pabyHeader, "MYFMT", 5) != 0)
return FALSE;
return TRUE;
}
GDALDataset *MyDataset::Open(GDALOpenInfo *poOpenInfo)
{
if (!Identify(poOpenInfo))
return nullptr;
if (poOpenInfo->eAccess == GA_Update)
{
ReportUpdateNotSupportedByDriver("MyFormat");
return nullptr;
}
if (poOpenInfo->fpL == nullptr)
return nullptr;
auto poDS = std::make_unique<MyDataset>();
std::swap(poDS->m_fp, poOpenInfo->fpL);
const GByte *pabyHeader = poOpenInfo->pabyHeader;
poDS->nRasterXSize = pabyHeader[8] | (pabyHeader[9] << 8);
poDS->nRasterYSize = pabyHeader[10] | (pabyHeader[11] << 8);
if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
return nullptr;
poDS->SetBand(1, new MyRasterBand(poDS.get(), 1));
poDS->SetDescription(poOpenInfo->pszFilename);
poDS->TryLoadXML();
poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
return poDS.release();
}
/************************************************************************/
/* GDALRegister_MyFormat() */
/************************************************************************/
void GDALRegister_MyFormat()
{
if (GDALGetDriverByName("MyFormat") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("MyFormat");
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
"My Custom Format (.myf)");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "myf");
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
poDriver->pfnOpen = MyDataset::Open;
poDriver->pfnIdentify = MyDataset::Identify;
GetGDALDriverManager()->RegisterDriver(poDriver);
}
驱动注册函数
每个驱动必须提供一个 GDALRegister_<DriverName>() 函数,该函数完成以下工作:
检查驱动是否已注册,避免重复注册
创建
GDALDriver对象设置驱动描述(短名称)
设置元数据项(能力声明、文件扩展名等)
赋值回调函数指针(
pfnOpen、pfnIdentify、pfnCreate等)调用
GetGDALDriverManager()->RegisterDriver()完成注册
void GDALRegister_MyFormat()
{
// 避免重复注册
if (GDALGetDriverByName("MyFormat") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
// 驱动短名称
poDriver->SetDescription("MyFormat");
// 能力声明
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
// 驱动元信息
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
"My Custom Format (.myf)");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "myf");
poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
"drivers/raster/myformat.html");
// VSI 支持(见下文)
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
// 回调函数
poDriver->pfnOpen = MyDataset::Open;
poDriver->pfnIdentify = MyDataset::Identify;
// 如果支持创建,还需设置:
// poDriver->pfnCreate = MyDataset::Create;
// poDriver->pfnCreateCopy = MyDataset::CreateCopy;
// 注册到驱动管理器
GetGDALDriverManager()->RegisterDriver(poDriver);
}
对于支持写入的驱动,还需要实现 Create() 或 CreateCopy() 回调,并在注册时设置 GDAL_DCAP_CREATE 或 GDAL_DCAP_CREATECOPY 元数据项。Create() 从零创建一个空的数据集,CreateCopy() 则基于一个已有数据集进行复制转换。
矢量驱动架构
矢量驱动的架构与栅格驱动类似,但核心类是 OGRLayer 而非 GDALRasterBand 。一个矢量驱动通常需要:
数据集类,继承
GDALDataset,管理多个图层图层类,继承
OGRLayer,实现要素的读写和查询
子类化 GDALDataset 和 OGRLayer
OGRLayer 是 GDAL/OGR 矢量数据的核心抽象,每个图层代表一组同类型的要素。子类需要实现以下纯虚函数或重要虚函数:
虚函数 |
说明 |
|---|---|
|
遍历要素的核心方法,返回下一个 |
|
向图层写入一个要素 |
|
设置空间过滤条件 |
|
返回图层的要素类定义( |
|
查询图层支持的能力(如读、写、随机读取等) |
|
重置要素遍历到起始位置 |
|
返回图层的空间参考系统 |
#include "ogrsf_frmts.h"
class MyVectorLayer final : public OGRLayer
{
OGRFeatureDefn *m_poFeatureDefn;
OGRSpatialReference m_oSRS;
int m_nNextFID;
// ... 内部数据源状态
public:
MyVectorLayer(const char *pszName,
OGRSpatialReference *poSRS,
OGRwkbGeometryType eGeomType);
~MyVectorLayer() override;
// 要素遍历
void ResetReading() override;
OGRFeature *GetNextFeature() override;
// 要素写入
OGRErr ICreateFeature(OGRFeature *poFeature) override;
// 空间过滤
OGRErr ISetSpatialFilter(int iGeomField,
const OGRGeometry *poGeom) override;
// 图层定义
const OGRFeatureDefn *GetLayerDefn() override;
// 能力查询
int TestCapability(const char *) override;
};
GetNextFeature() 是最核心的方法。实现时需要注意:
要素的 FID 必须在图层内唯一
返回的
OGRFeature的所有权归调用者,调用者负责释放当没有更多要素时返回
nullptr需要尊重当前的空间过滤条件
ICreateFeature() (注意是 I 前缀的内部版本,公开的 CreateFeature() 会进行参数校验后调用它)负责将要素持久化到数据源中。
代码示例:矢量驱动注册
矢量驱动的注册函数与栅格驱动类似,但元数据项使用 GDAL_DCAP_VECTOR 而非 GDAL_DCAP_RASTER 。
/************************************************************************/
/* MyVectorDataset */
/************************************************************************/
class MyVectorDataset final : public GDALDataset
{
std::vector<std::unique_ptr<MyVectorLayer>> m_apoLayers;
public:
int GetLayerCount() override;
OGRLayer *GetLayer(int iLayer) override;
static GDALDataset *Open(GDALOpenInfo *);
static int Identify(GDALOpenInfo *);
// 如果支持创建图层
OGRLayer *ICreateLayer(const char *pszName,
OGRSpatialReference *poSpatialRef,
OGRwkbGeometryType eGType,
CSLConstList papszOptions) override;
};
/************************************************************************/
/* RegisterOGRMyFormat() */
/************************************************************************/
void RegisterOGRMyFormat()
{
if (GDALGetDriverByName("MyVectorFormat") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("MyVectorFormat");
poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
"My Vector Format (.mvf)");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mvf");
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
poDriver->pfnOpen = MyVectorDataset::Open;
poDriver->pfnIdentify = MyVectorDataset::Identify;
GetGDALDriverManager()->RegisterDriver(poDriver);
}
矢量驱动常用的额外元数据项:
GDAL_DCAP_CREATE_LAYER:声明支持创建新图层GDAL_DCAP_DELETE_LAYER:声明支持删除图层GDAL_DCAP_CREATE_FIELD:声明支持添加字段GDAL_DCAP_DELETE_FIELD:声明支持删除字段GDAL_DCAP_CURVE_GEOMETRIES:声明支持曲线几何GDAL_DCAP_Z_GEOMETRIES:声明支持三维几何
延迟插件加载(RFC 96)
GDAL 3.9 引入了延迟插件加载机制( RFC 96 ),允许将插件驱动的实际加载推迟到真正需要时。这对于减少 GDALAllRegister() 的启动时间非常有效,特别是当系统安装了大量插件驱动时。
背景与动机
在 GDAL 3.9 之前,插件驱动在 GDALAllRegister() 时通过 dlopen() 全部加载。当系统安装了 100 多个插件时,仅 dlopen() 调用就需要约 300 毫秒,对短生命周期的进程(如批量运行 gdalinfo )影响显著。
延迟加载的核心思想是:在 GDALAllRegister() 时只注册一个轻量的代理驱动( GDALPluginDriverProxy ),该代理携带足够的元数据(如扩展名、 Identify() 回调)来判断是否需要加载实际驱动。只有当用户真正打开该格式的文件时,才加载实际的共享库。
代理驱动机制
代理驱动 GDALPluginDriverProxy 继承自 GDALDriver ,通过 GDALDriverManager::DeclareDeferredPluginDriver() 注册。代理驱动需要设置以下信息:
驱动名称和描述
格式能力声明(
GDAL_DCAP_RASTER、GDAL_DCAP_VECTOR等)文件扩展名
pfnIdentify回调(必须编译在 libgdal 核心库中,不依赖外部库)对应的
GDAL_DCAP_OPEN、GDAL_DCAP_CREATE等声明
// 驱动核心能力的元数据设置函数
// 在代理驱动和真实驱动中共享
void FOODriverSetCommonMetadata(GDALDriver *poDriver)
{
poDriver->SetDescription("FOO");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "The FOO format");
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "foo");
poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES");
poDriver->pfnIdentify = FOODatasetIdentify;
}
// 延迟插件声明(编译在 libgdal 核心库中)
#ifdef PLUGIN_FILENAME
void DeclareDeferredFOOPlugin()
{
if (GDALGetDriverByName("FOO") != nullptr)
return;
auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME);
FOODriverSetCommonMetadata(poDriver);
GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver);
}
#endif
真实驱动的注册函数仍然存在,但只在插件被实际加载后才执行:
// 真实驱动注册(在插件共享库中)
void GDALRegister_FOO()
{
if (GDALGetDriverByName("FOO") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
FOODriverSetCommonMetadata(poDriver);
// 设置实际的回调函数
poDriver->pfnOpen = FOODataset::Open;
poDriver->pfnCreate = FOODataset::Create;
GetGDALDriverManager()->RegisterDriver(poDriver);
}
gdalallregister.cpp 中的注册顺序:
void GDALAllRegister()
{
auto poDriverManager = GetGDALDriverManager();
// 延迟驱动声明必须在 AutoLoadDrivers() 之前
#if defined(DEFERRED_FOO_DRIVER)
DeclareDeferredFOOPlugin();
#endif
// AutoLoadDrivers() 不会加载已被延迟声明的插件
poDriverManager->AutoLoadDrivers();
// 内置驱动的正常注册
#if FRMT_foo
GDALRegisterFOO();
#endif
}
Out-of-tree 驱动的延迟加载
外部驱动也可以利用延迟加载机制,前提是 libgdal 编译时通过 CMake 变量 ADD_EXTERNAL_DEFERRED_PLUGIN_<driver_name> 指向包含代理声明代码的外部源文件:
set(ADD_EXTERNAL_DEFERRED_PLUGIN_FOO
FILEPATH /path/to/foo_deferred_plugin.cpp)
该源文件必须导出一个 C 链接的 DeclareDeferredFOO() 函数,负责创建 GDALPluginDriverProxy 并调用 DeclareDeferredPluginDriver() 。
CSLConstList 参数变更(3.13)
GDAL 3.13 将大量 API 中的 char ** 参数类型替换为 CSLConstList 。CSLConstList 本质上是 const char *const * 的类型别名,表示一个只读的字符串列表。这一变更提升了类型安全性,明确了参数的只读语义。
受影响的 API
以下接口的签名在 GDAL 3.13 中发生了变更:
GDALDriver::pfnCreate回调:最后一个参数从char **改为CSLConstListGDALDriver::pfnCreateCopy回调:第三个参数从char **改为CSLConstListGDALDataset::AddBand():参数从char **改为CSLConstListGDALDataset::AdviseRead():参数从char **改为CSLConstListGDALDataset::BeginAsyncReader():参数从char **改为CSLConstListGDALDataset::CopyLayer():参数从char **改为CSLConstListGDALRasterBand::AdviseRead():参数从char **改为CSLConstListGDALMajorObject::SetMetadata():参数从char **改为CSLConstListGDALMajorObject::GetMetadata():返回值从char **改为CSLConstList
对 Out-of-tree 驱动的影响
如果你维护一个外部驱动,需要注意以下事项:
Create()和CreateCopy()的函数签名必须更新。旧签名:// GDAL 3.12 及更早 static GDALDataset *Create(const char *pszName, int nXSize, int nYSize, int nBands, GDALDataType eType, char **papszOptions);
新签名:
// GDAL 3.13+ static GDALDataset *Create(const char *pszName, int nXSize, int nYSize, int nBands, GDALDataType eType, CSLConstList papszOptions);
GetMetadata()返回CSLConstList后,如果你之前将返回值存储为char **,需要改为CSLConstList。此变更与旧版 GDAL 兼容,因为char **可以隐式转换为CSLConstList。SetMetadata()接受CSLConstList后,调用方无需修改代码,但驱动实现者应注意参数不可被修改。
GDALDataset::Close() 签名变化(3.13)
GDALDataset::Close() 方法最初在 GDAL 3.7 中引入( RFC 91 ),用于在数据集销毁前提供一个可以返回错误码的清理机会。在 GDAL 3.13 中,其签名增加了进度回调参数:
// GDAL 3.7 - 3.12
virtual CPLErr Close();
// GDAL 3.13+
virtual CPLErr Close(GDALProgressFunc pfnProgress = nullptr,
void *pProgressData = nullptr);
实现 Close() 的最佳实践
驱动实现 Close() 时应遵循以下模式:
MyDataset::~MyDataset()
{
try
{
MyDataset::Close();
}
catch (const std::exception &exc)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Exception thrown in MyDataset::Close(): %s",
exc.what());
}
catch (...)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Exception thrown in MyDataset::Close()");
}
}
CPLErr MyDataset::Close()
{
CPLErr eErr = CE_None;
// 防止重复关闭
if (nOpenFlags != OPEN_FLAGS_CLOSED)
{
// 刷新缓存
if (MyDataset::FlushCache(true) != CE_None)
eErr = CE_Failure;
// 驱动特定的清理操作
if (m_fpImage != nullptr)
{
if (VSIFCloseL(m_fpImage) != 0)
{
CPLError(CE_Failure, CPLE_FileIO, "VSIFCloseL() failed");
eErr = CE_Failure;
}
m_fpImage = nullptr;
}
// 调用父类的 Close()
if (GDALDataset::Close() != CE_None)
eErr = CE_Failure;
}
return eErr;
}
关键要点:
Close()必须可以被多次调用,后续调用应直接返回CE_None如果驱动实现了
Close(),析构函数中必须调用它Close()调用后,除Close()和析构函数外不应再调用其他方法优先使用
Close()而非析构函数进行资源清理,因为Close()可以返回错误码
VSI 虚拟文件系统支持声明
GDAL_DCAP_VIRTUALIO 元数据项用于声明驱动是否支持 GDAL 的 VSI 虚拟文件系统( /vsi*/ 路径前缀)。支持 VSI 意味着驱动可以通过 VSIFOpenL() 、 VSIFReadL() 等 API 读写文件,从而支持以下场景:
从压缩文件中读取数据(
/vsizip/、/vsitar/)从网络 URL 读取数据(
/vsicurl/、/vsis3/)从内存缓冲区读取数据(
/vsimem/)
要使驱动支持 VSI,需要:
使用
VSILFILE *而非FILE *作为文件句柄类型使用
VSIFOpenL()、VSIFReadL()、VSIFSeekL()等 VSI 系列函数在注册时声明
GDAL_DCAP_VIRTUALIO
// 注册时声明 VSI 支持
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
// 在 Open() 中使用 VSI 函数打开文件
VSILFILE *fp = VSIFOpenL(poOpenInfo->pszFilename, "rb");
if (fp == nullptr)
return nullptr;
// 使用 VSI 函数读取数据
GByte abyHeader[16];
if (VSIFReadL(abyHeader, 1, 16, fp) != 16)
{
VSIFCloseL(fp);
return nullptr;
}
Warning
GDAL 3.13 中, VSILFILE * 类型不再与 FILE * 别名(即使在非 DEBUG 构建中)。所有使用 VSI API 的外部驱动必须确保文件句柄类型为 VSILFILE * 。