GDAL库中提供了很方便的插件机制来扩展支持的数据格式,比如HDF4、HDF5、NetCDF、FileGDB、Postgre、Oralce等等。都可以通过插件的方式来使得GDAL支持相应的格式。最近将所有的能编译成插件的格式都编译成插件,这样在发布的时候有些用不到的数据格式就可以不用将对应的插件以及以来的dll放进去,减少安装包的体积等。

发现HDF4、HDF5和NetCDF这三个编译成插件之后会出现几个问题,比如可以打开HDF4和HDF5的数据,但是不能打开里面的子数据集,找了好久,才发现GDAL的插件机制有点小欠缺(小问题)。

自己研究发现,GDAL的插件机制是这样的,结合代码看看,在文件gcore/gdaldrivermanager.cpp中的函数void GDALDriverManager::AutoLoadDrivers()中,节选最关键的一点代码,如下所示:

  1. /* -------------------------------------------------------------------- */
  2. /* Format the ABI version specific subdirectory to look in. */
  3. /* -------------------------------------------------------------------- */
  4. CPLString osABIVersion;
  5.  
  6. osABIVersion.Printf( "%d.%d", GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR );
  7.  
  8. /* -------------------------------------------------------------------- */
  9. /* Scan each directory looking for files starting with gdal_ */
  10. /* -------------------------------------------------------------------- */
  11. for( int iDir = 0; iDir < CSLCount(papszSearchPath); iDir++ )
  12. {
  13. char **papszFiles = NULL;
  14. VSIStatBufL sStatBuf;
  15. CPLString osABISpecificDir =
  16. CPLFormFilename( papszSearchPath[iDir], osABIVersion, NULL );
  17.  
  18. if( VSIStatL( osABISpecificDir, &sStatBuf ) != 0 )
  19. osABISpecificDir = papszSearchPath[iDir];
  20.  
  21. papszFiles = CPLReadDir( osABISpecificDir );
  22. int nFileCount = CSLCount(papszFiles);
  23.  
  24. for( int iFile = 0; iFile < nFileCount; iFile++ )
  25. {
  26. char *pszFuncName;
  27. const char *pszFilename;
  28. const char *pszExtension = CPLGetExtension( papszFiles[iFile] );
  29. void *pRegister;
  30.  
  31. if( !EQUAL(pszExtension,"dll")
  32. && !EQUAL(pszExtension,"so")
  33. && !EQUAL(pszExtension,"dylib") )
  34. continue;
  35.  
  36. if( EQUALN(papszFiles[iFile],"gdal_",strlen("gdal_")) )
  37. {
  38. pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1);
  39. sprintf( pszFuncName, "GDALRegister_%s",
  40. CPLGetBasename(papszFiles[iFile]) + strlen("gdal_") );
  41. }
  42. else if ( EQUALN(papszFiles[iFile],"ogr_",strlen("ogr_")) )
  43. {
  44. pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1);
  45. sprintf( pszFuncName, "RegisterOGR%s",
  46. CPLGetBasename(papszFiles[iFile]) + strlen("ogr_") );
  47. }
  48. else
  49. continue;
  50.  
  51. pszFilename =
  52. CPLFormFilename( osABISpecificDir,
  53. papszFiles[iFile], NULL );
  54.  
  55. CPLErrorReset();
  56. CPLPushErrorHandler(CPLQuietErrorHandler);
  57. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  58. CPLPopErrorHandler();
  59. if( pRegister == NULL )
  60. {
  61. CPLString osLastErrorMsg(CPLGetLastErrorMsg());
  62. strcpy( pszFuncName, "GDALRegisterMe" );
  63. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  64. if( pRegister == NULL )
  65. {
  66. CPLError( CE_Failure, CPLE_AppDefined,
  67. "%s", osLastErrorMsg.c_str() );
  68. }
  69. }
  70.  
  71. if( pRegister != NULL )
  72. {
  73. CPLDebug( "GDAL", "Auto register %s using %s.",
  74. pszFilename, pszFuncName );
  75.  
  76. ((void (*)()) pRegister)();
  77. }
  78.  
  79. CPLFree( pszFuncName );
  80. }
  81.  
  82. CSLDestroy( papszFiles );
  83. }
  84.  
  85. 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也有类似的问题)。修改后的代码如下:

  1. /* -------------------------------------------------------------------- */
  2. /* Format the ABI version specific subdirectory to look in. */
  3. /* -------------------------------------------------------------------- */
  4. CPLString osABIVersion;
  5.  
  6. osABIVersion.Printf( "%d.%d", GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR );
  7.  
  8. /* -------------------------------------------------------------------- */
  9. /* Scan each directory looking for files starting with gdal_ */
  10. /* -------------------------------------------------------------------- */
  11. for( int iDir = 0; iDir < CSLCount(papszSearchPath); iDir++ )
  12. {
  13. char **papszFiles = NULL;
  14. VSIStatBufL sStatBuf;
  15. CPLString osABISpecificDir =
  16. CPLFormFilename( papszSearchPath[iDir], osABIVersion, NULL );
  17.  
  18. if( VSIStatL( osABISpecificDir, &sStatBuf ) != 0 )
  19. osABISpecificDir = papszSearchPath[iDir];
  20.  
  21. papszFiles = CPLReadDir( osABISpecificDir );
  22. int nFileCount = CSLCount(papszFiles);
  23.  
  24. for( int iFile = 0; iFile < nFileCount; iFile++ )
  25. {
  26. char *pszFuncName;
  27. const char *pszFilename;
  28. const char *pszExtension = CPLGetExtension( papszFiles[iFile] );
  29. void *pRegister;
  30.  
  31. if( !EQUAL(pszExtension,"dll")
  32. && !EQUAL(pszExtension,"so")
  33. && !EQUAL(pszExtension,"dylib") )
  34. continue;
  35.  
  36. if( EQUALN(papszFiles[iFile],"gdal_",strlen("gdal_")) )
  37. {
  38. pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1);
  39. sprintf( pszFuncName, "GDALRegister_%s",
  40. CPLGetBasename(papszFiles[iFile]) + strlen("gdal_") );
  41. }
  42. else if ( EQUALN(papszFiles[iFile],"ogr_",strlen("ogr_")) )
  43. {
  44. pszFuncName = (char *) CPLCalloc(strlen(papszFiles[iFile])+20,1);
  45. sprintf( pszFuncName, "RegisterOGR%s",
  46. CPLGetBasename(papszFiles[iFile]) + strlen("ogr_") );
  47. }
  48. else
  49. continue;
  50.  
  51. pszFilename =
  52. CPLFormFilename( osABISpecificDir,
  53. papszFiles[iFile], NULL );
  54.  
  55. CPLErrorReset();
  56. CPLPushErrorHandler(CPLQuietErrorHandler);
  57. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  58. CPLPopErrorHandler();
  59. if( pRegister == NULL )
  60. {
  61. CPLString osLastErrorMsg(CPLGetLastErrorMsg());
  62. strcpy( pszFuncName, "GDALRegisterMe" );
  63. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  64. if( pRegister == NULL )
  65. {
  66. CPLError( CE_Failure, CPLE_AppDefined,
  67. "%s", osLastErrorMsg.c_str() );
  68. }
  69. }
  70.  
  71. if( pRegister != NULL )
  72. {
  73. CPLDebug( "GDAL", "Auto register %s using %s.",
  74. pszFilename, pszFuncName );
  75.  
  76. ((void (*)()) pRegister)();
  77.  
  78. // 如果一个插件dll中有多个注册函数,目前针对特殊的几种格式进行特殊处理
  79. const char* pszBaseName = CPLGetBasename(papszFiles[iFile]) + 5;
  80. if( EQUAL(pszBaseName, "HDF4") || EQUAL(pszBaseName, "HDF5") )
  81. {
  82. CPLString osLastErrorMsg(CPLGetLastErrorMsg());
  83. sprintf( pszFuncName, "GDALRegister_%sImage",
  84. CPLGetBasename(papszFiles[iFile]) + 5 );
  85.  
  86. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  87. if( pRegister == NULL )
  88. {
  89. CPLError( CE_Failure, CPLE_AppDefined,
  90. "%s", osLastErrorMsg.c_str() );
  91. }
  92.  
  93. CPLDebug( "GDAL", "Auto register %s using %s.",
  94. pszFilename, pszFuncName );
  95.  
  96. ((void (*)()) pRegister)();
  97. }
  98. else if(EQUAL(pszBaseName, "NETCDF"))
  99. {
  100. CPLString osLastErrorMsg(CPLGetLastErrorMsg());
  101. strcpy( pszFuncName, "GDALRegister_GMT" );
  102. pRegister = CPLGetSymbol( pszFilename, pszFuncName );
  103. if( pRegister == NULL )
  104. {
  105. CPLError( CE_Failure, CPLE_AppDefined,
  106. "%s", osLastErrorMsg.c_str() );
  107. }
  108.  
  109. CPLDebug( "GDAL", "Auto register %s using %s.",
  110. pszFilename, pszFuncName );
  111.  
  112. ((void (*)()) pRegister)();
  113. }
  114. }
  115.  
  116. CPLFree( pszFuncName );
  117. }
  118.  
  119. CSLDestroy( papszFiles );
  120. }
  121.  
  122. CSLDestroy( papszSearchPath );
  123. }

