#опытным
В тему указателей на функции вкину еще один способ реализации полиморфизма в С++ - разделяемые или динамические библиотеки.
Обычно разделяемые библиотеки загружаются на самом старте программы(какие-нибудь libc и libstdc++ например неявно подгружаются на старте). Основную часть таких библиотек мы прописываем в опциях линковки.
Однако динамические библиотеки можно неявно подгружать прямо из кода! Для этого на разных системах существует разное системное апи, но для юниксов это dlopen+dlsym.
dlopen по заданному пути файла библиотеки возвращает void указатель на хэндлер этой либы. С помощью хэндлера, функции dlsym и текстового названия определенной функции можно получить указатель на эту функцию и вызвать ее.
Тут пример будет довольно длинный, поэтому начнем с начала.
У вас есть какой-то интерфейс и вы хотите передать реализацию этого интерфейса другой команде, которая имеет чуть больше скилла в данной доменной области:
class PluginInterface {
public:
virtual int method() = 0;
};
extern "C" PluginInterface* create_plugin();
extern "C" void destroy_plugin(PluginInterface* obj);Эта команда берет и реализует этот интерфейс:
#include "PluginInterface.hpp"
#include <iostream>
class MyPlugin : public PluginInterface {
public:
virtual void method() override;
};
int MyPlugin::method() {
std::cout << "Method is called\n";
return 42;
}
extern "C" PluginInterface* create_plugin() {
return new MyPlugin();
}
extern "C" void destroy_plugin(PluginInterface* obj) {
delete obj;
}
Также вы договорились, что каждая реализация интерфейса предоставляет 2 функции: создания и уничтожения наследников.
Функции create_plugin и destroy_plugin обязаны иметь сишную линковку, чтобы достать указатели на них по их имени из библиотеки с помощью dlsym:
#include "PluginInterface.hpp"
#include <dlfcn.h>
#include <iostream>
typedef PluginInterface *(*creatorFunction)();
typedef void (*destroyerFunction)(PluginInterface *);
int main() {
void *handle = dlopen("myplugin.so", RTLD_LAZY);
if (!handle) {
std::println("dlopen failure: {}", dlerror());
return 1;
}
creatorFunction create = reinterpret_cast<creatorFunction>(dlsym(handle, "create_plugin"));
destroyerFunction destroy = reinterpret_cast<destroyerFunction>(dlsym(handle, "destroy_plugin"));
PluginInterface *plugin = (*create)();
std::println("{}", plugin->method());
(*destroy)(plugin);
dlclose(handle);
}
С помощью dlopen и пути к библиотеке-реализации интерфейса получает хэндлер либы. Дальше получаем указатели на функции создания и уничтожения плагина с помощью dlsym, хэндлера и текстовому имени функции.
Разве по имени функции можно получить указатель на нее? Похоже на какую-то рефлексию с первого взгляда.
Тут дело в именах функций и отображении их в символы бинарного файла при компиляции. В С нет никакого манглинга имен, поэтому в готовом бинарном файле можно найти символ, соответствующий названию функции, и связанный с ним адрес этой фукнции. Именно поэтому create_plugin и destroy_plugin помечены extern "C", чтобы их имена обрабатывались по правилам С.
По сути, это все еще про указатели на функции, просто интересно, что на момент компиляции программы у вас может не быть реализации этих функции.
Choose the right name. Stay cool.
#cppcore #OS #compiler