Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
dynamic_library.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
6
7#include <cstdint>
8#include <expected>
9#include <filesystem>
10#include <string>
11#include <string_view>
12#include <type_traits>
13
14namespace helios::utils {
15
16/**
17 * @brief Error codes for dynamic library operations.
18 */
19enum class DynamicLibraryError : uint8_t {
20 FileNotFound, ///< Library file not found
21 LoadFailed, ///< Failed to load library
22 SymbolNotFound, ///< Symbol not found in library
23 InvalidHandle, ///< Invalid library handle
24 AlreadyLoaded, ///< Library is already loaded
25 NotLoaded, ///< Library is not loaded
26 PlatformError, ///< Platform-specific error
27};
28
29/**
30 * @brief Gets a human-readable description for a DynamicLibraryError.
31 * @param error The error code
32 * @return String description of the error
33 */
34[[nodiscard]] constexpr std::string_view DynamicLibraryErrorToString(DynamicLibraryError error) noexcept {
35 switch (error) {
37 return "Library file not found";
39 return "Failed to load library";
41 return "Symbol not found in library";
43 return "Invalid library handle";
45 return "Library is already loaded";
47 return "Library is not loaded";
49 return "Platform-specific error";
50 default:
51 return "Unknown error";
52 }
53}
54
55/**
56 * @brief Cross-platform dynamic library loader.
57 * @details Provides a unified interface for loading dynamic libraries (DLLs on Windows,
58 * .so on Linux, .dylib on macOS) and retrieving function symbols.
59 *
60 * @note Not thread-safe. External synchronization required for concurrent access.
61 *
62 * @example
63 * @code
64 * DynamicLibrary lib;
65 * if (auto result = lib.Load("my_module.so"); result) {
66 * using CreateModuleFn = Module* (*)();
67 * if (auto fn = lib.GetSymbol<CreateModuleFn>("create_module"); fn) {
68 * Module* module = (*fn)();
69 * // Use module...
70 * }
71 * }
72 * @endcode
73 */
75public:
76 /// Native handle type (void* on all platforms for ABI stability)
77 using HandleType = void*;
78 static constexpr HandleType kInvalidHandle = nullptr;
79
80 DynamicLibrary() = default;
81
82 /**
83 * @brief Constructs and loads a library from the specified path.
84 * @param path Path to the dynamic library
85 */
86 explicit DynamicLibrary(const std::filesystem::path& path);
88 DynamicLibrary(DynamicLibrary&& other) noexcept;
89
90 /**
91 * @brief Destructor that unloads the library if loaded.
92 */
93 ~DynamicLibrary() noexcept;
94
95 DynamicLibrary& operator=(const DynamicLibrary&) = delete;
96 DynamicLibrary& operator=(DynamicLibrary&& other) noexcept;
97
98 [[nodiscard]] static auto FromPath(const std::filesystem::path& path)
100
101 /**
102 * @brief Loads a dynamic library from the specified path.
103 * @param path Path to the dynamic library
104 * @return Expected with void on success, or error on failure
105 */
106 [[nodiscard]] auto Load(const std::filesystem::path& path) -> std::expected<void, DynamicLibraryError>;
107
108 /**
109 * @brief Unloads the currently loaded library.
110 * @return Expected with void on success, or error on failure
111 */
112 [[nodiscard]] auto Unload() -> std::expected<void, DynamicLibraryError>;
113
114 /**
115 * @brief Reloads the library from the same path.
116 * @details Unloads the current library and loads it again.
117 * @return Expected with void on success, or error on failure
118 */
119 [[nodiscard]] auto Reload() -> std::expected<void, DynamicLibraryError>;
120
121 /**
122 * @brief Gets a raw symbol address from the library.
123 * @param name Name of the symbol to retrieve
124 * @return Expected with void pointer on success, or error on failure
125 */
126 [[nodiscard]] auto GetSymbolAddress(std::string_view name) const -> std::expected<void*, DynamicLibraryError>;
127
128 /**
129 * @brief Gets a typed function pointer from the library.
130 * @tparam T Function pointer type
131 * @param name Name of the symbol to retrieve
132 * @return Expected with function pointer on success, or error on failure
133 */
134 template <typename T>
135 requires std::is_pointer_v<T>
136 [[nodiscard]] auto GetSymbol(std::string_view name) const -> std::expected<T, DynamicLibraryError>;
137
138 /**
139 * @brief Checks if a library is currently loaded.
140 * @return True if a library is loaded
141 */
142 [[nodiscard]] bool Loaded() const noexcept { return handle_ != kInvalidHandle; }
143
144 /**
145 * @brief Gets the path of the loaded library.
146 * @return Path to the library, or empty if not loaded
147 */
148 [[nodiscard]] const std::filesystem::path& Path() const noexcept { return path_; }
149
150 /**
151 * @brief Gets the native handle of the loaded library.
152 * @return Native handle, or kInvalidHandle if not loaded
153 */
154 [[nodiscard]] HandleType Handle() const noexcept { return handle_; }
155
156 /**
157 * @brief Gets the last platform-specific error message.
158 * @return Error message string
159 */
160 [[nodiscard]] static std::string GetLastErrorMessage() noexcept;
161
162 /**
163 * @brief Gets the platform-specific file extension for dynamic libraries.
164 * @return ".dll" on Windows, ".so" on Linux, ".dylib" on macOS
165 */
166 [[nodiscard]] static constexpr std::string_view GetPlatformExtension() noexcept {
167#ifdef HELIOS_PLATFORM_WINDOWS
168 return ".dll";
169#elifdef HELIOS_PLATFORM_MACOS
170 return ".dylib";
171#else
172 return ".so";
173#endif
174 }
175
176 /**
177 * @brief Gets the platform-specific library prefix.
178 * @return Empty on Windows, "lib" on Unix-like systems
179 */
180 [[nodiscard]] static constexpr std::string_view GetPlatformPrefix() noexcept {
181#ifdef HELIOS_PLATFORM_WINDOWS
182 return "";
183#else
184 return "lib";
185#endif
186 }
187
188private:
189 HandleType handle_ = kInvalidHandle; ///< Native library handle
190 std::filesystem::path path_; ///< Path to the loaded library
191};
192
193inline DynamicLibrary::DynamicLibrary(const std::filesystem::path& path) {
194 auto result = Load(path);
195 if (!result) {
196 HELIOS_ERROR("Failed to load dynamic library '{}': {}!", path.string(),
197 DynamicLibraryErrorToString(result.error()));
198 }
199}
200
202 : handle_(other.handle_), path_(std::move(other.path_)) {
203 other.handle_ = kInvalidHandle;
204}
205
207 if (Loaded()) {
208 auto result = Unload();
209 if (!result) {
210 HELIOS_WARN("Failed to unload dynamic library '{}': {}!", path_.string(),
211 DynamicLibraryErrorToString(result.error()));
212 }
213 }
214}
215
217 if (this != &other) {
218 if (Loaded()) {
219 [[maybe_unused]] auto _ = Unload();
220 }
221 handle_ = other.handle_;
222 path_ = std::move(other.path_);
223 other.handle_ = kInvalidHandle;
224 }
225 return *this;
226}
227
228inline auto FromPath(const std::filesystem::path& path) -> std::expected<DynamicLibrary, DynamicLibraryError> {
229 DynamicLibrary lib;
230 if (const auto result = lib.Load(path); !result) {
231 return std::unexpected(result.error());
232 }
233 return lib;
234}
235
236inline auto DynamicLibrary::Reload() -> std::expected<void, DynamicLibraryError> {
237 if (!Loaded()) {
238 return std::unexpected(DynamicLibraryError::NotLoaded);
239 }
240
241 const auto saved_path = path_;
242
243 auto unload_result = Unload();
244 if (!unload_result) {
245 return unload_result;
246 }
247
248 return Load(saved_path);
249}
250
251template <typename T>
252 requires std::is_pointer_v<T>
253inline auto DynamicLibrary::GetSymbol(std::string_view name) const -> std::expected<T, DynamicLibraryError> {
254 auto result = GetSymbolAddress(name);
255 if (!result) {
256 return std::unexpected(result.error());
257 }
258
259 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
260 return reinterpret_cast<T>(*result);
261}
262
263} // namespace helios::utils
auto GetSymbol(std::string_view name) const -> std::expected< T, DynamicLibraryError >
Gets a typed function pointer from the library.
bool Loaded() const noexcept
Checks if a library is currently loaded.
static constexpr std::string_view GetPlatformExtension() noexcept
Gets the platform-specific file extension for dynamic libraries.
HandleType Handle() const noexcept
Gets the native handle of the loaded library.
auto GetSymbolAddress(std::string_view name) const -> std::expected< void *, DynamicLibraryError >
Gets a raw symbol address from the library.
static std::string GetLastErrorMessage() noexcept
Gets the last platform-specific error message.
void * HandleType
Native handle type (void* on all platforms for ABI stability)
static auto FromPath(const std::filesystem::path &path) -> std::expected< DynamicLibrary, DynamicLibraryError >
static constexpr std::string_view GetPlatformPrefix() noexcept
Gets the platform-specific library prefix.
static constexpr HandleType kInvalidHandle
auto Reload() -> std::expected< void, DynamicLibraryError >
Reloads the library from the same path.
DynamicLibrary(const DynamicLibrary &)=delete
auto Load(const std::filesystem::path &path) -> std::expected< void, DynamicLibraryError >
Loads a dynamic library from the specified path.
auto Unload() -> std::expected< void, DynamicLibraryError >
Unloads the currently loaded library.
const std::filesystem::path & Path() const noexcept
Gets the path of the loaded library.
~DynamicLibrary() noexcept
Destructor that unloads the library if loaded.
DynamicLibrary & operator=(const DynamicLibrary &)=delete
#define HELIOS_ERROR(...)
Definition logger.hpp:689
#define HELIOS_WARN(...)
Definition logger.hpp:687
constexpr std::string_view DynamicLibraryErrorToString(DynamicLibraryError error) noexcept
Gets a human-readable description for a DynamicLibraryError.
auto FromPath(const std::filesystem::path &path) -> std::expected< DynamicLibrary, DynamicLibraryError >
DynamicLibraryError
Error codes for dynamic library operations.
@ FileNotFound
Library file not found.
@ InvalidHandle
Invalid library handle.
@ LoadFailed
Failed to load library.
@ AlreadyLoaded
Library is already loaded.
@ NotLoaded
Library is not loaded.
@ PlatformError
Platform-specific error.
@ SymbolNotFound
Symbol not found in library.
STL namespace.