Repository navigation
Usage
First, we need to get the library we want to load. Here we will make a small example library.
Loading dynamic libraries at runtime is an operation where, at runtime, you use the OS's API to load symbols from a shared library. This library loads named functions, that is, functions whose names are not under the influence of name mangling. Because of this, your functions have to be exported with C semantics.
To create a library, simply write some C or C++ code like this:
Header:
#pragma once
extern "C"
{
void begin();
}Source file:
#include "libtest.hpp"
#include <iostream>
void begin()
{
std::cout << "begin" << std::endl;
}As you can see, the library we made is a C++ one. To compile with C semantics, you need to wrap your functions in an
extern "C" block. Otherwise, name mangling will be enabled.
Make sure that, when compiling for a Unix system, you have enabled -fPIC in your compiler flags.
This way you can have PIC (Position Independent Code).
On Windows, make sure that all the functions you want to export are prefixed with __declspec(dllexport), otherwise they
will not be exported from the DLL.
The C++ API provides some good syntactic sugar, like templates, that generally make your life better. The library looks like this:
namespace URLL
{
// Libraries are always loaded in lazy mode. A null location returns a handle to the main
// program, on Windows too, but there it only resolves symbols exported by the executable
// itself, not by the libraries it links against
void* dlopen(const char* location) noexcept;
// A null handle is not supported. On glibc it happens to search every loaded library, since
// RTLD_DEFAULT is null there, but Windows offers no cheap equivalent and simply fails
void* dlsym(void* handle, const char* name) noexcept;
template<typename T, typename... T2>
void* dlsym(void* handle, const char* name, std::function<T(T2...)>& function) noexcept;
template<typename T>
void* dlsym_var(void* handle, const char* name, T*& var) noexcept;
template<typename T>
void* dlsym_func(void* handle, const char* name, T& var) noexcept;
// returns 0 on success, everything else must be an error
int dlclose(void* handle) noexcept;
// returns a string with the corresponding error, if there is no error it returns null. Reading
// the error clears it, so 2 calls in a row will always report null the second time. The buffer
// is owned by the calling thread and is invalidated by that thread's next dlerror() call
char* dlerror() noexcept;
}First, you need to include the urll.h header file. After that, you need to load a library from a file.
This is how the library is intended to be used:
- Call
dlopenwith a string argument set to the path to the library you want to load, and save the returned handle pointer to a variable - Call one of the
dlsymfunctions to load a symbol, and check if it has been successfully loaded by checking if the return value is notnullptr - Use your functions
- When you're done using the functions, simply call
dlclose - If there is any error along the way, call
dlerrorto get an error message
All functions are under the URLL namespace. Here is an example of the C++ API:
void load()
{
auto* handle = URLL::dlopen("./libLIBRARY.so");
if (handle == nullptr)
{
std::cout << dlerror() << std::endl;
return;
}
std::function<void(void)> func;
if (URLL::dlsym(handle, "begin", func) == handle)
func();
URLL::dlclose(handle);
}The code is completely compatible with the library example we listed above.
On the line where we use dlsym, we use the std::function support and templates.
This, as said on the last page, can be enabled using the URLL_USE_FUNCTIONAL macro.
We also have a generic dlsym function that just returns a void*, which you can use to manually cast to the type of your
variable/function. Additionally, we also provide support for C function pointers using the dlsym_func function, and
support for variables using the dlsym_var function.
The C API is much more bare-bones; it looks like this:
// Libraries are always loaded in lazy mode. A null location returns a handle to the main
// program, on Windows too, but there it only resolves symbols exported by the executable
// itself, not by the libraries it links against
void* urll_dlopen(const char* location);
// A null handle is not supported. On glibc it happens to search every loaded library, since
// RTLD_DEFAULT is null there, but Windows offers no cheap equivalent and simply fails
void* urll_dlsym(void* handle, const char* name);
void* urll_dlsym_func(void* handle, const char* name, void** function);
void* urll_dlsym_var(void* handle, const char* name, void** var);
int urll_dlclose(void* handle);
// returns a string with the corresponding error, if there is no error it returns null. Reading
// the error clears it, so 2 calls in a row will always report null the second time. The buffer
// is owned by the calling thread and is invalidated by that thread's next urll_dlerror() call
char* urll_dlerror();Notice how if we compare with the C++ API, the only differences in most cases are that the names are prefixed with
urll_ to signify the lack of the namespaces feature in C.
Here is an example that uses the C API. Remember that, for the C API, you need to include the curll.h header:
void load()
{
void* handle = urll_dlopen("./libLIBRARY.so");
if (handle == NULL)
{
printf("%s\n", urll_dlerror());
return;
}
void (*test)(void) = NULL;
if (urll_dlsym_func(handle, "begin", &test))
{
test();
}
urll_dlclose(handle);
}Here, to load a function, we call dlsym_func. For loading variables, we also have
dlsym_var, and a generic dlsym that just returns a void* which you can manually cast to your variables/functions.
This library depends on libdl when compiling on Unix-based systems, so make sure you are linking against it.
This project is supported by all the people who joined our Discord server and became beta testers. If you want to join the Discord, you can click here.