/****************************************************************************** * $Id: gdaldrivermanager.cpp 29330 2015-06-14 12:11:11Z rouault $ * * Project: GDAL Core * Purpose: Implementation of GDALDriverManager class. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 1998, Frank Warmerdam * Copyright (c) 2009-2013, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "gdal_priv.h" #include "cpl_string.h" #include "cpl_multiproc.h" #include "ogr_srs_api.h" #include "cpl_multiproc.h" #include "gdal_pam.h" #include "gdal_alg_priv.h" #ifdef _MSC_VER # ifdef MSVC_USE_VLD # include # include # endif #endif CPL_CVSID("$Id: gdaldrivermanager.cpp 29330 2015-06-14 12:11:11Z rouault $"); static const char *pszUpdatableINST_DATA = "__INST_DATA_TARGET: "; /************************************************************************/ /* ==================================================================== */ /* GDALDriverManager */ /* ==================================================================== */ /************************************************************************/ static volatile GDALDriverManager *poDM = NULL; static CPLMutex *hDMMutex = NULL; CPLMutex** GDALGetphDMMutex() { return &hDMMutex; } /************************************************************************/ /* GetGDALDriverManager() */ /* */ /* A freestanding function to get the only instance of the */ /* GDALDriverManager. */ /************************************************************************/ /** * \brief Fetch the global GDAL driver manager. * * This function fetches the pointer to the singleton global driver manager. * If the driver manager doesn't exist it is automatically created. * * @return pointer to the global driver manager. This should not be able * to fail. */ GDALDriverManager * GetGDALDriverManager() { if( poDM == NULL ) { CPLMutexHolderD( &hDMMutex ); if( poDM == NULL ) poDM = new GDALDriverManager(); } CPLAssert( NULL != poDM ); return const_cast( poDM ); } /************************************************************************/ /* GDALDriverManager() */ /************************************************************************/ GDALDriverManager::GDALDriverManager() { nDrivers = 0; papoDrivers = NULL; CPLAssert( poDM == NULL ); /* -------------------------------------------------------------------- */ /* We want to push a location to search for data files */ /* supporting GDAL/OGR such as EPSG csv files, S-57 definition */ /* files, and so forth. The static pszUpdateableINST_DATA */ /* string can be updated within the shared library or */ /* executable during an install to point installed data */ /* directory. If it isn't burned in here then we use the */ /* INST_DATA macro (setup at configure time) if */ /* available. Otherwise we don't push anything and we hope */ /* other mechanisms such as environment variables will have */ /* been employed. */ /* -------------------------------------------------------------------- */ if( CPLGetConfigOption( "GDAL_DATA", NULL ) != NULL ) { // this one is picked up automatically by finder initialization. } else if( pszUpdatableINST_DATA[19] != ' ' ) { CPLPushFinderLocation( pszUpdatableINST_DATA + 19 ); } else { #ifdef INST_DATA CPLPushFinderLocation( INST_DATA ); #endif } } /************************************************************************/ /* ~GDALDriverManager() */ /************************************************************************/ void GDALDatasetPoolPreventDestroy(); /* keep that in sync with gdalproxypool.cpp */ void GDALDatasetPoolForceDestroy(); /* keep that in sync with gdalproxypool.cpp */ GDALDriverManager::~GDALDriverManager() { /* -------------------------------------------------------------------- */ /* Cleanup any open datasets. */ /* -------------------------------------------------------------------- */ int i, nDSCount; GDALDataset **papoDSList; /* First begin by requesting each reamining dataset to drop any reference */ /* to other datasets */ int bHasDroppedRef; /* We have to prevent the destroying of the dataset pool during this first */ /* phase, otherwise it cause crashes with a VRT B referencing a VRT A, and if */ /* CloseDependentDatasets() is called first on VRT A. */ /* If we didn't do this nasty trick, due to the refCountOfDisableRefCount */ /* mechanism that cheats the real refcount of the dataset pool, we might */ /* destroy the dataset pool too early, leading the VRT A to */ /* destroy itself indirectly ... Ok, I am aware this explanation does */ /* not make any sense unless you try it under a debugger ... */ /* When people just manipulate "top-level" dataset handles, we luckily */ /* don't need this horrible hack, but GetOpenDatasets() expose "low-level" */ /* datasets, which defeat some "design" of the proxy pool */ GDALDatasetPoolPreventDestroy(); do { papoDSList = GDALDataset::GetOpenDatasets(&nDSCount); /* If a dataset has dropped a reference, the list might have become */ /* invalid, so go out of the loop and try again with the new valid */ /* list */ bHasDroppedRef = FALSE; for(i=0;iGetDescription() ); bHasDroppedRef = papoDSList[i]->CloseDependentDatasets(); } } while(bHasDroppedRef); /* Now let's destroy the dataset pool. Nobody should use it afterwards */ /* if people have well released their dependent datasets above */ GDALDatasetPoolForceDestroy(); /* Now close the stand-alone datasets */ papoDSList = GDALDataset::GetOpenDatasets(&nDSCount); for(i=0;iGetDescription(), papoDSList[i] ); /* Destroy with delete operator rather than GDALClose() to force deletion of */ /* datasets with multiple reference count */ /* We could also iterate while GetOpenDatasets() returns a non NULL list */ delete papoDSList[i]; } /* -------------------------------------------------------------------- */ /* Destroy the existing drivers. */ /* -------------------------------------------------------------------- */ while( GetDriverCount() > 0 ) { GDALDriver *poDriver = GetDriver(0); DeregisterDriver(poDriver); delete poDriver; } delete GDALGetAPIPROXYDriver(); /* -------------------------------------------------------------------- */ /* Cleanup local memory. */ /* -------------------------------------------------------------------- */ VSIFree( papoDrivers ); /* -------------------------------------------------------------------- */ /* 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(); CPLCleanupSharedFileMutex(); /* -------------------------------------------------------------------- */ /* 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(); /* -------------------------------------------------------------------- */ /* Cleanup our mutex. */ /* -------------------------------------------------------------------- */ if( hDMMutex ) { CPLDestroyMutex( hDMMutex ); hDMMutex = NULL; } /* -------------------------------------------------------------------- */ /* Cleanup dataset list mutex */ /* -------------------------------------------------------------------- */ if ( *GDALGetphDLMutex() != NULL ) { CPLDestroyMutex( *GDALGetphDLMutex() ); *GDALGetphDLMutex() = NULL; } /* -------------------------------------------------------------------- */ /* Cleanup raster block mutex */ /* -------------------------------------------------------------------- */ GDALRasterBlock::DestroyRBMutex(); /* -------------------------------------------------------------------- */ /* Cleanup gdaltransformer.cpp mutex */ /* -------------------------------------------------------------------- */ GDALCleanupTransformDeserializerMutex(); /* -------------------------------------------------------------------- */ /* Cleanup cpl_error.cpp mutex */ /* -------------------------------------------------------------------- */ CPLCleanupErrorMutex(); /* -------------------------------------------------------------------- */ /* Cleanup CPLsetlocale mutex */ /* -------------------------------------------------------------------- */ CPLCleanupSetlocaleMutex(); /* -------------------------------------------------------------------- */ /* Cleanup the master CPL mutex, which governs the creation */ /* of all other mutexes. */ /* -------------------------------------------------------------------- */ CPLCleanupMasterMutex(); /* -------------------------------------------------------------------- */ /* Ensure the global driver manager pointer is NULLed out. */ /* -------------------------------------------------------------------- */ if( poDM == this ) poDM = NULL; } /************************************************************************/ /* GetDriverCount() */ /************************************************************************/ /** * \brief Fetch the number of registered drivers. * * This C analog to this is GDALGetDriverCount(). * * @return the number of registered drivers. */ int GDALDriverManager::GetDriverCount() { return( nDrivers ); } /************************************************************************/ /* GDALGetDriverCount() */ /************************************************************************/ /** * \brief Fetch the number of registered drivers. * * @see GDALDriverManager::GetDriverCount() */ int CPL_STDCALL GDALGetDriverCount() { return GetGDALDriverManager()->GetDriverCount(); } /************************************************************************/ /* GetDriver() */ /************************************************************************/ /** * \brief Fetch driver by index. * * This C analog to this is GDALGetDriver(). * * @param iDriver the driver index from 0 to GetDriverCount()-1. * * @return the driver identified by the index or NULL if the index is invalid */ GDALDriver * GDALDriverManager::GetDriver( int iDriver ) { CPLMutexHolderD( &hDMMutex ); return GetDriver_unlocked(iDriver); } /************************************************************************/ /* GDALGetDriver() */ /************************************************************************/ /** * \brief Fetch driver by index. * * @see GDALDriverManager::GetDriver() */ GDALDriverH CPL_STDCALL GDALGetDriver( int iDriver ) { return (GDALDriverH) GetGDALDriverManager()->GetDriver(iDriver); } /************************************************************************/ /* RegisterDriver() */ /************************************************************************/ /** * \brief Register a driver for use. * * The C analog is GDALRegisterDriver(). * * Normally this method is used by format specific C callable registration * entry points such as GDALRegister_GTiff() rather than being called * directly by application level code. * * If this driver (based on the object pointer, not short name) is already * registered, then no change is made, and the index of the existing driver * is returned. Otherwise the driver list is extended, and the new driver * is added at the end. * * @param poDriver the driver to register. * * @return the index of the new installed driver. */ int GDALDriverManager::RegisterDriver( GDALDriver * poDriver ) { CPLMutexHolderD( &hDMMutex ); /* -------------------------------------------------------------------- */ /* If it is already registered, just return the existing */ /* index. */ /* -------------------------------------------------------------------- */ if( GetDriverByName_unlocked( poDriver->GetDescription() ) != NULL ) { int i; for( i = 0; i < nDrivers; i++ ) { if( papoDrivers[i] == poDriver ) { return i; } } CPLAssert( FALSE ); } /* -------------------------------------------------------------------- */ /* Otherwise grow the list to hold the new entry. */ /* -------------------------------------------------------------------- */ papoDrivers = (GDALDriver **) VSIRealloc(papoDrivers, sizeof(GDALDriver *) * (nDrivers+1)); papoDrivers[nDrivers] = poDriver; nDrivers++; if( poDriver->pfnOpen != NULL || poDriver->pfnOpenWithDriverArg != NULL ) poDriver->SetMetadataItem( GDAL_DCAP_OPEN, "YES" ); if( poDriver->pfnCreate != NULL ) poDriver->SetMetadataItem( GDAL_DCAP_CREATE, "YES" ); if( poDriver->pfnCreateCopy != NULL ) poDriver->SetMetadataItem( GDAL_DCAP_CREATECOPY, "YES" ); /* Backward compability for GDAL raster out-of-tree drivers: */ /* if a driver hasn't explicitly set a vector capability, assume it is */ /* a raster driver (legacy OGR drivers will have DCAP_VECTOR set before */ /* calling RegisterDriver() ) */ if( poDriver->GetMetadataItem( GDAL_DCAP_RASTER ) == NULL && poDriver->GetMetadataItem( GDAL_DCAP_VECTOR ) == NULL ) { CPLDebug("GDAL", "Assuming DCAP_RASTER for driver %s. Please fix it.", poDriver->GetDescription() ); poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" ); } if( poDriver->GetMetadataItem( GDAL_DMD_OPENOPTIONLIST ) != NULL && poDriver->pfnIdentify == NULL && !EQUALN(poDriver->GetDescription(), "Interlis", strlen("Interlis")) ) { CPLDebug("GDAL", "Driver %s that defines GDAL_DMD_OPENOPTIONLIST must also " "implement Identify(), so that it can be used", poDriver->GetDescription() ); } oMapNameToDrivers[CPLString(poDriver->GetDescription()).toupper()] = poDriver; int iResult = nDrivers - 1; return iResult; } /************************************************************************/ /* GDALRegisterDriver() */ /************************************************************************/ /** * \brief Register a driver for use. * * @see GDALDriverManager::GetRegisterDriver() */ int CPL_STDCALL GDALRegisterDriver( GDALDriverH hDriver ) { VALIDATE_POINTER1( hDriver, "GDALRegisterDriver", 0 ); return GetGDALDriverManager()->RegisterDriver( (GDALDriver *) hDriver ); } /************************************************************************/ /* DeregisterDriver() */ /************************************************************************/ /** * \brief Deregister the passed driver. * * If the driver isn't found no change is made. * * The C analog is GDALDeregisterDriver(). * * @param poDriver the driver to deregister. */ void GDALDriverManager::DeregisterDriver( GDALDriver * poDriver ) { int i; CPLMutexHolderD( &hDMMutex ); for( i = 0; i < nDrivers; i++ ) { if( papoDrivers[i] == poDriver ) break; } if( i == nDrivers ) return; oMapNameToDrivers.erase(CPLString(poDriver->GetDescription()).toupper()); while( i < nDrivers-1 ) { papoDrivers[i] = papoDrivers[i+1]; i++; } nDrivers--; } /************************************************************************/ /* GDALDeregisterDriver() */ /************************************************************************/ /** * \brief Deregister the passed driver. * * @see GDALDriverManager::GetDeregisterDriver() */ void CPL_STDCALL GDALDeregisterDriver( GDALDriverH hDriver ) { VALIDATE_POINTER0( hDriver, "GDALDeregisterDriver" ); GetGDALDriverManager()->DeregisterDriver( (GDALDriver *) hDriver ); } /************************************************************************/ /* GetDriverByName() */ /************************************************************************/ /** * \brief Fetch a driver based on the short name. * * The C analog is the GDALGetDriverByName() function. * * @param pszName the short name, such as GTiff, being searched for. * * @return the identified driver, or NULL if no match is found. */ GDALDriver * GDALDriverManager::GetDriverByName( const char * pszName ) { CPLMutexHolderD( &hDMMutex ); return oMapNameToDrivers[CPLString(pszName).toupper()]; } /************************************************************************/ /* GDALGetDriverByName() */ /************************************************************************/ /** * \brief Fetch a driver based on the short name. * * @see GDALDriverManager::GetDriverByName() */ GDALDriverH CPL_STDCALL GDALGetDriverByName( const char * pszName ) { VALIDATE_POINTER1( pszName, "GDALGetDriverByName", NULL ); return( GetGDALDriverManager()->GetDriverByName( pszName ) ); } /************************************************************************/ /* AutoSkipDrivers() */ /************************************************************************/ /** * \brief This method unload undesirable drivers. * * All drivers specified in the comma delimited list in the GDAL_SKIP * environment variable) will be deregistered and destroyed. This method * should normally be called after registration of standard drivers to allow * the user a way of unloading undesired drivers. The GDALAllRegister() * function already invokes AutoSkipDrivers() at the end, so if that functions * is called, it should not be necessary to call this method from application * code. * * Note: space separator is also accepted for backward compatibility, but some * vector formats have spaces in their names, so it is encouraged to use comma * to avoid issues. */ void GDALDriverManager::AutoSkipDrivers() { char **apapszList[2] = { NULL, NULL }; const char* pszGDAL_SKIP = CPLGetConfigOption( "GDAL_SKIP", NULL ); if( pszGDAL_SKIP != NULL ) { /* Favour comma as a separator. If not found, then use space */ const char* pszSep = (strchr(pszGDAL_SKIP, ',') != NULL) ? "," : " "; apapszList[0] = CSLTokenizeStringComplex( pszGDAL_SKIP, pszSep, FALSE, FALSE); } const char* pszOGR_SKIP = CPLGetConfigOption( "OGR_SKIP", NULL ); if( pszOGR_SKIP != NULL ) { /* OGR has always used comma as a separator */ apapszList[1] = CSLTokenizeStringComplex(pszOGR_SKIP, ",", FALSE, FALSE); } for( int j = 0; j < 2; j++ ) { for( int i = 0; apapszList[j] != NULL && apapszList[j][i] != NULL; i++ ) { GDALDriver *poDriver = GetDriverByName( apapszList[j][i] ); if( poDriver == NULL ) CPLError( CE_Warning, CPLE_AppDefined, "Unable to find driver %s to unload from GDAL_SKIP environment variable.", apapszList[j][i] ); else { CPLDebug( "GDAL", "AutoSkipDriver(%s)", apapszList[j][i] ); DeregisterDriver( poDriver ); delete poDriver; } } } CSLDestroy( apapszList[0] ); CSLDestroy( apapszList[1] ); } /************************************************************************/ /* AutoLoadDrivers() */ /************************************************************************/ /** * \brief Auto-load GDAL drivers from shared libraries. * * This function will automatically load drivers from shared libraries. It * searches the "driver path" for .so (or .dll) files that start with the * prefix "gdal_X.so". It then tries to load them and then tries to call a * function within them called GDALRegister_X() where the 'X' is the same as * the remainder of the shared library basename ('X' is case sensitive), or * failing that to call GDALRegisterMe(). * * There are a few rules for the driver path. If the GDAL_DRIVER_PATH * environment variable it set, it is taken to be a list of directories to * search separated by colons on UNIX, or semi-colons on Windows. Otherwise * the /usr/local/lib/gdalplugins directory, and (if known) the * lib/gdalplugins subdirectory of the gdal home directory are searched on * UNIX and $(BINDIR)\gdalplugins on Windows. * * Auto loading can be completely disabled by setting the GDAL_DRIVER_PATH * config option to "disable". */ void GDALDriverManager::AutoLoadDrivers() { char **papszSearchPath = NULL; const char *pszGDAL_DRIVER_PATH = CPLGetConfigOption( "GDAL_DRIVER_PATH", NULL ); if( pszGDAL_DRIVER_PATH == NULL ) pszGDAL_DRIVER_PATH = CPLGetConfigOption( "OGR_DRIVER_PATH", NULL ); /* -------------------------------------------------------------------- */ /* Allow applications to completely disable this search by */ /* setting the driver path to the special string "disable". */ /* -------------------------------------------------------------------- */ if( pszGDAL_DRIVER_PATH != NULL && EQUAL(pszGDAL_DRIVER_PATH,"disable")) { CPLDebug( "GDAL", "GDALDriverManager::AutoLoadDrivers() disabled." ); return; } /* -------------------------------------------------------------------- */ /* Where should we look for stuff? */ /* -------------------------------------------------------------------- */ if( pszGDAL_DRIVER_PATH != NULL ) { #ifdef WIN32 papszSearchPath = CSLTokenizeStringComplex( pszGDAL_DRIVER_PATH, ";", TRUE, FALSE ); #else papszSearchPath = CSLTokenizeStringComplex( pszGDAL_DRIVER_PATH, ":", TRUE, FALSE ); #endif } else { #ifdef GDAL_PREFIX papszSearchPath = CSLAddString( papszSearchPath, #ifdef MACOSX_FRAMEWORK GDAL_PREFIX "/PlugIns"); #else GDAL_PREFIX "/lib/gdalplugins" ); #endif #else char szExecPath[1024]; if( CPLGetExecPath( szExecPath, sizeof(szExecPath) ) ) { char szPluginDir[sizeof(szExecPath)+50]; strcpy( szPluginDir, CPLGetDirname( szExecPath ) ); strcat( szPluginDir, "\\gdalplugins" ); papszSearchPath = CSLAddString( papszSearchPath, szPluginDir ); } else { papszSearchPath = CSLAddString( papszSearchPath, "/usr/local/lib/gdalplugins" ); } #endif #ifdef MACOSX_FRAMEWORK #define num2str(x) str(x) #define str(x) #x papszSearchPath = CSLAddString( papszSearchPath, "/Library/Application Support/GDAL/" num2str(GDAL_VERSION_MAJOR) "." num2str(GDAL_VERSION_MINOR) "/PlugIns" ); #endif } /* -------------------------------------------------------------------- */ /* 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 ); } /************************************************************************/ /* GDALDestroyDriverManager() */ /************************************************************************/ /** * \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; }