GDAL库中提供了很方便的插件机制来扩展支持的数据格式,比如HDF4、HDF5、NetCDF、FileGDB、Postgre、Oralce等等。都可以通过插件的方式来使得GDAL支持相应的格式。最近将所有的能编译成插件的格式都编译成插件,这样在发布的时候有些用不到的数据格式就可以不用将对应的插件以及以来的dll放进去,减少安装包的体积等。
发现HDF4、HDF5和NetCDF这三个编译成插件之后会出现几个问题,比如可以打开HDF4和HDF5的数据,但是不能打开里面的子数据集,找了好久,才发现GDAL的插件机制有点小欠缺(小问题)。
自己研究发现,GDAL的插件机制是这样的,结合代码看看,在文件gcore/gdaldrivermanager.cpp中的函数void GDALDriverManager::AutoLoadDrivers()中,节选最关键的一点代码,如下所示:
/* -------------------------------------------------------------------- */ /* Format the ABI version specific subdirectory to look in. */ /* -------------------------------------------------------------------- */ CPLString osABIVersion; osABIVersion.Printf( "%d.%d", GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR ); /* -------------------------------------------------------------------- */ /* Scan each directory looking for files starting with gdal_ */ /* -------------------------------------------------------------------- */ for( int iDir = 0; iDir < CSLCount(papszSearchPath); iDir++ ) { char **papszFiles = NULL; VSIStatBufL sStatBuf; CPLString osABISpecificDir = CPLFormFilename( papszSearchPath[iDir], osABIVersion, NULL ); if( VSIStatL( osABISpecificDir, &sStatBuf ) != 0 ) osABISpecificDir = papszSearchPath[iDir]; papszFiles = CPLReadDir( osABISpecificDir ); int nFileCount = CSLCount(papszFiles); for( int iFile = 0; iFile < nFileCount; iFile++ ) { char *pszFuncName; const char *pszFilename; const char *pszExtension = CPLGetExtension( papszFiles[iFile] ); void *pRegister; if( !EQUAL(pszExtension,"dll") && !EQUAL(pszExtension,"so") && !EQUAL(pszExtension,"dylib") ) continue; if( EQUALN(papszFiles[iFile],"gdal_",strlen("gdal_")) ) { pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1); sprintf( pszFuncName, "GDALRegister_%s", CPLGetBasename(papszFiles[iFile]) + strlen("gdal_") ); } else if ( EQUALN(papszFiles[iFile],"ogr_",strlen("ogr_")) ) { pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1); sprintf( pszFuncName, "RegisterOGR%s", CPLGetBasename(papszFiles[iFile]) + strlen("ogr_") ); } else continue; pszFilename = CPLFormFilename( osABISpecificDir, papszFiles[iFile], NULL ); CPLErrorReset(); CPLPushErrorHandler(CPLQuietErrorHandler); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); CPLPopErrorHandler(); if( pRegister == NULL ) { CPLString osLastErrorMsg(CPLGetLastErrorMsg()); strcpy( pszFuncName, "GDALRegisterMe" ); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); if( pRegister == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "%s", osLastErrorMsg.c_str() ); } } if( pRegister != NULL ) { CPLDebug( "GDAL", "Auto register %s using %s.", pszFilename, pszFuncName ); ((void (*)()) pRegister)(); } CPLFree( pszFuncName ); } CSLDestroy( papszFiles ); } CSLDestroy( papszSearchPath );仔细分析上面的代码,可以看到GDAL在加载驱动并进行注册的时候,将插件的名称里面的gdal_XXX后面的字符串XXX取出来,然后组成函数GDALRegister_XXX,在插件的dll中查找这个函数GDALRegister_XXX指针,如果找不到就找GDALRegisterMe函数。然后调用这个函数进行注册。对于OGR的插件也是类似。
一般的插件来说,导出的函数中GDALRegister_开头的函数只有一个,如果一个插件里面有两个GDALRegister_函数,那就惨了,插件只能注册那个和插件名字一样的那一个另外一个没注册进行,所以肯定也打不开对应的数据了。HDF4、HDF5以及NetCDF就是这样的特例。HDF4和HDF5插件里面实现了两个GDALRegister_函数,一个是用来打开数据的,另外一个是用来打开子数据的,也就是GDALRegister_HDF4和GDALRegister_HDF4Image以及GDALRegister_HDF5和GDALRegister_HDF5Image。这样的话,对于插件方式来说,这两个后面带有Image的肯定注册不了。
知道插件注册的原理,那么解决方式就有了,不想改代码的话,就把GDAL的HDF4和HDF5的插件dll复制一份,然后改名字,后面加一个Image就好了,这样最简单,但是同样的dll会以不同的名字存在两边,觉得不太爽。那么第二种就是改源码了。具体就是修改上面这段代码,如果是HDF的格式的话,单独判断一下,然后将Image这个函数注册了就好了(对于NetCDF也有类似的问题)。修改后的代码如下:
/* -------------------------------------------------------------------- */ /* Format the ABI version specific subdirectory to look in. */ /* -------------------------------------------------------------------- */ CPLString osABIVersion; osABIVersion.Printf( "%d.%d", GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR ); /* -------------------------------------------------------------------- */ /* Scan each directory looking for files starting with gdal_ */ /* -------------------------------------------------------------------- */ for( int iDir = 0; iDir < CSLCount(papszSearchPath); iDir++ ) { char **papszFiles = NULL; VSIStatBufL sStatBuf; CPLString osABISpecificDir = CPLFormFilename( papszSearchPath[iDir], osABIVersion, NULL ); if( VSIStatL( osABISpecificDir, &sStatBuf ) != 0 ) osABISpecificDir = papszSearchPath[iDir]; papszFiles = CPLReadDir( osABISpecificDir ); int nFileCount = CSLCount(papszFiles); for( int iFile = 0; iFile < nFileCount; iFile++ ) { char *pszFuncName; const char *pszFilename; const char *pszExtension = CPLGetExtension( papszFiles[iFile] ); void *pRegister; if( !EQUAL(pszExtension,"dll") && !EQUAL(pszExtension,"so") && !EQUAL(pszExtension,"dylib") ) continue; if( EQUALN(papszFiles[iFile],"gdal_",strlen("gdal_")) ) { pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1); sprintf( pszFuncName, "GDALRegister_%s", CPLGetBasename(papszFiles[iFile]) + strlen("gdal_") ); } else if ( EQUALN(papszFiles[iFile],"ogr_",strlen("ogr_")) ) { pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1); sprintf( pszFuncName, "RegisterOGR%s", CPLGetBasename(papszFiles[iFile]) + strlen("ogr_") ); } else continue; pszFilename = CPLFormFilename( osABISpecificDir, papszFiles[iFile], NULL ); CPLErrorReset(); CPLPushErrorHandler(CPLQuietErrorHandler); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); CPLPopErrorHandler(); if( pRegister == NULL ) { CPLString osLastErrorMsg(CPLGetLastErrorMsg()); strcpy( pszFuncName, "GDALRegisterMe" ); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); if( pRegister == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "%s", osLastErrorMsg.c_str() ); } } if( pRegister != NULL ) { CPLDebug( "GDAL", "Auto register %s using %s.", pszFilename, pszFuncName ); ((void (*)()) pRegister)(); // 如果一个插件dll中有多个注册函数,目前针对特殊的几种格式进行特殊处理 const char* pszBaseName = CPLGetBasename(papszFiles[iFile]) + 5; if( EQUAL(pszBaseName, "HDF4") || EQUAL(pszBaseName, "HDF5") ) { CPLString osLastErrorMsg(CPLGetLastErrorMsg()); sprintf( pszFuncName, "GDALRegister_%sImage", CPLGetBasename(papszFiles[iFile]) + 5 ); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); if( pRegister == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "%s", osLastErrorMsg.c_str() ); } CPLDebug( "GDAL", "Auto register %s using %s.", pszFilename, pszFuncName ); ((void (*)()) pRegister)(); } else if(EQUAL(pszBaseName, "NETCDF")) { CPLString osLastErrorMsg(CPLGetLastErrorMsg()); strcpy( pszFuncName, "GDALRegister_GMT" ); pRegister = CPLGetSymbol( pszFilename, pszFuncName ); if( pRegister == NULL ) { CPLError( CE_Failure, CPLE_AppDefined, "%s", osLastErrorMsg.c_str() ); } CPLDebug( "GDAL", "Auto register %s using %s.", pszFilename, pszFuncName ); ((void (*)()) pRegister)(); } } CPLFree( pszFuncName ); } CSLDestroy( papszFiles ); } CSLDestroy( papszSearchPath ); }