/** @file

    $Id$

    $Date$
    $Author$

    @copy © Marc Wäckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

    $Log$
    Revision 1.2  2005/02/21 16:16:47  marc
    finished and documented

    Revision 1.1  2005/02/18 15:53:56  marc
    initial release


         1         2         3         4         5         6         7         8
    5678901234567890123456789012345678901234567890123456789012345678901234567890
*/

#ifndef __MRW_DYNAMICLIBRARY_HPP__
#define __MRW_DYNAMICLIBRARY_HPP__

#include <ltdl.h>
#include <mrw/string.hpp>
#include <mrw/stdext.hpp>
#include <mrw/exception.hpp>

namespace mrw {

  /** @defgroup dll Dynamic Library Loading (ltdl)

      Implements a C++ wrapper around the Libtool's
      platform independent dynamic library loading library named
      libltdl. See <code>info&nbsp;libtool</code> for more
      information.

      Loading a symbol from a shared library:

      @code
      void fn() throw(mrw::DynamicLibrary::failure) {
        static DynamicLibrary lib("libsomething");
        static void(*theRealFunction)() =
          (void(*)())lib.symbol("theRealFunction");
        (*theRealFunction)(); // call the loaded function
      }
      @endcode

      @pre #include <mrw/dynamiclibrary.hpp>
      
      @pre Files that use this wrapper must be linked with libltdl,
      with GNU g++ on UNIX, this is link option: @c -lltdl
      */
  //@{

  //============================================================================
  /** @brief Dynamic Library loaded from the filesystem at runtime.

      This class is responsible for loading dynamic&nbsp;/ shared
      libraries from the filesystem and extract symbols, means
      functions to assign them to function pointers that can then be
      accessed.

      @copydoc dll
      */
  class DynamicLibrary {

      //............................................................... typedefs
    public:
      /// failure in access to shared libraries
      class failure: public mrw::runtime_error {
        public:
          failure(const std::string& arg) throw(std::bad_exception):
              mrw::runtime_error("DynamicLibrary failure "+arg
                                 +"; ltdl reason: "+
                                 +mrw::ifelse(lt_dlerror(), "<none>")) {
          }
      };

      //................................................................ methods
    public:

      /** @brief initialize, but open dynamic library later
          @throw failure if initialisation fails
          */
      DynamicLibrary() throw(std::exception): _opened(false) {
        if (lt_dlinit()>0)
          throw failure("cannot initialize dynamic library loader");
        _opened = true;
      }

      /** @brief load a dynamic library from a file

          It is not necessary to specify the extension, the system
          dependant shared object&nbsp;/ dynamic library extension is
          automatically added. Also, if no path is given, the library
          is automatically looked for in your system's library
          paths. You can also specify additional paths to look in,
          e.g. with addSearchDir.

          @param lib the name of the library without the extension
          @throw failure if initialisation fails
          @throw failure if opeing the library fails
          */
      DynamicLibrary(const std::string& lib) throw(std::exception):
          _opened(false) {
        if (lt_dlinit()>0)
          throw failure("cannot initialize dynamic library loader");
        _opened = true;
        _lib.open(lib);
      }

      /// close and clean up
      ~DynamicLibrary() throw() {
        try {close();} catch (...) {}
        if (_opened) lt_dlexit();
      }
      
      /** @brief load a dynamic library from a file

          It is not necessary to specify the extension, the system
          dependant shared object&nbsp;/ dynamic library extension is
          automatically added. Also, if no path is given, the library
          is automatically looked for in your system's library
          paths. You can also specify additional paths to look in,
          e.g. with addSearchDir.

          @param lib the name of the library without the extension
          @throw failure if opeing the library fails
          */
      DynamicLibrary& open(const std::string& lib) throw(std::exception) {
        _lib.open(lib);
        return *this;
      }

      /** @brief close the dynamic library
          @throw failure if the library was open and close failed
          */
      DynamicLibrary& close() throw(std::exception) {
        if (!isResident()) _lib.close();
        return *this;
      }

      /** @brief make the library resident

          Makes the library resident, so that it cannot be
          closed. This can be useful if a module implements some core
          functionality in your project, which would cause your code
          to crash if removed.
          
          @throw failure in case of an error
          */
      DynamicLibrary& resident() throw(std::exception) {
        if (lt_dlmakeresident(_lib.handle())!=0)
          throw failure("cannot make library resident");
        return *this;
      }

      /** @brief check whether the library is resident
          @throw failure if the library is not open */
      bool isResident() throw(std::exception) {
        return lt_dlisresident(_lib.handle())==1;
      }

