GDALDestroyDriverManager 分析

本文探讨了GDAL库中的GDALDestroyDriverManager函数,该函数用于在main函数退出前销毁GDALDriverManager singleton对象。文章指出在其他线程仍使用GDAL对象时不应调用此函数,并对是否应由driver的析构函数负责清理工作提出了疑问。作者通过查看源代码来理解GDAL内部的工作原理,特别提到了GeoTiff驱动的析构过程,对于C函数指针代替虚函数的设计提出了一定的质疑。

有生就有死,既然有了singleton对象GDALDriverManger,就要在main函数退出之前销毁它。

前文GDAL singleton的经典错误 已经分析了这是DCLP的模式,最后由一个指针poDM指向被创建出来的唯一的对象。因为是new出来的,自然要找到delete的地方。同样在gdaldrivermanager.cpp文件,在最后的位置,提供了函数GDALDestroyDriverManager用来完成最后的清理工作。 注释提醒,不要在其他线程还在使用GDAL内部的对象时调用该函数。我只会在退出前调用。

/**
 * \brief Destroy the driver manager.
 *
 * Incidently unloads all managed drivers.
 *
 * NOTE: This function is not thread safe.  It should not be called while
 * other threads are actively using GDAL. 
 */

void CPL_STDCALL GDALDestroyDriverManager( void )

{
    // THREADSAFETY: We would like to lock the mutex here, but it 
    // needs to be reacquired within the destructor during driver
    // deregistration.
    if( poDM != NULL )
        delete poDM;
}

poDM也定义在这个文件开头,是一个静态指针。

static volatile GDALDriverManager        *poDM = NULL;


delete poDM会引发GDALDriverManager的析构函数被执行,看一下析构函数代码,的确做了不少工作。所以那种GDALDestroyDriverManager函数不需要调用的观点是错误的。

/************************************************************************/
/*                         ~GDALDriverManager()                         */
/*                                                                      */
/*      Eventually this should also likely clean up all open            */
/*      datasets.  Or perhaps the drivers that own them should do       */
/*      that in their destructor?                                       */
/************************************************************************/

GDALDriverManager::~GDALDriverManager()

{
/* -------------------------------------------------------------------- */
/*      Destroy the existing drivers.                                   */
/* -------------------------------------------------------------------- */
    while( GetDriverCount() > 0 )
    {
        GDALDriver      *poDriver = GetDriver(0);

        DeregisterDriver(poDriver);
        delete poDriver;
    }

    m_NameDriverMap.clear();

/* -------------------------------------------------------------------- */
/*      Cleanup local memory.                                           */
/* -------------------------------------------------------------------- */
    VSIFree( papoDrivers );
    VSIFree( pszHome );

/* -------------------------------------------------------------------- */
/*      Cleanup any Proxy related memory.                               */
/* -------------------------------------------------------------------- */
    PamCleanProxyDB();

/* -------------------------------------------------------------------- */
/*      Blow away all the finder hints paths.  We really shouldn't      */
/*      be doing all of them, but it is currently hard to keep track    */
/*      of those that actually belong to us.                            */
/* -------------------------------------------------------------------- */
    CPLFinderClean();
    CPLFreeConfig();

/* -------------------------------------------------------------------- */
/*      Cleanup any memory allocated by the OGRSpatialReference         */
/*      related subsystem.                                              */
/* -------------------------------------------------------------------- */
    OSRCleanup();

/* -------------------------------------------------------------------- */
/*      Cleanup VSIFileManager.                                         */
/* -------------------------------------------------------------------- */
    VSICleanupFileManager();

/* -------------------------------------------------------------------- */
/*      Cleanup thread local storage ... I hope the program is all      */
/*      done with GDAL/OGR!                                             */
/* -------------------------------------------------------------------- */
    CPLCleanupTLS();

/* -------------------------------------------------------------------- */
/*      Ensure the global driver manager pointer is NULLed out.         */
/* -------------------------------------------------------------------- */
    if( poDM == this )
        poDM = NULL;
}

这个析构函数的注释很奇怪,居然提问,是不是应该让driver的析构函数负责清理工作。当然应该这样。这也带来个疑问,这些driver的析构函数还是有必要看一下。

