cae  0.0.0
Cross-API graphics engine
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 <filesystem>
10#include <mutex>
11
12#ifdef _WIN32
13#include <windows.h>
14#define PLUGINS_EXTENSION ".dll"
15#define PLUGINS_PREFIX ""
16#else
17#include <dlfcn.h>
18#ifdef __APPLE__
19#define PLUGINS_EXTENSION ".dylib"
20#elif __linux__
21#define PLUGINS_EXTENSION ".so"
22#endif
23#define PLUGINS_PREFIX "lib"
24#endif
25
26namespace utl
27{
28
29 using LibHandle =
30#ifdef _WIN32
31 HMODULE;
32#else
33 void *;
34#endif
35
36 ///
37 /// @struct SharedLib
38 /// @brief RAII wrapper for shared libraries
39 /// @namespace utl
40 ///
41 struct SharedLib
42 {
43 LibHandle handle = nullptr;
44
45 explicit SharedLib(const LibHandle h = nullptr) : handle(h) {}
47
48 SharedLib(const SharedLib &) = delete;
49 SharedLib &operator=(const SharedLib &) = delete;
50 SharedLib(SharedLib &&other) noexcept : handle(other.handle) { other.handle = nullptr; }
51 SharedLib &operator=(SharedLib &&other) noexcept
52 {
53 if (this != &other)
54 {
55 close();
56 handle = other.handle;
57 other.handle = nullptr;
58 }
59 return *this;
60 }
61
62 void close()
63 {
64 if (handle == nullptr)
65 {
66 return;
67 }
68#ifdef _WIN32
69 FreeLibrary(handle);
70#else
71 dlclose(handle);
72#endif
73 handle = nullptr;
74 }
75
76 explicit operator bool() const { return handle != nullptr; }
77 };
78
79 using EntryPointFn = IPlugin *(*)();
80
81 ///
82 /// @class PluginLoader
83 /// @brief Modern, type-safe plugin loader
84 /// @namespace utl
85 ///
87 {
88 public:
89 PluginLoader() = default;
90 ~PluginLoader() = default;
91
92 PluginLoader(const PluginLoader &) = delete;
96
97 ///
98 /// @tparam T Expected plugin interface (must derive from IPlugin)
99 /// @param path Path to the dynamic library
100 /// @param pluginPrefix Expected prefix for plugin filenames
101 /// @return shared_ptr<T> instance
102 /// @brief Load a plugin of type T
103 ///
104 template <std::derived_from<IPlugin> T>
105 std::shared_ptr<T> loadPlugin(const std::string &path, const std::string_view &pluginPrefix = "")
106 {
107 std::scoped_lock lock(m_mutex);
108
109 try
110 {
111 validatePluginPath(path, PLUGINS_PREFIX + std::string(pluginPrefix));
112
113 if (m_plugins.contains(path))
114 {
115 return nullptr;
116 }
117
118 SharedLib lib = loadLibrary(path);
119 const EntryPointFn entry = getEntryPoint(lib, path);
120
121 std::unique_ptr<IPlugin> plugin(entry());
122 if (!plugin)
123 {
124 throw std::runtime_error("EntryPoint returned null");
125 }
126
127 T *typed = dynamic_cast<T *>(plugin.get());
128 if (!typed)
129 {
130 throw std::runtime_error("Plugin type mismatch");
131 }
132
133 auto [it, inserted] = m_plugins.emplace(path, std::move(plugin));
134 if (!inserted)
135 {
136 throw std::runtime_error("Failed to store plugin");
137 }
138
139 m_handles[path] = std::move(lib);
140
141 std::shared_ptr<IPlugin> baseShared(it->second.get(), [](IPlugin *) {});
142 return std::shared_ptr<T>(baseShared, typed);
143 }
144 catch (const std::exception &e)
145 {
146 return nullptr;
147 }
148 }
149
150 private:
151 std::mutex m_mutex;
152 std::unordered_map<std::string, SharedLib> m_handles;
153 std::unordered_map<std::string, std::unique_ptr<IPlugin>> m_plugins;
154
155 ///
156 /// @param path Path to the dynamic library
157 /// @return Loaded SharedLib
158 /// @brief Load a shared library
159 ///
160 static SharedLib loadLibrary(const std::string &path)
161 {
162 const LibHandle handle =
163#ifdef _WIN32
164 LoadLibraryA(path.c_str());
165#else
166 dlopen(path.c_str(), RTLD_LAZY);
167#endif
168 if (handle == nullptr)
169 {
170 const std::string dlErrorMsg =
171#ifdef _WIN32
172 "LoadLibraryA failed";
173#else
174 dlerror();
175#endif
176 throw std::runtime_error("Cannot load library: " + dlErrorMsg + "\nWith path: " + path);
177 }
178 return SharedLib(handle);
179 }
180
181 ///
182 /// @param lib Loaded SharedLib
183 /// @param path Path to the dynamic library
184 /// @return EntryPoint function pointer
185 ///
186 static EntryPointFn getEntryPoint(SharedLib &lib, const std::string &path)
187 {
188 const auto entry =
189#ifdef _WIN32
190 reinterpret_cast<EntryPointFn>(GetProcAddress(lib.handle, "entryPoint"));
191#else
192 reinterpret_cast<EntryPointFn>(dlsym(lib.handle, "entryPoint"));
193#endif
194 if (entry == nullptr)
195 {
196 throw std::runtime_error("EntryPoint not found in plugin: " + path);
197 }
198 return entry;
199 }
200
201 ///
202 /// @param path Path to the dynamic library
203 /// @param pluginPrefix Expected prefix for plugin filenames
204 /// @brief Validate the plugin path
205 ///
206 static void validatePluginPath(const std::filesystem::path &path, const std::string_view &pluginPrefix)
207 {
208 const std::string filename = path.filename().string();
209
210 if (path.extension() != PLUGINS_EXTENSION)
211 {
212 throw std::runtime_error("Invalid plugin extension: " + filename +
213 " (expected " PLUGINS_EXTENSION ")");
214 }
215
216 if (!filename.starts_with(pluginPrefix))
217 {
218 throw std::runtime_error("Invalid plugin name: " + filename + " (plugins must start with '" +
219 std::string(pluginPrefix) + "')");
220 }
221 }
222
223 }; // class PluginLoader
224
225} // namespace utl
#define PLUGINS_PREFIX
Interface for plugins.
Definition IPlugin.hpp:46
Modern, type-safe plugin loader.
std::unordered_map< std::string, std::unique_ptr< IPlugin > > m_plugins
PluginLoader(PluginLoader &&)=delete
static EntryPointFn getEntryPoint(SharedLib &lib, const std::string &path)
PluginLoader & operator=(PluginLoader &&)=delete
std::shared_ptr< T > loadPlugin(const std::string &path, const std::string_view &pluginPrefix="")
Load a plugin of type T.
PluginLoader()=default
PluginLoader & operator=(const PluginLoader &)=delete
PluginLoader(const PluginLoader &)=delete
static void validatePluginPath(const std::filesystem::path &path, const std::string_view &pluginPrefix)
Validate the plugin path.
std::unordered_map< std::string, SharedLib > m_handles
static SharedLib loadLibrary(const std::string &path)
Load a shared library.
~PluginLoader()=default
IPlugin *(*)() EntryPointFn
void * LibHandle
RAII wrapper for shared libraries.
SharedLib & operator=(const SharedLib &)=delete
SharedLib(const LibHandle h=nullptr)
SharedLib & operator=(SharedLib &&other) noexcept
SharedLib(const SharedLib &)=delete
SharedLib(SharedLib &&other) noexcept