      /** @brief load a symbol from the library

          @warning The symbol is returned as void* and must be
          converted to the correct function pointer. If this
          conversion is wrong, then your process will crash, or even
          worse, behave in an unexpected way, so be careful!
          */
      lt_ptr symbol(const std::string& name) throw(std::exception) {
        lt_ptr sym(lt_dlsym(_lib.handle(), name.c_str()));
        if (!sym)
          throw failure("cannot load dynamic library symbol: "+name);
        return sym;
      }
      
      /** register a list of preloaded modules

          @code
          const lt_dlsymlist symbols[] = {
            {"symbol1", &fnptr1}, {"symbol2", &fnptr2}, 0
          }
          lib.preload(symbols);
          @endcode
          
          @param symbols a null terminated C array of structs of name
          character pointer and function pointer
          @note if symbols is @c 0, all previousely registered
          symbols except the list set by preloadDefault are deleted
          */
      DynamicLibrary& preload(const lt_dlsymlist symbols[])
          throw(std::exception) {
        if (lt_dlpreload(symbols)!=0)
          throw failure("error in preloading libraries");
        return *this;
      }
      
      /** register the default list of preloaded modules
          
          @code
          const lt_dlsymlist symbols[] = {
            {"symbol1", &fnptr1}, {"symbol2", &fnptr2}, 0
          }
          lib.preloadDefault(symbols);
          @endcode
          
          @param symbols a null terminated C array of structs of name
                         character pointer and function pointer
          */
      static void preloadDefault(const lt_dlsymlist symbols[])
          throw(std::exception) {
        if (lt_dlpreload_default(symbols)!=0)
          throw failure("error in preloading libraries");
      }
      
      /** @brief set the default list of preloaded symbols */
      static void preloadedSymbols() throw(std::bad_exception) {
        LTDL_SET_PRELOADED_SYMBOLS();
      }

      /** @brief append a directory to the library search path */
      static void addSearchDir(const std::string& dir) throw(std::exception) {
        if (lt_dladdsearchdir(dir.c_str())!=0)
          throw failure("cannot add search dir: "+dir);
      }
      
      /** @brief insert a directory at a given position to the library
          search path */
      static void insertSearchDir(const std::string& before,
                                  const std::string& dir)
          throw(std::exception) {
        if (lt_dlinsertsearchdir(before.c_str(), dir.c_str())!=0)
          throw failure("cannot add search dir: "+dir);
      }

      /** @brief set the library search path

          The search path is a colon separated list of paths. */
      static void setSearchPath(const std::string& dirs) throw() {
        if (lt_dlsetsearchpath(dirs.c_str()))
          throw failure("cannot set search path to "+dirs);
      }
      
      /** @brief get the library search path */
      static std::string getSearchPath() throw() {
        char const * const path(lt_dlgetsearchpath());
        if (!path)
          throw failure("cannot get search path");
        return path;
      }

      /** @brief calls a callback function for all directories in a
          search path

          In some applications you may not want to load individual
          modules with known names, but rather find all of the modules
          in a set of directories and load them all during
          initialisation. With this function you can have
          DynamicLibrary scan the colon delimited directory list in
          searchPath for candidates, and pass them, along with data to
          your own callback function, function. If searchPath is @c 0,
          then search all of the standard locations that open would
          examine.  This function will continue to make calls to
          function for each file that it discovers in searchPath until
          one of these calls returns non-zero, or until the files are
          exhausted.

          @return value returned by the last call made to function
          */
      int foreachfile(const char* searchPath,
                      int(*function)(const char*, lt_ptr),
                      lt_ptr data) throw() {
        return lt_dlforeachfile(searchPath, function, data);
      }
      
    private:
      class Lib {
          Lib() throw(): _lib(0) {}
          Lib(const std::string& lib) throw(std::exception): _lib(0) {
            open(lib);
          }
          ~Lib() throw() {
            try {close();} catch(...) {}
          }
          operator bool() throw() {
            return _lib;
          }
          Lib& open(const std::string& lib) throw(std::exception) {
            close();
            if (!(_lib = lt_dlopenext(lib.c_str())))
              throw failure("cannot open dynamic library "+lib);
            return *this;
          }
          Lib& close() throw(std::exception) {
            if (_lib && lt_dlclose(_lib)!=0)
              throw failure("cannot close dynamic library");
            _lib = 0;
            return *this;
          }
        private:
          friend class DynamicLibrary;
          lt_dlhandle handle() throw(std::exception) {
            if (!_lib)
              throw failure("library not open");
            return _lib;
          }
        private:
          lt_dlhandle _lib;
      };
    private:
      bool _opened;
      Lib _lib;
  };

  //@}
}

#endif