r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
PluginLoader.hpp
Go to the documentation of this file.
1///
2/// @file PluginLoader.hpp
3/// @brief Modern, cross-platform plugin loader
4/// @namespace utl
5///
6
7#pragma once
8
9#include <concepts>
10#include <memory>
11#include <mutex>
12#include <stdexcept>
13#include <string>
14#include <unordered_map>
15
16#ifdef _WIN32
17#include <windows.h>
18#define PLUGINS_EXTENSION ".dll"
19#else
20#include <dlfcn.h>
21#if __APPLE__
22#define PLUGINS_EXTENSION ".dylib"
23#else
24#define PLUGINS_EXTENSION ".so"
25#endif
26#endif
27
29#include "Utils/Logger.hpp"
30
31namespace utl
32{
33
34 using LibHandle =
35#ifdef _WIN32
36 HMODULE;
37#else
38 void *;
39#endif
40
41 /// Handle to a dynamic library with RAII
42 struct SharedLib
43 {
44 LibHandle handle = nullptr;
45
46 explicit SharedLib(const LibHandle h = nullptr) : handle(h) {}
48
49 SharedLib(const SharedLib &) = delete;
50 SharedLib &operator=(const SharedLib &) = delete;
51 SharedLib(SharedLib &&other) noexcept : handle(other.handle) { other.handle = nullptr; }
52 SharedLib &operator=(SharedLib &&other) noexcept
53 {
54 if (this != &other)
55 {
56 close();
57 handle = other.handle;
58 other.handle = nullptr;
59 }
60 return *this;
61 }
62
63 void close()
64 {
65 if (!handle)
66 return;
67#ifdef _WIN32
68 FreeLibrary(handle);
69#else
70 dlclose(handle);
71#endif
72 handle = nullptr;
73 }
74
75 explicit operator bool() const { return handle != nullptr; }
76 };
77
78 using EntryPointFn = IPlugin *(*)();
79
80 ///
81 /// @class PluginLoader
82 /// @brief Modern, type-safe plugin loader
83 /// @namespace utl
84 ///
86 {
87 public:
88 PluginLoader() = default;
89 ~PluginLoader() = default;
90
91 PluginLoader(const PluginLoader &) = delete;
95
96 ///
97 /// Load a plugin of type T
98 /// @tparam T Expected plugin interface (must derive from IPlugin)
99 /// @param path Path to the dynamic library
100 /// @return shared_ptr<T> instance
101 ///
102 template <std::derived_from<IPlugin> T> std::shared_ptr<T> loadPlugin(const std::string &path)
103 {
104 std::scoped_lock lock(m_mutex);
105
106 if (m_plugins.contains(path))
107 throw std::runtime_error("Plugin already loaded: " + path);
108
109 SharedLib lib = loadLibrary(path);
110 const EntryPointFn entry = getEntryPoint(lib, path);
111
112 std::unique_ptr<IPlugin> plugin(entry());
113 if (!plugin)
114 throw std::runtime_error("EntryPoint failed: " + path);
115
116 T *typed = dynamic_cast<T *>(plugin.get());
117 if (!typed)
118 throw std::runtime_error("Plugin type mismatch: " + path);
119
120 auto [it, inserted] = m_plugins.emplace(path, std::move(plugin));
121 if (!inserted)
122 throw std::runtime_error("Failed to store plugin: " + path);
123
124 m_handles[path] = std::move(lib);
125
126 Logger::log("Plugin loaded:\t name: " + it->second->getName() + "\t path: " + path, LogLevel::INFO);
127
128 std::shared_ptr<IPlugin> baseShared(it->second.get(), [](IPlugin *) {});
129 return std::shared_ptr<T>(baseShared, typed);
130 }
131
132 private:
133 std::mutex m_mutex;
134 std::unordered_map<std::string, SharedLib> m_handles;
135 std::unordered_map<std::string, std::unique_ptr<IPlugin>> m_plugins;
136
137 SharedLib loadLibrary(const std::string &path)
138 {
139
140#ifdef _WIN32
141 const LibHandle handle = LoadLibraryA(path.c_str());
142 if (!handle)
143 {
144 throw std::runtime_error("Cannot load library: " + path);
145 }
146#else
147 dlerror();
148 const LibHandle handle = dlopen(path.c_str(), RTLD_LAZY);
149 if (!handle)
150 {
151 const char *error = dlerror();
152 std::string msg = "Cannot load library: " + path;
153 if (error)
154 {
155 msg += " (" + std::string(error) + ")";
156 }
157 throw std::runtime_error(msg);
158 }
159#endif
160 return SharedLib(handle);
161 }
162
163 EntryPointFn getEntryPoint(SharedLib &lib, const std::string &path)
164 {
165 EntryPointFn entry =
166#ifdef _WIN32
167 reinterpret_cast<EntryPointFn>(GetProcAddress(lib.handle, "entryPoint"));
168#else
169 reinterpret_cast<EntryPointFn>(dlsym(lib.handle, "entryPoint"));
170#endif
171 if (!entry)
172 throw std::runtime_error("EntryPoint not found in plugin: " + path);
173 return entry;
174 }
175 };
176
177} // namespace utl
This file contains the plugin interface.
This file contains the Logger class.
Interface for plugins.
Definition IPlugin.hpp:29
static void log(const std::string &message, const LogLevel &logLevel)
Definition Logger.hpp:51
Modern, type-safe plugin loader.
std::unordered_map< std::string, std::unique_ptr< IPlugin > > m_plugins
PluginLoader(PluginLoader &&)=delete
PluginLoader & operator=(PluginLoader &&)=delete
PluginLoader()=default
PluginLoader & operator=(const PluginLoader &)=delete
PluginLoader(const PluginLoader &)=delete
SharedLib loadLibrary(const std::string &path)
std::unordered_map< std::string, SharedLib > m_handles
EntryPointFn getEntryPoint(SharedLib &lib, const std::string &path)
std::shared_ptr< T > loadPlugin(const std::string &path)
Load a plugin of type T.
~PluginLoader()=default
IPlugin *(*)() EntryPointFn
void * LibHandle
Handle to a dynamic library with RAII.
SharedLib & operator=(const SharedLib &)=delete
SharedLib(const LibHandle h=nullptr)
SharedLib & operator=(SharedLib &&other) noexcept
SharedLib(const SharedLib &)=delete
SharedLib(SharedLib &&other) noexcept