Skip to content

Instantly share code, notes, and snippets.

@jemand2001
Last active May 5, 2025 09:21
Show Gist options
  • Select an option

  • Save jemand2001/3082f4e40e485b4e98d6e933549bc47f to your computer and use it in GitHub Desktop.

Select an option

Save jemand2001/3082f4e40e485b4e98d6e933549bc47f to your computer and use it in GitHub Desktop.
late binding dynamic libraries
#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