pion-net
4.0.9
|
00001 // ----------------------------------------------------------------------- 00002 // pion-common: a collection of common libraries used by the Pion Platform 00003 // ----------------------------------------------------------------------- 00004 // Copyright (C) 2007-2008 Atomic Labs, Inc. (http://www.atomiclabs.com) 00005 // 00006 // Distributed under the Boost Software License, Version 1.0. 00007 // See http://www.boost.org/LICENSE_1_0.txt 00008 // 00009 00010 #include <boost/filesystem.hpp> 00011 #include <boost/filesystem/operations.hpp> 00012 #include <boost/thread/mutex.hpp> 00013 #include <pion/PionConfig.hpp> 00014 #include <pion/PionPlugin.hpp> 00015 00016 #ifdef PION_WIN32 00017 #include <windows.h> 00018 #else 00019 #include <dlfcn.h> 00020 #endif 00021 00022 00023 namespace pion { // begin namespace pion 00024 00025 // static members of PionPlugin 00026 00027 const std::string PionPlugin::PION_PLUGIN_CREATE("pion_create_"); 00028 const std::string PionPlugin::PION_PLUGIN_DESTROY("pion_destroy_"); 00029 #ifdef PION_WIN32 00030 const std::string PionPlugin::PION_PLUGIN_EXTENSION(".dll"); 00031 #else 00032 const std::string PionPlugin::PION_PLUGIN_EXTENSION(".so"); 00033 #endif 00034 const std::string PionPlugin::PION_CONFIG_EXTENSION(".conf"); 00035 std::vector<std::string> PionPlugin::m_plugin_dirs; 00036 PionPlugin::PluginMap PionPlugin::m_plugin_map; 00037 boost::mutex PionPlugin::m_plugin_mutex; 00038 PionPlugin::StaticEntryPointList *PionPlugin::m_entry_points_ptr = NULL; 00039 00040 00041 // PionPlugin member functions 00042 00043 void PionPlugin::checkCygwinPath(boost::filesystem::path& final_path, 00044 const std::string& start_path) 00045 { 00046 #if defined(PION_WIN32) && defined(PION_CYGWIN_DIRECTORY) 00047 // try prepending PION_CYGWIN_DIRECTORY if not complete 00048 if (! final_path.is_complete() && final_path.has_root_directory()) { 00049 final_path = boost::filesystem::path(std::string(PION_CYGWIN_DIRECTORY) + start_path); 00050 } 00051 #endif 00052 } 00053 00054 void PionPlugin::addPluginDirectory(const std::string& dir) 00055 { 00056 boost::filesystem::path plugin_path = boost::filesystem::system_complete(dir); 00057 checkCygwinPath(plugin_path, dir); 00058 if (! boost::filesystem::exists(plugin_path) ) 00059 throw DirectoryNotFoundException(dir); 00060 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00061 m_plugin_dirs.push_back(plugin_path.string()); 00062 } 00063 00064 void PionPlugin::resetPluginDirectories(void) 00065 { 00066 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00067 m_plugin_dirs.clear(); 00068 } 00069 00070 void PionPlugin::open(const std::string& plugin_name) 00071 { 00072 std::string plugin_file; 00073 00074 if (!findPluginFile(plugin_file, plugin_name)) 00075 throw PluginNotFoundException(plugin_name); 00076 00077 openFile(plugin_file); 00078 } 00079 00080 void PionPlugin::openFile(const std::string& plugin_file) 00081 { 00082 releaseData(); // make sure we're not already pointing to something 00083 00084 // use a temporary object first since openPlugin() may throw 00085 PionPluginData plugin_data(getPluginName(plugin_file)); 00086 00087 // check to see if we already have a matching shared library 00088 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00089 PluginMap::iterator itr = m_plugin_map.find(plugin_data.m_plugin_name); 00090 if (itr == m_plugin_map.end()) { 00091 // no plug-ins found with the same name 00092 00093 // open up the shared library using our temporary data object 00094 openPlugin(plugin_file, plugin_data); // may throw 00095 00096 // all is good -> insert it into the plug-in map 00097 m_plugin_data = new PionPluginData(plugin_data); 00098 m_plugin_map.insert( std::make_pair(m_plugin_data->m_plugin_name, 00099 m_plugin_data) ); 00100 } else { 00101 // found an existing plug-in with the same name 00102 m_plugin_data = itr->second; 00103 } 00104 00105 // increment the number of references 00106 ++ m_plugin_data->m_references; 00107 } 00108 00109 void PionPlugin::openStaticLinked(const std::string& plugin_name, 00110 void *create_func, 00111 void *destroy_func) 00112 { 00113 releaseData(); // make sure we're not already pointing to something 00114 00115 // check to see if we already have a matching shared library 00116 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00117 PluginMap::iterator itr = m_plugin_map.find(plugin_name); 00118 if (itr == m_plugin_map.end()) { 00119 // no plug-ins found with the same name 00120 00121 // all is good -> insert it into the plug-in map 00122 m_plugin_data = new PionPluginData(plugin_name); 00123 m_plugin_data->m_lib_handle = NULL; // this will indicate that we are using statically linked plug-in 00124 m_plugin_data->m_create_func = create_func; 00125 m_plugin_data->m_destroy_func = destroy_func; 00126 m_plugin_map.insert(std::make_pair(m_plugin_data->m_plugin_name, 00127 m_plugin_data)); 00128 } else { 00129 // found an existing plug-in with the same name 00130 m_plugin_data = itr->second; 00131 } 00132 00133 // increment the number of references 00134 ++ m_plugin_data->m_references; 00135 } 00136 00137 void PionPlugin::releaseData(void) 00138 { 00139 if (m_plugin_data != NULL) { 00140 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00141 // double-check after locking mutex 00142 if (m_plugin_data != NULL && --m_plugin_data->m_references == 0) { 00143 // no more references to the plug-in library 00144 00145 // release the shared object 00146 closeDynamicLibrary(m_plugin_data->m_lib_handle); 00147 00148 // remove it from the plug-in map 00149 PluginMap::iterator itr = m_plugin_map.find(m_plugin_data->m_plugin_name); 00150 // check itr just to be safe (it SHOULD always find a match) 00151 if (itr != m_plugin_map.end()) 00152 m_plugin_map.erase(itr); 00153 00154 // release the heap object 00155 delete m_plugin_data; 00156 } 00157 m_plugin_data = NULL; 00158 } 00159 } 00160 00161 void PionPlugin::grabData(const PionPlugin& p) 00162 { 00163 releaseData(); // make sure we're not already pointing to something 00164 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00165 m_plugin_data = const_cast<PionPluginData*>(p.m_plugin_data); 00166 if (m_plugin_data != NULL) { 00167 ++ m_plugin_data->m_references; 00168 } 00169 } 00170 00171 bool PionPlugin::findFile(std::string& path_to_file, const std::string& name, 00172 const std::string& extension) 00173 { 00174 // first, try the name as-is 00175 if (checkForFile(path_to_file, name, "", extension)) 00176 return true; 00177 00178 // nope, check search paths 00179 boost::mutex::scoped_lock plugin_lock(m_plugin_mutex); 00180 for (std::vector<std::string>::iterator i = m_plugin_dirs.begin(); 00181 i != m_plugin_dirs.end(); ++i) 00182 { 00183 if (checkForFile(path_to_file, *i, name, extension)) 00184 return true; 00185 } 00186 00187 // no plug-in file found 00188 return false; 00189 } 00190 00191 bool PionPlugin::checkForFile(std::string& final_path, const std::string& start_path, 00192 const std::string& name, const std::string& extension) 00193 { 00194 // check for cygwin path oddities 00195 boost::filesystem::path cygwin_safe_path(start_path); 00196 checkCygwinPath(cygwin_safe_path, start_path); 00197 boost::filesystem::path test_path(cygwin_safe_path); 00198 00199 // if a name is specified, append it to the test path 00200 if (! name.empty()) 00201 test_path /= name; 00202 00203 // check for existence of file (without extension) 00204 try { 00205 // is_regular may throw if directory is not readable 00206 if (boost::filesystem::is_regular(test_path)) { 00207 final_path = test_path.string(); 00208 return true; 00209 } 00210 } catch (...) {} 00211 00212 // next, try appending the extension 00213 if (name.empty()) { 00214 // no "name" specified -> append it directly to start_path 00215 test_path = boost::filesystem::path(start_path + extension); 00216 // in this case, we need to re-check for the cygwin oddities 00217 checkCygwinPath(test_path, start_path + extension); 00218 } else { 00219 // name is specified, so we can just re-use cygwin_safe_path 00220 test_path = cygwin_safe_path / 00221 boost::filesystem::path(name + extension); 00222 } 00223 00224 // re-check for existence of file (after adding extension) 00225 try { 00226 // is_regular may throw if directory is not readable 00227 if (boost::filesystem::is_regular(test_path)) { 00228 final_path = test_path.string(); 00229 return true; 00230 } 00231 } catch (...) {} 00232 00233 // no plug-in file found 00234 return false; 00235 } 00236 00237 void PionPlugin::openPlugin(const std::string& plugin_file, 00238 PionPluginData& plugin_data) 00239 { 00240 // get the name of the plugin (for create/destroy symbol names) 00241 plugin_data.m_plugin_name = getPluginName(plugin_file); 00242 00243 // attempt to open the plugin; note that this tries all search paths 00244 // and also tries a variety of platform-specific extensions 00245 plugin_data.m_lib_handle = loadDynamicLibrary(plugin_file.c_str()); 00246 if (plugin_data.m_lib_handle == NULL) { 00247 #ifndef PION_WIN32 00248 const char *error_msg = dlerror(); 00249 if (error_msg != NULL) { 00250 std::string error_str(plugin_file); 00251 error_str += " ("; 00252 error_str += error_msg; 00253 error_str += ')'; 00254 throw OpenPluginException(error_str); 00255 } else 00256 #endif 00257 throw OpenPluginException(plugin_file); 00258 } 00259 00260 // find the function used to create new plugin objects 00261 plugin_data.m_create_func = 00262 getLibrarySymbol(plugin_data.m_lib_handle, 00263 PION_PLUGIN_CREATE + plugin_data.m_plugin_name); 00264 if (plugin_data.m_create_func == NULL) { 00265 closeDynamicLibrary(plugin_data.m_lib_handle); 00266 throw PluginMissingCreateException(plugin_file); 00267 } 00268 00269 // find the function used to destroy existing plugin objects 00270 plugin_data.m_destroy_func = 00271 getLibrarySymbol(plugin_data.m_lib_handle, 00272 PION_PLUGIN_DESTROY + plugin_data.m_plugin_name); 00273 if (plugin_data.m_destroy_func == NULL) { 00274 closeDynamicLibrary(plugin_data.m_lib_handle); 00275 throw PluginMissingDestroyException(plugin_file); 00276 } 00277 } 00278 00279 std::string PionPlugin::getPluginName(const std::string& plugin_file) 00280 { 00281 return boost::filesystem::basename(boost::filesystem::path(plugin_file)); 00282 } 00283 00284 void PionPlugin::getAllPluginNames(std::vector<std::string>& plugin_names) 00285 { 00286 // Iterate through all the Plugin directories. 00287 std::vector<std::string>::iterator it; 00288 for (it = m_plugin_dirs.begin(); it != m_plugin_dirs.end(); ++it) { 00289 // Find all shared libraries in the directory and add them to the list of Plugin names. 00290 boost::filesystem::directory_iterator end; 00291 for (boost::filesystem::directory_iterator it2(*it); it2 != end; ++it2) { 00292 if (boost::filesystem::is_regular(*it2)) { 00293 if (boost::filesystem::extension(it2->path()) == PionPlugin::PION_PLUGIN_EXTENSION) { 00294 plugin_names.push_back(PionPlugin::getPluginName(it2->path().filename().native())); 00295 } 00296 } 00297 } 00298 } 00299 } 00300 00301 void *PionPlugin::loadDynamicLibrary(const std::string& plugin_file) 00302 { 00303 #ifdef PION_WIN32 00304 #ifdef _MSC_VER 00305 return LoadLibraryA(plugin_file.c_str()); 00306 #else 00307 return LoadLibrary(plugin_file.c_str()); 00308 #endif 00309 #else 00310 // convert into a full/absolute/complete path since dlopen() 00311 // does not always search the CWD on some operating systems 00312 const boost::filesystem::path full_path = boost::filesystem::absolute(plugin_file); 00313 // NOTE: you must load shared libraries using RTLD_GLOBAL on Unix platforms 00314 // due to a bug in GCC (or Boost::any, depending on which crowd you want to believe). 00315 // see: http://svn.boost.org/trac/boost/ticket/754 00316 return dlopen(full_path.string().c_str(), RTLD_LAZY | RTLD_GLOBAL); 00317 #endif 00318 } 00319 00320 void PionPlugin::closeDynamicLibrary(void *lib_handle) 00321 { 00322 #ifdef PION_WIN32 00323 // Apparently, FreeLibrary sometimes causes crashes when running 00324 // pion-net-unit-tests under Windows. 00325 // It's hard to pin down, because many things can suppress the crashes, 00326 // such as enabling logging or setting breakpoints (i.e. things that 00327 // might help pin it down.) Also, it's very intermittent, and can be 00328 // strongly affected by other processes that are running. 00329 // So, please don't call FreeLibrary here unless you've been able to 00330 // reproduce and fix the crashing of the unit tests. 00331 00332 //FreeLibrary((HINSTANCE) lib_handle); 00333 #else 00334 dlclose(lib_handle); 00335 #endif 00336 } 00337 00338 void *PionPlugin::getLibrarySymbol(void *lib_handle, const std::string& symbol) 00339 { 00340 #ifdef PION_WIN32 00341 return (void*)GetProcAddress((HINSTANCE) lib_handle, symbol.c_str()); 00342 #else 00343 return dlsym(lib_handle, symbol.c_str()); 00344 #endif 00345 } 00346 00347 bool PionPlugin::findStaticEntryPoint(const std::string& plugin_name, 00348 void **create_func, 00349 void **destroy_func) 00350 { 00351 // check simple case first: no entry points exist 00352 if (m_entry_points_ptr == NULL || m_entry_points_ptr->empty()) 00353 return false; 00354 00355 // try to find the entry point for the plugin 00356 for (std::list<StaticEntryPoint>::const_iterator i = m_entry_points_ptr->begin(); 00357 i != m_entry_points_ptr->end(); ++i) { 00358 if (i->m_plugin_name==plugin_name) { 00359 *create_func = i->m_create_func; 00360 *destroy_func = i->m_destroy_func; 00361 return true; 00362 } 00363 } 00364 return false; 00365 } 00366 00367 void PionPlugin::addStaticEntryPoint(const std::string& plugin_name, 00368 void *create_func, 00369 void *destroy_func) 00370 { 00371 // make sure that this function can only be called by one thread at a time 00372 static boost::mutex entrypoint_mutex; 00373 boost::mutex::scoped_lock entrypoint_lock(entrypoint_mutex); 00374 00375 // create the entry point list if it doesn't already exist 00376 if (m_entry_points_ptr == NULL) 00377 m_entry_points_ptr = new StaticEntryPointList; 00378 00379 // insert it into the entry point list 00380 m_entry_points_ptr->push_back(StaticEntryPoint(plugin_name, create_func, destroy_func)); 00381 } 00382 00383 } // end namespace pion