如果看官方API Reference文档,只能看到公有成员的描述,所以如果想把握GDAL内部的运行原理,只有看代码是唯一的有效方式。

http://www.gdal.org/classGDALDriverManager.html


delete poDriver会导致GDALDriver析构函数被调用,

GDALDriver::~GDALDriver()

{
    if( pfnUnloadDriver != NULL )
        pfnUnloadDriver( this );
}
这个GDALDriver没有采用常规的C++多态方式,即不同的driver用不同子类来实现,而是用了函数指针的方式。典型C风格!

如果是GeoTiff驱动时,会调用到geotiff.cpp文件的函数:

/************************************************************************/
/*                        GDALDeregister_GTiff()                        */
/************************************************************************/

void GDALDeregister_GTiff( GDALDriver * )

{
    CPLDebug( "GDAL", "GDALDeregister_GTiff() called." );
    CSVDeaccess( NULL );

#if defined(LIBGEOTIFF_VERSION) && LIBGEOTIFF_VERSION > 1150
    GTIFDeaccessCSV();
#endif
}

void GTIFDeaccessCSV()

{
    CSVDeaccess( NULL );
}

结果追踪下来,driver没有什么有效代码做清理。除了GDALDriver的析构函数做了默认的清理以外。

获许因为没什么东西真的不需要清理,或许是bug。暂时还说不准,我只是对这种用C函数指针代替虚函数的设计究竟能带来多少好处,表示怀疑。