GDAL使用插件方式编译HDF4、HDF5以及NetCDF的bug修改的更多相关文章

  1. 如何实现Windows Phone代码与Unity相互通信(插件方式)

    原地址:http://www.cnblogs.com/petto/p/3915943.html 一些废话 原文地址: http://imwper.com/unity/petto/%E5%A6%82%E ...

  2. Heka 的编译 和 Heka 插件的编译

    相关英文文档在: https://hekad.readthedocs.io/en/latest/installing.html 所有系统都必须的如下: Prerequisites (all syste ...

  3. [转]Windows中使用命令行方式编译打包Android项目

    http://my.oschina.net/liux/blog/37875 网上很多用Ant来编译打包Android应用的文章,毕竟Ant是纯Java语言编写的,具有很好的跨平台性.今天想写个纯win ...

  4. AndroidStudio用Cmake方式编译NDK代码(cmake配置.a库)

    1.cmake是什么? CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程).他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C+ ...

  5. Django【设计】可插拔的插件方式实现

    需求: 在CMDB系统中,我们需要对资产进行采集和资产入库,包括serverBasic.disk.memory.nic信息等,客户端需要采集这些硬件的信息,服务端则负责资产入库,但是需要采集的硬件并不 ...

  6. 编译spark源码 Maven 、SBT 2种方式编译

    由于实际环境较为复杂,从Spark官方下载二进制安装包可能不具有相关功能或不支持指定的软件版本,这就需要我们根据实际情况编译Spark源代码,生成所需要的部署包. Spark可以通过Maven和SBT ...

  7. mybatis通过插件方式实现读写分离

    原理:通过自定义mybatis插件,拦截Executor的update和query方法,检查sql中有select就用读的库,其它的用写的库(如果有调用存储过程就另当别论了) @Intercepts( ...

  8. OSGI.NET插件方式开发你的应用

    之前一直从事C# WEB开发.基本都是业务开发,性能优化. 体力活占比90%吧.模块真的很多很多,每次部署经常出先各种问题.发布经常加班. 今年开始接触winform 开发.发现C# 的事件  委托 ...

  9. MyBatis代码生成器(maven插件方式和控制台命令运行方式)

    代码生成器的作用: 1.生成domain 2.生成mapper接口 3.生成mapper映射文件 准备工作:导入MyBatis所需要的包 第一步:在src/main/resources(必须)目录下创 ...

随机推荐

  1. 51nod 1364 最大字典序排列(线段树)

    1364 最大字典序排列基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 给出一个1至N的排列,允许你做不超过K次操作,每次操作可以将相邻的两个数交换,问能够得到的字 ...

  2. 【bzoj4569 scoi2016】萌萌哒

    题目描述 一个长度为n的大数,用S1S2S3...Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串S ...

  3. hdu5631 BestCoder Round #73 (div.2)

    Rikka with Graph  Accepts: 123  Submissions: 525  Time Limit: 2000/1000 MS (Java/Others)  Memory Lim ...

  4. [51nod1238]最小公倍数之和V3

    来自FallDream的博客,未经允许,请勿转载,谢谢. ----------------------------------------------------------------------- ...

  5. bzoj1934

    1934: [Shoi2007]Vote 善意的投票 Time Limit: 1 Sec  Memory Limit: 64 MBSubmit: 2406  Solved: 1498[Submit][ ...

  6. 华科机考:N阶楼梯上楼

    时间限制:1秒空间限制:32768K 题目描述 N阶楼梯上楼问题:一次可以走两阶或一阶,问有多少种上楼方式.(要求采用非递归) 输入描述: 输入包括一个整数N,(1<=N<90). 输出描 ...

  7. Cisco Port-Channel 设置(链路聚合)

    Port-Channel 的在实际工作中的主要作用是将两个或多个端口捆绑成为一个虚拟通道. interface Port-channel1 description port(1/0/5-6) swit ...

  8. JVM Class字节码之三-使用BCEL改变类属性

    使用BCEL动态改变Class内容 之前对Class文件中的常量池,Method的字节码指令进行了说明.JVM Class详解之一JVM Class详解之二 Method字节码指令现在我们开始实际动手 ...

  9. moment.js常用时间示例,时间管理

    '今天': moment() '昨天': moment().subtract(1, 'days') '过去7天':moment().subtract(7, 'days'),moment() '上月': ...

  10. Java Socket通信代码片

    package zhang; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOExcept ...