GDAL使用插件方式編譯HDF4、HDF5以及NetCDF的bug修改

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 );
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章