我正在编辑【c++】代码,遇到了 【0x00007FFFD5C115E1 (vcruntime140.dll) (test.exe 中)处有未经处理的异常: 0xC0000005: 写入位置 0x00000259FE6D4000 时发生访问冲突。 】 ,请帮我检查并改正错误点。我的原始代码如下: 【#include <iostream> #include <memory> #include <chrono> #include <fstream> #include <string> #include <iomanip> #include <opencv2/highgui.hpp> #include <opencv2/opencv.hpp> #include "gdal_priv.h" #include <gdal_alg_priv.h> #include <gdal.h> using namespace std; using namespace cv; //参考https://blog.csdn.net/ivan_ljf/article/details/9226463 //计算trans中图片xy点的经纬度信息 //adfGeoTransform的6个参数分别为左上角x坐标,水平分辨率,旋转参数,左上角y坐标,旋转参数,竖直分辨率,一般来说,旋转参数都为0 bool Projection2ImageRowCol(double* adfGeoTransform, double dProjX, double dProjY, int& iCol, int& iRow) { try { double dTemp = adfGeoTransform[1] * adfGeoTransform[5] - adfGeoTransform[2] * adfGeoTransform[4]; double dCol = 0.0, dRow = 0.0; dCol = (adfGeoTransform[5] * (dProjX - adfGeoTransform[0]) - adfGeoTransform[2] * (dProjY - adfGeoTransform[3])) / dTemp + 0.5; dRow = (adfGeoTransform[1] * (dProjY - adfGeoTransform[3]) - adfGeoTransform[4] * (dProjX - adfGeoTransform[0])) / dTemp + 0.5; iCol = int(dCol); iRow = int(dRow); return true; } catch (...) { return false; } } bool ImageRowCol2Projection(double* adfGeoTransform, int iCol, int iRow, double& dProjX, double& dProjY) { try { dProjX = adfGeoTransform[0] + adfGeoTransform[1] * iCol + adfGeoTransform[2] * iRow; dProjY = adfGeoTransform[3] + adfGeoTransform[4] * iCol + adfGeoTransform[5] * iRow; return true; } catch (...) { return false; } } int main() { GDALAllRegister();//注册所有的驱动 CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); //设置支持中文路径和文件名 //1、加载tif数据 string file_path_name = "D:\\Downloads\\上海区域九景影像\\ZY3_01a_mynbavp_880146_20120923_104301_0008_SASMAC_CHN_sec_rel_001_1209248618.tif"; //std::cout << "请输入图片路径:" << std::endl; //std::cin >> file_path_name; GDALDataset* poDataset = (GDALDataset*)GDALOpen(file_path_name.c_str(), GA_ReadOnly);//GA_Update和GA_ReadOnly两种模式 if (poDataset == NULL) { std::cout << "指定的文件不能打开!" << std::endl; return 0; } //获取图像的尺寸 int nImgSizeX = poDataset->GetRasterXSize(); int nImgSizeY = poDataset->GetRasterYSize(); std::cout << "ImageX = " << nImgSizeX << ", ImageY = " << nImgSizeY << std::endl; //获取图像的通道数(波段数量) int bandCount = poDataset->GetRasterCount(); std::cout << "bandCount = " << bandCount << std::endl; //获取图像波段 在GDAL中波段数的起始数是1,而非0 GDALRasterBand* poBand1 = poDataset->GetRasterBand(1); //GDAL中的数据类型 由此可知,每一个波段都可以有不同的数据类型 /* 一共包含以下12种数据类型 typedef enum { GDT_Unknown = 0, GDT_Byte = 1, GDT_UInt16 = 2, GDT_Int16 = 3, GDT_UInt32 = 4, GDT_Int32 = 5,GDT_UInt64,GDT_Int64 GDT_Float32 = 6, GDT_Float64 = 7, GDT_CInt16 = 8, GDT_CInt32 = 9,GDT_CInt64 GDT_CFloat32 = 10, GDT_CFloat64 = 11, GDT_TypeCount = 12 } GDALDataType; */ GDALDataType g_type = GDALDataType(poBand1->GetRasterDataType()); std::cout << "g_type = " << g_type << std::endl; //获取坐标变换系数 double trans[6] = { 0,1,0,0,0,1 };//定义为默认值,即x、y分辨率为1,其他信息为0 CPLErr aaa = poDataset->GetGeoTransform(trans); trans[2] = 0.3;//修改x的旋转参数信息 trans[4] = 0.1;//修改y的旋转参数信息 //poDataset->SetGeoTransform(trans);//设置坐标变换系数 std::cout << "trans = " << trans[0] << "," << trans[1] << "," << trans[2] << "," << trans[3] << "," << trans[4] << "," << trans[5] << std::endl; //像素坐标与投影坐标的换算 double dProjX, dProjY; int iCol, iRow; iCol = 111; iRow = 111; ImageRowCol2Projection(trans, iCol, iRow, dProjX, dProjY); std::cout << "在trans中,像素坐标=》经纬度:" << iCol << "," << iRow << "====》" << dProjX << "," << dProjY << std::endl; Projection2ImageRowCol(trans, dProjX, dProjY, iCol, iRow); std::cout << "在trans中,经纬度=》像素坐标:" << dProjX << "," << dProjY << "====》" << iCol << "," << iRow << std::endl; //获取图像投影坐标系信息, std::string projs = poDataset->GetProjectionRef(); //设置地理坐标系信息 //poDataset->SetProjection(projs.c_str()); std::cout << "projs = " << projs << std::endl; //读取gadl中第一个通道的数据到mat中 【通道数是从1开始的】 //RasterIO参数列表的详细说明可以参考 https://blog.51cto.com/u_15469043/4903358 /*从参数列表中是可以看到,GDAL是支持将数据分块读入的内存中的 CPLErr GDALRasterBand::RasterIO ( GDALRWFlag eRWFlag, int nXOff,//x的起始点 int nYOff,//y的起始点 int nXSize,//读取窗口的宽 int nYSize,//读取窗口的高 void * pData, int nBufXSize,//与nXSize相同 int nBufYSize,//与nYSize相同 GDALDataType eBufType, int nPixelSpace,//通常默认为0 int nLineSpace //通常默认为0 ) */ // cv::Mat gdal_mat1(nImgSizeY, nImgSizeX, CV_8UC1, Scalar(0)); cv::Mat gdal_mat2(nImgSizeY, nImgSizeX, CV_8UC1, Scalar(0)); cv::Mat gdal_mat3(nImgSizeY, nImgSizeX, CV_8UC1, Scalar(0)); poDataset->GetRasterBand(1)->RasterIO(GF_Read, 0, 0, nImgSizeX, nImgSizeY, gdal_mat1.data, nImgSizeX, nImgSizeY, g_type, 0, 0); poDataset->GetRasterBand(2)->RasterIO(GF_Read, 0, 0, nImgSizeX, nImgSizeY, gdal_mat2.data, nImgSizeX, nImgSizeY, g_type, 0, 0); poDataset->GetRasterBand(3)->RasterIO(GF_Read, 0, 0, nImgSizeX, nImgSizeY, gdal_mat3.data, nImgSizeX, nImgSizeY, g_type, 0, 0); cv::Mat mg; cv::merge(vector<cv::Mat>{ gdal_mat3, gdal_mat2, gdal_mat1, }, mg); cv::imwrite("read_save.jpg", mg); /* //读取gadl中第一个通道的数据到指针中 //void * malloc(size_t n):给指针分配相应的内存,并返回内存空间的首地址。当内存不再使用的时候,应使用free()函数将内存块释放掉。 uint8_t* srcData = (uint8_t*)malloc(sizeof(uint8_t) * nImgSizeX * nImgSizeY); //void * memset (void * p,int c,size_t n):将p中的n个字节都赋值为c memset(srcData, 0, sizeof(uint8_t) * 1 * nImgSizeX * nImgSizeY);//为空间赋默认值0 poDataset->GetRasterBand(1)->RasterIO(GF_Read, 0, 0, nImgSizeX, nImgSizeY, srcData, nImgSizeX, nImgSizeY, g_type, 0, 0); */ //-----------创建gdal对象 MEM追加,CreateCopy保存支持tif、png、jpg等格式----- int nImgSizeX2 = gdal_mat1.cols; int nImgSizeY2 = gdal_mat1.rows; //获取GDAL驱动,MEM表示为内存对象,可以快速的分块追加写入数据。MEM文件大小是和你的系统内存大小有关系,并不会存储到磁盘中。可用的驱动格式还有:BMP、JPEG、PNG、GTiff、GIF、HFA、BT、ECW、FITS、HDF4、EHdr。分别对应着不同的文件类型 GDALDriver* pDriverMEM = GetGDALDriverManager()->GetDriverByName("MEM"); int nBands = 1; //创建GDAL对象,只保存原图的一个通道 //Create(const char * pszName,int nXSize, int nYSize, int nBands, GDALDataType eType, char** papszOptions) GDALDataset* poDataset2 = pDriverMEM->Create("", nImgSizeX2, nImgSizeY2, nBands, g_type, NULL); //将mat数据写入到GDALDataset中 poDataset2->GetRasterBand(1)->RasterIO(GF_Write, 0, 0, nImgSizeX2, nImgSizeY2, gdal_mat1.data, nImgSizeX2, nImgSizeY2, GDT_Byte, 0, 0); //获取GDAL驱动,PNG表示为用png驱动保存数据 GDALDriver* pDriverSave = GetGDALDriverManager()->GetDriverByName("PNG"); pDriverSave->CreateCopy("saved.png", poDataset2, TRUE, 0, 0, 0); //创建png文件 std::cout << "png 文件保存成功" << std::endl; //-----------创建gdal对象 一次性写入,只支持tiff数据。PEN、JPEG等驱动没有实现相应的Create方法----- int nImgSizeX3 = gdal_mat1.cols; int nImgSizeY3 = gdal_mat1.rows; GDALDriver* pDriverMEM3 = GetGDALDriverManager()->GetDriverByName("GTiff"); if (!pDriverMEM3) { fprintf(stderr, "get driver by name failed\n"); return -1; } int nBands3 = 1; GDALDataset* poDataset3 = pDriverMEM3->Create("saved3.tif", nImgSizeX3, nImgSizeY3, nBands3, g_type, NULL); if (!poDataset3) { fprintf(stderr, "Create GDALDataset failed\n"); return -1; } poDataset3->GetRasterBand(1)->RasterIO(GF_Write, 0, 0, nImgSizeX3, nImgSizeY3, gdal_mat1.data, nImgSizeX3, nImgSizeY3, GDT_Byte, 0, 0); std::cout << "tif 文件保存成功" << std::endl; //关闭GDAL对象,并注销所有驱动 GDALClose(poDataset); GDALClose(poDataset2); GDALClose(poDataset3); GDALDestroyDriverManager(); return -1; }】
最新发布
07-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值