1 // Copyright 2014 Renato Tegon Forti, Antony Polukhin.
2 // Copyright 2015-2019 Antony Polukhin.
3 //
4 // Distributed under the Boost Software License, Version 1.0.
5 // (See accompanying file LICENSE_1_0.txt
6 // or copy at http://www.boost.org/LICENSE_1_0.txt)
7 
8 #ifndef BOOST_DLL_SHARED_LIBRARY_HPP
9 #define BOOST_DLL_SHARED_LIBRARY_HPP
10 
11 /// \file boost/dll/shared_library.hpp
12 /// \brief Contains the boost::dll::shared_library class, core class for all the
13 /// DLL/DSO operations.
14 
15 #include <boost/dll/config.hpp>
16 #include <boost/predef/os.h>
17 #include <boost/core/enable_if.hpp>
18 #include <boost/core/explicit_operator_bool.hpp>
19 #include <boost/type_traits/is_member_pointer.hpp>
20 #include <boost/dll/detail/system_error.hpp>
21 #include <boost/dll/detail/aggressive_ptr_cast.hpp>
22 
23 #if BOOST_OS_WINDOWS
24 #   include <boost/dll/detail/windows/shared_library_impl.hpp>
25 #else
26 #   include <boost/dll/detail/posix/shared_library_impl.hpp>
27 #endif
28 
29 #ifdef BOOST_HAS_PRAGMA_ONCE
30 # pragma once
31 #endif
32 
33 namespace boost { namespace dll {
34 
35 /*!
36 * \brief This class can be used to load a
37 *        Dynamic link libraries (DLL's) or Shared Libraries, also know
38 *        as dynamic shared objects (DSO's) and get their exported
39 *        symbols (functions and variables).
40 *
41 * shared_library instances share reference count to an actual loaded DLL/DSO, so it
42 * is safe and memory efficient to have multiple instances of shared_library referencing the same DLL/DSO
43 * even if those instances were loaded using different paths (relative + absolute) referencing the same object.
44 *
45 * On Linux/POSIX link with library "dl". "-fvisibility=hidden" flag is also recommended for use on Linux/POSIX.
46 */
47 class shared_library
48 /// @cond
49     : private boost::dll::detail::shared_library_impl
50 /// @endcond
51 {
52     typedef boost::dll::detail::shared_library_impl base_t;
53     BOOST_COPYABLE_AND_MOVABLE(shared_library)
54 
55 public:
56 #ifdef BOOST_DLL_DOXYGEN
57     typedef platform_specific native_handle_t;
58 #else
59     typedef shared_library_impl::native_handle_t native_handle_t;
60 #endif
61 
62     /*!
63     * Creates in anstance that does not reference any DLL/DSO.
64     *
65     * \post this->is_loaded() returns false.
66     * \throw Nothing.
67     */
shared_library()68     shared_library() BOOST_NOEXCEPT {}
69 
70     /*!
71     * Copy constructor that increments the reference count of an underlying shared library.
72     * Same as calling constructor with `lib.location()` parameter.
73     *
74     * \param lib A library to copy.
75     * \post lib == *this
76     * \throw \forcedlinkfs{system_error}, std::bad_alloc in case of insufficient memory.
77     */
shared_library(const shared_library & lib)78     shared_library(const shared_library& lib)
79         : base_t()
80     {
81         assign(lib);
82     }
83 
84     /*!
85     * Copy constructor that increments the reference count of an underlying shared library.
86     * Same as calling constructor with `lib.location(), ec` parameters.
87     *
88     * \param lib A shared library to copy.
89     * \param ec Variable that will be set to the result of the operation.
90     * \post lib == *this
91     * \throw std::bad_alloc in case of insufficient memory.
92     */
shared_library(const shared_library & lib,boost::dll::fs::error_code & ec)93     shared_library(const shared_library& lib, boost::dll::fs::error_code& ec)
94         : base_t()
95     {
96         assign(lib, ec);
97     }
98 
99     /*!
100     * Move constructor. Does not invalidate existing symbols and functions loaded from lib.
101     *
102     * \param lib A shared library to move from.
103     * \post lib.is_loaded() returns false, this->is_loaded() return true.
104     * \throw Nothing.
105     */
shared_library(BOOST_RV_REF (shared_library)lib)106     shared_library(BOOST_RV_REF(shared_library) lib) BOOST_NOEXCEPT
107         : base_t(boost::move(static_cast<base_t&>(lib)))
108     {}
109 
110     /*!
111     * Loads a library by specified path with a specified mode.
112     *
113     * \param lib_path Library file name. Can handle std::string, const char*, std::wstring,
114     *           const wchar_t* or \forcedlinkfs{path}.
115     * \param mode A mode that will be used on library load.
116     * \throw \forcedlinkfs{system_error}, std::bad_alloc in case of insufficient memory.
117     */
shared_library(const boost::dll::fs::path & lib_path,load_mode::type mode=load_mode::default_mode)118     explicit shared_library(const boost::dll::fs::path& lib_path, load_mode::type mode = load_mode::default_mode) {
119         shared_library::load(lib_path, mode);
120     }
121 
122     /*!
123     * Loads a library by specified path with a specified mode.
124     *
125     * \param lib_path Library file name. Can handle std::string, const char*, std::wstring,
126     *           const wchar_t* or \forcedlinkfs{path}.
127     * \param mode A mode that will be used on library load.
128     * \param ec Variable that will be set to the result of the operation.
129     * \throw std::bad_alloc in case of insufficient memory.
130     */
shared_library(const boost::dll::fs::path & lib_path,boost::dll::fs::error_code & ec,load_mode::type mode=load_mode::default_mode)131     shared_library(const boost::dll::fs::path& lib_path, boost::dll::fs::error_code& ec, load_mode::type mode = load_mode::default_mode) {
132         shared_library::load(lib_path, mode, ec);
133     }
134 
135     //! \overload shared_library(const boost::dll::fs::path& lib_path, boost::dll::fs::error_code& ec, load_mode::type mode = load_mode::default_mode)
shared_library(const boost::dll::fs::path & lib_path,load_mode::type mode,boost::dll::fs::error_code & ec)136     shared_library(const boost::dll::fs::path& lib_path, load_mode::type mode, boost::dll::fs::error_code& ec) {
137         shared_library::load(lib_path, mode, ec);
138     }
139 
140     /*!
141     * Assignment operator. If this->is_loaded() then calls this->unload(). Does not invalidate existing symbols and functions loaded from lib.
142     *
143     * \param lib A shared library to assign from.
144     * \post lib == *this
145     * \throw \forcedlinkfs{system_error}, std::bad_alloc in case of insufficient memory.
146     */
operator =(BOOST_COPY_ASSIGN_REF (shared_library)lib)147     shared_library& operator=(BOOST_COPY_ASSIGN_REF(shared_library) lib) {
148         boost::dll::fs::error_code ec;
149         assign(lib, ec);
150         if (ec) {
151             boost::dll::detail::report_error(ec, "boost::dll::shared_library::operator= failed");
152         }
153 
154         return *this;
155     }
156 
157     /*!
158     * Move assignment operator. If this->is_loaded() then calls this->unload(). Does not invalidate existing symbols and functions loaded from lib.
159     *
160     * \param lib A library to move from.
161     * \post lib.is_loaded() returns false.
162     * \throw Nothing.
163     */
operator =(BOOST_RV_REF (shared_library)lib)164     shared_library& operator=(BOOST_RV_REF(shared_library) lib) BOOST_NOEXCEPT {
165         if (lib.native() != native()) {
166             swap(lib);
167         }
168 
169         return *this;
170     }
171 
172     /*!
173     * Destroys the object by calling `unload()`. If library was loaded multiple times
174     * by different instances, the actual DLL/DSO won't be unloaded until
175     * there is at least one instance that references the DLL/DSO.
176     *
177     * \throw Nothing.
178     */
~shared_library()179     ~shared_library() BOOST_NOEXCEPT {}
180 
181     /*!
182     * Makes *this share the same shared object as lib. If *this is loaded, then unloads it.
183     *
184     * \post lib.location() == this->location(), lib == *this
185     * \param lib A library to copy.
186     * \param ec Variable that will be set to the result of the operation.
187     * \throw std::bad_alloc in case of insufficient memory.
188     */
assign(const shared_library & lib,boost::dll::fs::error_code & ec)189     shared_library& assign(const shared_library& lib, boost::dll::fs::error_code& ec) {
190         ec.clear();
191 
192         if (native() == lib.native()) {
193             return *this;
194         }
195 
196         if (!lib) {
197             unload();
198             return *this;
199         }
200 
201         boost::dll::fs::path loc = lib.location(ec);
202         if (ec) {
203             return *this;
204         }
205 
206         shared_library copy(loc, ec);
207         if (ec) {
208             return *this;
209         }
210 
211         swap(copy);
212         return *this;
213     }
214 
215     /*!
216     * Makes *this share the same shared object as lib. If *this is loaded, then unloads it.
217     *
218     * \param lib A library instance to assign from.
219     * \post lib.location() == this->location()
220     * \throw \forcedlinkfs{system_error}, std::bad_alloc in case of insufficient memory.
221     */
assign(const shared_library & lib)222     shared_library& assign(const shared_library& lib) {
223         boost::dll::fs::error_code ec;
224         assign(lib, ec);
225         if (ec) {
226             boost::dll::detail::report_error(ec, "boost::dll::shared_library::assign() failed");
227         }
228 
229         return *this;
230     }
231 
232     /*!
233     * Loads a library by specified path with a specified mode.
234     *
235     * Note that if some library is already loaded in this instance, load will
236     * call unload() and then load the new provided library.
237     *
238     * \param lib_path Library file name. Can handle std::string, const char*, std::wstring,
239     *           const wchar_t* or \forcedlinkfs{path}.
240     * \param mode A mode that will be used on library load.
241     * \throw \forcedlinkfs{system_error}, std::bad_alloc in case of insufficient memory.
242     *
243     */
load(const boost::dll::fs::path & lib_path,load_mode::type mode=load_mode::default_mode)244     void load(const boost::dll::fs::path& lib_path, load_mode::type mode = load_mode::default_mode) {
245         boost::dll::fs::error_code ec;
246 
247         base_t::load(lib_path, mode, ec);
248 
249         if (ec) {
250             boost::dll::detail::report_error(ec, "boost::dll::shared_library::load() failed");
251         }
252     }
253 
254     /*!
255     * Loads a library by specified path with a specified mode.
256     *
257     * Note that if some library is already loaded in this instance, load will
258     * call unload() and then load the new provided library.
259     *
260     * \param lib_path Library file name. Can handle std::string, const char*, std::wstring,
261     *           const wchar_t* or \forcedlinkfs{path}.
262     * \param ec Variable that will be set to the result of the operation.
263     * \param mode A mode that will be used on library load.
264     * \throw std::bad_alloc in case of insufficient memory.
265     */
load(const boost::dll::fs::path & lib_path,boost::dll::fs::error_code & ec,load_mode::type mode=load_mode::default_mode)266     void load(const boost::dll::fs::path& lib_path, boost::dll::fs::error_code& ec, load_mode::type mode = load_mode::default_mode) {
267         ec.clear();
268         base_t::load(lib_path, mode, ec);
269     }
270 
271     //! \overload void load(const boost::dll::fs::path& lib_path, boost::dll::fs::error_code& ec, load_mode::type mode = load_mode::default_mode)
load(const boost::dll::fs::path & lib_path,load_mode::type mode,boost::dll::fs::error_code & ec)272     void load(const boost::dll::fs::path& lib_path, load_mode::type mode, boost::dll::fs::error_code& ec) {
273         ec.clear();
274         base_t::load(lib_path, mode, ec);
275     }
276 
277     /*!
278     * Unloads a shared library.  If library was loaded multiple times
279     * by different instances, the actual DLL/DSO won't be unloaded until
280     * there is at least one instance that references the DLL/DSO.
281     *
282     * \post this->is_loaded() returns false.
283     * \throw Nothing.
284     */
unload()285     void unload() BOOST_NOEXCEPT {
286         base_t::unload();
287     }
288 
289     /*!
290     * Check if an library is loaded.
291     *
292     * \return true if a library has been loaded.
293     * \throw Nothing.
294     */
is_loaded() const295     bool is_loaded() const BOOST_NOEXCEPT {
296         return base_t::is_loaded();
297     }
298 
299     /*!
300     * Check if an library is not loaded.
301     *
302     * \return true if a library has not been loaded.
303     * \throw Nothing.
304     */
operator !() const305     bool operator!() const BOOST_NOEXCEPT {
306         return !is_loaded();
307     }
308 
309     /*!
310     * Check if an library is loaded.
311     *
312     * \return true if a library has been loaded.
313     * \throw Nothing.
314     */
BOOST_EXPLICIT_OPERATOR_BOOL()315     BOOST_EXPLICIT_OPERATOR_BOOL()
316 
317     /*!
318     * Search for a given symbol on loaded library. Works for all symbols, including alias names.
319     *
320     * \param symbol_name Null-terminated symbol name. Can handle std::string, char*, const char*.
321     * \return `true` if the loaded library contains a symbol with a given name.
322     * \throw Nothing.
323     */
324     bool has(const char* symbol_name) const BOOST_NOEXCEPT {
325         boost::dll::fs::error_code ec;
326         return is_loaded() && !!base_t::symbol_addr(symbol_name, ec) && !ec;
327     }
328 
329     //! \overload bool has(const char* symbol_name) const
has(const std::string & symbol_name) const330     bool has(const std::string& symbol_name) const BOOST_NOEXCEPT {
331         return has(symbol_name.c_str());
332     }
333 
334     /*!
335     * Returns reference to the symbol (function or variable) with the given name from the loaded library.
336     * This call will always succeed and throw nothing if call to `has(const char* )`
337     * member function with the same symbol name returned `true`.
338     *
339     * \b Example:
340     * \code
341     * int& i0 = lib.get<int>("integer_name");
342     * int& i1 = *lib.get<int*>("integer_alias_name");
343     * \endcode
344     *
345     * \tparam T Type of the symbol that we are going to import. Must be explicitly specified.
346     * \param symbol_name Null-terminated symbol name. Can handle std::string, char*, const char*.
347     * \return Reference to the symbol.
348     * \throw \forcedlinkfs{system_error} if symbol does not exist or if the DLL/DSO was not loaded.
349     */
350     template <typename T>
get(const std::string & symbol_name) const351     inline typename boost::enable_if_c<boost::is_member_pointer<T>::value || boost::is_reference<T>::value, T>::type  get(const std::string& symbol_name) const {
352         return get<T>(symbol_name.c_str());
353     }
354 
355     //! \overload T& get(const std::string& symbol_name) const
356     template <typename T>
get(const std::string & symbol_name) const357     inline typename boost::disable_if_c<boost::is_member_pointer<T>::value || boost::is_reference<T>::value, T&>::type get(const std::string& symbol_name) const {
358         return get<T>(symbol_name.c_str());
359     }
360 
361     //! \overload T& get(const std::string& symbol_name) const
362     template <typename T>
get(const char * symbol_name) const363     inline typename boost::enable_if_c<boost::is_member_pointer<T>::value || boost::is_reference<T>::value, T>::type get(const char* symbol_name) const {
364         return boost::dll::detail::aggressive_ptr_cast<T>(
365             get_void(symbol_name)
366         );
367     }
368 
369     //! \overload T& get(const std::string& symbol_name) const
370     template <typename T>
get(const char * symbol_name) const371     inline typename boost::disable_if_c<boost::is_member_pointer<T>::value || boost::is_reference<T>::value, T&>::type get(const char* symbol_name) const {
372         return *boost::dll::detail::aggressive_ptr_cast<T*>(
373             get_void(symbol_name)
374         );
375     }
376 
377     /*!
378     * Returns a symbol (function or variable) from a shared library by alias name of the symbol.
379     *
380     * \b Example:
381     * \code
382     * int& i = lib.get_alias<int>("integer_alias_name");
383     * \endcode
384     *
385     * \tparam T Type of the symbol that we are going to import. Must be explicitly specified..
386     * \param alias_name Null-terminated alias symbol name. Can handle std::string, char*, const char*.
387     * \throw \forcedlinkfs{system_error} if symbol does not exist or if the DLL/DSO was not loaded.
388     */
389     template <typename T>
get_alias(const char * alias_name) const390     inline T& get_alias(const char* alias_name) const {
391         return *get<T*>(alias_name);
392     }
393 
394     //! \overload T& get_alias(const char* alias_name) const
395     template <typename T>
get_alias(const std::string & alias_name) const396     inline T& get_alias(const std::string& alias_name) const {
397         return *get<T*>(alias_name.c_str());
398     }
399 
400 private:
401     /// @cond
402     // get_void is required to reduce binary size: it does not depend on a template
403     // parameter and will be instantiated only once.
get_void(const char * sb) const404     void* get_void(const char* sb) const {
405         boost::dll::fs::error_code ec;
406 
407         if (!is_loaded()) {
408             ec = boost::dll::fs::make_error_code(
409                 boost::dll::fs::errc::bad_file_descriptor
410             );
411 
412             // report_error() calls dlsym, do not use it here!
413             boost::throw_exception(
414                 boost::dll::fs::system_error(
415                     ec, "boost::dll::shared_library::get() failed: no library was loaded"
416                 )
417             );
418         }
419 
420         void* const ret = base_t::symbol_addr(sb, ec);
421         if (ec || !ret) {
422             boost::dll::detail::report_error(ec, "boost::dll::shared_library::get() failed");
423         }
424 
425         return ret;
426     }
427     /// @endcond
428 
429 public:
430 
431     /*!
432     * Returns the native handler of the loaded library.
433     *
434     * \return Platform-specific handle.
435     */
native() const436     native_handle_t native() const BOOST_NOEXCEPT {
437         return base_t::native();
438     }
439 
440    /*!
441     * Returns full path and name of this shared object.
442     *
443     * \b Example:
444     * \code
445     * shared_library lib("test_lib.dll");
446     * filesystem::path full_path = lib.location(); // C:\Windows\System32\test_lib.dll
447     * \endcode
448     *
449     * \return Full path to the shared library.
450     * \throw \forcedlinkfs{system_error}, std::bad_alloc.
451     */
location() const452     boost::dll::fs::path location() const {
453         boost::dll::fs::error_code ec;
454         if (!is_loaded()) {
455             ec = boost::dll::fs::make_error_code(
456                 boost::dll::fs::errc::bad_file_descriptor
457             );
458 
459             boost::throw_exception(
460                 boost::dll::fs::system_error(
461                     ec, "boost::dll::shared_library::location() failed (no library was loaded)"
462                 )
463             );
464         }
465 
466         boost::dll::fs::path full_path = base_t::full_module_path(ec);
467 
468         if (ec) {
469             boost::dll::detail::report_error(ec, "boost::dll::shared_library::location() failed");
470         }
471 
472         return full_path;
473     }
474 
475    /*!
476     * Returns full path and name of shared module.
477     *
478     * \b Example:
479     * \code
480     * shared_library lib("test_lib.dll");
481     * filesystem::path full_path = lib.location(); // C:\Windows\System32\test_lib.dll
482     * \endcode
483     *
484     * \param ec Variable that will be set to the result of the operation.
485     * \return Full path to the shared library.
486     * \throw std::bad_alloc.
487     */
location(boost::dll::fs::error_code & ec) const488     boost::dll::fs::path location(boost::dll::fs::error_code& ec) const {
489         if (!is_loaded()) {
490             ec = boost::dll::fs::make_error_code(
491                 boost::dll::fs::errc::bad_file_descriptor
492             );
493 
494             return boost::dll::fs::path();
495         }
496 
497         ec.clear();
498         return base_t::full_module_path(ec);
499     }
500 
501     /*!
502     * Returns suffix of shared module:
503     * in a call to load() or the constructor/load.
504     *
505     * \return The suffix od shared module: ".dll" (Windows), ".so" (Unix/Linux/BSD), ".dylib" (MacOS/IOS)
506     */
suffix()507     static boost::dll::fs::path suffix() {
508         return base_t::suffix();
509     }
510 
511     /*!
512     * Returns the decorated path to a shared module name, i.e. with needed prefix/suffix added.
513     *
514     * \b Recommendations: Use `load` with `load_mode::append_decorations` instead of constructing the decorated path via `decorate()` and loading by it.
515     *
516     * For instance, for a path like "path/to/boost" it returns :
517     * - path/to/libboost.so on posix platforms
518     * - path/to/libboost.dylib on OSX
519     * - path/to/boost.dll on Windows
520     *
521     * Method handles both relative and absolute paths.
522     *
523     * - Windows note: `decorate()` does not prepend "lib" to the decorated path. Use `load` with `load_mode::append_decorations` for MinGW compatibility purpose.
524     * - Posix note: if the initial module name is already prepended with lib, only the suffix() is appended to the path
525     *
526     * \param sl the module name and path to decorate - for instance : /usr/lib/boost
527     *
528     * \return The decorated unportable path that may not exists in the filesystem or could be wrong due to platform specifics.
529     */
decorate(const boost::dll::fs::path & sl)530     static boost::dll::fs::path decorate(const boost::dll::fs::path& sl) {
531         return base_t::decorate(sl);
532     }
533 
534     /*!
535     * Swaps two libraries. Does not invalidate existing symbols and functions loaded from libraries.
536     *
537     * \param rhs Library to swap with.
538     * \throw Nothing.
539     */
swap(shared_library & rhs)540     void swap(shared_library& rhs) BOOST_NOEXCEPT {
541         base_t::swap(rhs);
542     }
543 };
544 
545 
546 
547 /// Very fast equality check that compares the actual DLL/DSO objects. Throws nothing.
operator ==(const shared_library & lhs,const shared_library & rhs)548 inline bool operator==(const shared_library& lhs, const shared_library& rhs) BOOST_NOEXCEPT {
549     return lhs.native() == rhs.native();
550 }
551 
552 /// Very fast inequality check that compares the actual DLL/DSO objects. Throws nothing.
operator !=(const shared_library & lhs,const shared_library & rhs)553 inline bool operator!=(const shared_library& lhs, const shared_library& rhs) BOOST_NOEXCEPT {
554     return lhs.native() != rhs.native();
555 }
556 
557 /// Compare the actual DLL/DSO objects without any guarantee to be stable between runs. Throws nothing.
operator <(const shared_library & lhs,const shared_library & rhs)558 inline bool operator<(const shared_library& lhs, const shared_library& rhs) BOOST_NOEXCEPT {
559     return lhs.native() < rhs.native();
560 }
561 
562 /// Swaps two shared libraries. Does not invalidate symbols and functions loaded from libraries. Throws nothing.
swap(shared_library & lhs,shared_library & rhs)563 inline void swap(shared_library& lhs, shared_library& rhs) BOOST_NOEXCEPT {
564     lhs.swap(rhs);
565 }
566 
567 }} // boost::dll
568 
569 #endif // BOOST_DLL_SHARED_LIBRARY_HPP
570