Last active
May 5, 2025 09:21
-
-
Save jemand2001/3082f4e40e485b4e98d6e933549bc47f to your computer and use it in GitHub Desktop.
late binding dynamic libraries
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <dlfcn.h> | |
| #include <format> | |
| #include <memory> | |
| #include <stdexcept> | |
| #include <unordered_map> | |
| struct dl_exception : std::runtime_error { | |
| using std::runtime_error::runtime_error; | |
| }; | |
| class DynamicLibrary { | |
| void *lib; | |
| std::unordered_map<const char *, std::pair<const std::type_info *, void *>> | |
| cache; | |
| std::string name; | |
| static std::unordered_map<const char *, std::weak_ptr<DynamicLibrary>> loaded_libs; | |
| DynamicLibrary(const DynamicLibrary &) = delete; | |
| DynamicLibrary(DynamicLibrary &&) = delete; | |
| DynamicLibrary &operator=(const DynamicLibrary &) = delete; | |
| DynamicLibrary &operator=(DynamicLibrary &&) = delete; | |
| static void _throw() { throw dl_exception{dlerror()}; } | |
| static void *load(const char *filename) { | |
| if (dlopen(filename, RTLD_LAZY | RTLD_NOLOAD)) | |
| throw dl_exception{ | |
| "you should only have one reference to a shared library so " | |
| "reloading works; you already have a shared_ptr after all"}; | |
| void *ptr = dlopen(filename, RTLD_LAZY | RTLD_GLOBAL); | |
| if (!ptr) | |
| _throw(); | |
| return ptr; | |
| } | |
| static void unload(void *ptr) { | |
| if (dlclose(ptr)) | |
| _throw(); | |
| } | |
| DynamicLibrary(const char *filename) | |
| : lib{load(filename)}, name{filename} {}; | |
| public: | |
| /// @brief get a shared_ptr to a shared library given its filename | |
| /// @param name the file you want to load (on linux, generally uses the | |
| /// `.so` extension) | |
| /// @return a std::shared_ptr through which you can access the library's | |
| /// symbols | |
| [[nodiscard]] static auto get(const char *name) { | |
| if (auto ptr = loaded_libs[name].lock()) { | |
| return ptr; | |
| } | |
| auto ptr = std::shared_ptr<DynamicLibrary>(new DynamicLibrary{name}); | |
| loaded_libs.insert_or_assign(name, ptr); | |
| return ptr; | |
| } | |
| ~DynamicLibrary() { unload(lib); } | |
| /// @brief dereference a symbol from this library | |
| /// @tparam R the type of the symbol to load (will be reinterpret_cast, so | |
| /// be extra sure about it) | |
| /// @param name the symbol to load | |
| /// @return the value of the symbol, dereferenced | |
| template <typename R> | |
| [[nodiscard]] R &symbol(const char *name) | |
| requires(!std::is_pointer_v<R>) | |
| { | |
| return *symbol<R *>(name); | |
| } | |
| /// @brief load a symbol from this library | |
| /// @tparam R the type of the symbol to load (will be reinterpret_cast, so | |
| /// be extra sure about it) | |
| /// @param name the symbol to load | |
| /// @return a pointer corresponding to the symbol, cast to a pointer to the | |
| /// provided type | |
| /// @note for your sanity's sake, only use this function with names that | |
| /// were declared `extern "C"` | |
| /// @overload | |
| /// @note the symbol pointers are cached. | |
| template <typename R> | |
| [[nodiscard]] R symbol(const char *name) | |
| requires(std::is_pointer_v<R>) | |
| { | |
| auto it = cache.find(name); | |
| if (it != cache.end()) { | |
| auto [key, things] = *it; | |
| auto [type, known_ptr] = things; | |
| if (type != &typeid(R)) | |
| throw dl_exception{ | |
| std::format("received different types for \"{}\"", name)}; | |
| return reinterpret_cast<R>(known_ptr); | |
| } | |
| void *ptr = dlsym(lib, name); | |
| if (!ptr) | |
| _throw(); | |
| cache.insert(it, {name, {&typeid(R), ptr}}); | |
| return reinterpret_cast<R>(ptr); | |
| } | |
| /// @brief re-load this shared library. | |
| /// @note this function is why you should only load each library once per | |
| /// program, and why the constructor throws if this is not the case: if you | |
| /// load the same library twice, and try to reload through one handle, you | |
| /// would normally not reload at all. | |
| void reload() { | |
| cache.clear(); | |
| unload(lib); | |
| load(name.c_str()); | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment