Prerequisites
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' API to load symbols from a shared library. This library loads named functions, that being, 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 under a
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.
Using the library
C++ API
The C++ API provides some good syntactic sugar, like templates, that generally make your life better. The library looks like this:
namespace URLL
{
void* dlopen(const char* location) noexcept;
template<typename T, typename... T2>
void* dlsym(void* handle, const char* name, std::function<T(T2...)>& function) noexcept;
template<typename T>
void* dlsym_val(void* handle, const char* name, T* var) noexcept;
template<typename T>
void* dlsym_func(void* handle, const char* name, T& var) noexcept;
void* dlsym(void* handle, const char* name) 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
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
dlopen
with 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
dlsym
functions 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 with using the functions, simply call
dlclose
- If there is any error along the way, call
dlerror
to 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("https://madladsquad.com/libLIBRARY.so");
if (handle == nullptr)
{
std::cout << dlerror() << std::endl;
return;
}
std::function<void(void)> func;
if (URLL::dlsym(handle, "begin", func) == handle)
();
func::dlclose(handle);
URLL}
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 in the
last page, can be enabled using the URLL_USE_FUNCTIONAL
macro.
We also have a generic dlsym
function, that just returns
a void*
, that 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.
C API
The C API is much more bare-bones, it looks like this:
void* urll_dlopen(const char* location);
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);
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 missing 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("https://madladsquad.com/libLIBRARY.so");
if (handle == NULL)
{
("%s\n", urll_dlerror());
printfreturn;
}
void (*test)(void) = NULL;
if (urll_dlsym_func(handle, "begin", &test))
{
();
test}
(handle);
urll_dlclose}
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 your variables/functions.
Compiling your application
This library depends on libdl
when compiling on Unix
based systems, so make sure you are linking against it.