view modules/dynlib/dynlib.hpp @ 566:72390061044d

Net: improve documentation a bit
author David Demelier <markand@malikania.fr>
date Fri, 24 Jun 2016 13:00:35 +0200
parents 5725e2d2ab4c
children 943d07fbe352
line wrap: on
line source

/*
 * dynlib.hpp -- portable shared library loader
 *
 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#ifndef DYNLIB_HPP
#define DYNLIB_HPP

/**
 * \file dynlib.hpp
 * \brief Portable shared library loader.
 * \author David Demelier <markand@malikania.fr>
 */

/**
 * \page Dynlib Dynlib
 * \brief Portable shared library loader.
 *
 * The dynlib module let you open shared libraries dynamically at runtime.
 *
 * ## Operating system support
 *
 * | System  | Support | Remarks            |
 * |---------|---------|--------------------|
 * | Apple   | Ok      |                    |
 * | FreeBSD | Ok      |                    |
 * | Linux   | Ok      | Needs -ldl library |
 * | Windows | Ok      |                    |
 *
 * ## How to export symbols
 *
 * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if
 * not they will be [mangled][name-mangling].
 *
 * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function
 * overloading.
 *
 * Example of **plugin.cpp**:
 *
 * ````cpp
 * #include <iostream>
 *
 * #include "dynlib.hpp"
 *
 * extern "C" {
 *
 * DYNLIB_EXPORT void plugin_load()
 * {
 *   std::cout << "Loading plugin" << std::endl;
 * }
 *
 * DYNLIB_EXPORT void plugin_unload()
 * {
 *   std::cout << "Unloading plugin" << std::endl;
 * }
 *
 * }
 * ````
 *
 * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always
 * add it before any function.
 *
 * To compile, see your compiler documentation or build system. For gcc you can use the following:
 *
 * ````
 * gcc -std=c++14 -shared plugin.cpp -o plugin.so
 * ````
 *
 * ## How to load the library
 *
 * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure
 * that the library can be found. Otherwise, just use an absolute path to the file.
 *
 * ````cpp
 * #include <iostream>
 *
 * #include "dynlib.hpp"
 *
 * int main()
 * {
 *   try {
 *     Dynlib dso("./plugin" DYNLIB_SUFFIX);
 *   } catch (const std::exception &ex) {
 *     std::cerr << ex.what() << std::endl;
 *   }
 *
 *   return 0;
 * }
 * ````
 *
 * ## How to load symbol
 *
 * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function.
 *
 * ````cpp
 * #include <iostream>
 *
 * #include "dynlib.hpp"
 *
 * using PluginLoad = void (*)();
 * using PluginUnload = void (*)();
 *
 * int main()
 * {
 *  try {
 *      Dynlib dso("./plugin" DYNLIB_SUFFIX);
 *
 *      dso.require<PluginLoad>("plugin_load")();
 *      dso.require<PluginUnload>("plugin_unload")();
 *  } catch (const std::exception &ex) {
 *      std::cerr << ex.what() << std::endl;
 *  }
 *
 *  return 0;
 * }
 * ````
 *
 * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling
 */

#include <stdexcept>
#include <string>

#if defined(_WIN32)
#  include <Windows.h>
#else
#  include <dlfcn.h>
#endif

/**
 * \brief Export the symbol.
 *
 * This is required on some platforms and you should put it before your function signature.
 *
 * \code{.cpp}
 * extern "C" {
 *
 * DYNLIB_EXPORT void my_function()
 * {
 * }
 *
 * }
 * \endcode
 */
#if defined(_WIN32)
#  define DYNLIB_EXPORT __declspec(dllexport)
#else
#  define DYNLIB_EXPORT
#endif

/**
 * \brief Usual suffix for the library.
 *
 * This macro expands to the suffix convention for this platform.
 *
 * \code{.cpp}
 * Dynlib library("./myplugin" DYNLIB_SUFFIX);
 * \endcode
 *
 * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong.
 */
#if defined(_WIN32)
#  define DYNLIB_SUFFIX ".dll"
#elif defined(__APPLE__)
#  define DYNLIB_SUFFIX ".dylib"
#else
#  define DYNLIB_SUFFIX ".so"
#endif

/**
 * \class Dynlib
 * \brief Load a dynamic module.
 *
 * This class is a portable wrapper to load shared libraries on supported systems.
 */
class Dynlib {
private:
#if defined(_WIN32)
    using Handle        = HMODULE;
    using Sym           = FARPROC;
#else
    using Handle        = void *;
    using Sym           = void *;
#endif

public:
    /**
     * \brief Policy for symbol resolution.
     */
    enum Policy {
        Immediately,    //!< load symbols immediately
        Lazy            //!< load symbols when needed
    };

private:
    Handle  m_handle;

    Dynlib(const Dynlib &) = delete;
    Dynlib &operator=(const Dynlib &) = delete;

    Dynlib(Dynlib &&) = delete;
    Dynlib &operator=(Dynlib &&) = delete;

#if defined(_WIN32)
    std::string error()
    {
        LPSTR error = nullptr;
        std::string errmsg;

        FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            GetLastError(),
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPSTR)&error, 0, NULL);

        if (error) {
            errmsg = std::string(error);
            LocalFree(error);
        }

        return errmsg;
    }
#endif

public:
    /**
     * Constructor to load a shared module.
     *
     * \param path the absolute path
     * \param policy the policy to load
     * \throw std::runtime_error on error
     */
    Dynlib(const std::string &path, Policy policy = Immediately);

    /**
     * Close the library automatically.
     */
    ~Dynlib();

    /**
     * Get a symbol from the library.
     *
     * \param name the symbol
     * \return the symbol
     * \throw std::out_of_range on error
     * \see DYNLIB_EXPORT
     */
    template <typename T>
    inline T require(const std::string &name)
    {
        auto sym = get<T>(name);

        if (!sym)
            throw std::out_of_range(name + ": symbol not found");

        return sym;
    }

    /**
     * Get a symbol from the library or return nullptr if not found.
     *
     * \param name the symbol
     * \return the symbol
     */
    template <typename T>
    T get(const std::string &name) noexcept;
};

#if defined(_WIN32)

/*
 * Windows implementation
 * ------------------------------------------------------------------
 */

inline Dynlib::Dynlib(const std::string &path, Policy)
{
    m_handle = LoadLibraryA(path.c_str());

    if (m_handle == nullptr)
        throw std::runtime_error(error());
}

inline Dynlib::~Dynlib()
{
    FreeLibrary(m_handle);
    m_handle = nullptr;
}

template <typename T>
inline T Dynlib::get(const std::string &name) noexcept
{
    return reinterpret_cast<T>(GetProcAddress(m_handle, name.c_str()));
}

#else

/*
 * Unix implementation
 * ------------------------------------------------------------------
 */

inline Dynlib::Dynlib(const std::string &path, Policy policy)
{
    m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY);

    if (m_handle == nullptr)
        throw std::runtime_error(dlerror());
}

inline Dynlib::~Dynlib()
{
    dlclose(m_handle);
    m_handle = nullptr;
}

template <typename T>
inline T Dynlib::get(const std::string &name) noexcept
{
    return reinterpret_cast<T>(dlsym(m_handle, name.c_str()));
}

#endif

#endif // !DYNLIB_HPP