diff --git a/.clang-tidy b/.clang-tidy index 932a5be..143d361 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -6,13 +6,13 @@ Checks: > google-*,-google-readability-todo,-google-readability-braces-around-statements, hicpp-*,-hicpp-braces-around-statements,-hicpp-use-emplace,-hicpp-named-parameter, -hicpp-no-array-decay,-hicpp-uppercase-literal-suffix, - misc-*,-misc-no-recursion, + misc-*,-misc-include-cleaner,-misc-no-recursion, bugprone-*, boost-*, cert-*,-cert-dcl16-c,-cert-dcl51-cpp,-cert-dcl37-c, concurrency-*, cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers, - modernize-*,-modernize-use-trailing-return-type,-modernize-use-emplace, + modernize-*,-modernize-use-trailing-return-type,-modernize-use-emplace,-modernize-use-designated-initializers, performance-*,-performance-noexcept-move-constructor, portability-*, readability-*,-readability-redundant-access-specifiers diff --git a/.github/workflows/cpp-format.yml b/.github/workflows/cpp-format.yml index df59907..5fc5c62 100644 --- a/.github/workflows/cpp-format.yml +++ b/.github/workflows/cpp-format.yml @@ -28,9 +28,9 @@ jobs: - uses: actions/checkout@v3 - uses: jirutka/setup-alpine@v1 with: - branch: v3.19 + branch: v3.21 packages: > - clang-extra-tools + clang19-extra-tools # Check all source and header C++ files; ignore files in hidden directories - name: Execute clang-format diff --git a/.github/workflows/cpp-lint.yml b/.github/workflows/cpp-lint.yml index 65a0aaf..b0f2164 100644 --- a/.github/workflows/cpp-lint.yml +++ b/.github/workflows/cpp-lint.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v3 - uses: jirutka/setup-alpine@v1 with: - branch: v3.19 + branch: v3.21 packages: > cmake ninja-build @@ -44,7 +44,7 @@ jobs: python3-dev fmt-dev gtest-dev - clang-extra-tools + clang19-extra-tools - name: Fixup environment run: | ln -s liblua-5.2.so.0 /usr/lib/liblua-5.2.so diff --git a/examples/configurable_plugin/configurable_plugin_manager.cpp b/examples/configurable_plugin/configurable_plugin_manager.cpp index 14856cd..8efe1f3 100644 --- a/examples/configurable_plugin/configurable_plugin_manager.cpp +++ b/examples/configurable_plugin/configurable_plugin_manager.cpp @@ -32,12 +32,10 @@ int main(int argc, char* argv[]) if (!a_1 || !a_2) { return 2; } - auto unwrapped_a_1 = *a_1; - auto unwrapped_a_2 = *a_2; std::atomic stop_signal { false }; - std::thread thread_1([plugin = unwrapped_a_1, &stop_signal]() { plugin->loop(stop_signal); }); - std::thread thread_2([plugin = unwrapped_a_2, &stop_signal]() { plugin->loop(stop_signal); }); + std::thread thread_1([plugin = *a_1, &stop_signal]() { plugin->loop(stop_signal); }); + std::thread thread_2([plugin = *a_2, &stop_signal]() { plugin->loop(stop_signal); }); std::this_thread::sleep_for(std::chrono::seconds { 5 }); stop_signal.store(true); thread_1.join(); diff --git a/examples/multi_language_plugin/multi_language_plugin_manager.cpp b/examples/multi_language_plugin/multi_language_plugin_manager.cpp index dd98d88..aded76e 100644 --- a/examples/multi_language_plugin/multi_language_plugin_manager.cpp +++ b/examples/multi_language_plugin/multi_language_plugin_manager.cpp @@ -40,12 +40,12 @@ int main(int argc, char* argv[]) plugins.push_back(std::move(*plugin)); } plugins.push_back(manager.loadCPlugin(non_existant_lib_path) - .andThen([](const auto& plugin) { - // convert to generic plugin - return ppplugin::Plugin { plugin }; - }) - // silently fail and use no-op plugin instead - .valueOr(ppplugin::NoopPlugin {})); + .andThen([](const auto& plugin) { + // convert to generic plugin + return ppplugin::Plugin { plugin }; + }) + // silently fail and use no-op plugin instead + .valueOr(ppplugin::NoopPlugin {})); for (auto& plugin : plugins) { plugin.call("initialize").valueOrElse([](const ppplugin::CallError& error) { std::cerr << "Initialize Error: " << error.what() << '\n'; @@ -54,7 +54,7 @@ int main(int argc, char* argv[]) for (int counter {}; counter < std::numeric_limits::max(); ++counter) { for (auto& plugin : plugins) { // explicit cast to int to avoid passing by reference - plugin.call("loop", static_cast(counter)) + plugin.call("loop", static_cast(counter)) // NOLINT(readability-redundant-casting) .valueOrElse([](const ppplugin::CallError& error) { std::cerr << "Loop Error: " << error.what() << '\n'; }); diff --git a/examples/multi_language_plugin/plugins/cpp_plugin.cpp b/examples/multi_language_plugin/plugins/cpp_plugin.cpp index d84aa12..76d8d3a 100644 --- a/examples/multi_language_plugin/plugins/cpp_plugin.cpp +++ b/examples/multi_language_plugin/plugins/cpp_plugin.cpp @@ -2,7 +2,7 @@ #include -void initialize() +extern void initialize() { std::cout << "C++ initialize" << '\n'; } diff --git a/examples/simple_plugin/simple_plugin_manager.cpp b/examples/simple_plugin/simple_plugin_manager.cpp index 1d5d170..c99a594 100644 --- a/examples/simple_plugin/simple_plugin_manager.cpp +++ b/examples/simple_plugin/simple_plugin_manager.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "ppplugin/cpp/plugin.h" @@ -13,43 +12,25 @@ #include "simple_plugin.h" -void printError(std::string_view function_name, const ppplugin::CallError& error) -{ - const std::string_view error_string = error == ppplugin::CallError::Code::symbolNotFound ? "not found" - : error == ppplugin::CallError::Code::notLoaded ? "not loaded" - : error == ppplugin::CallError::Code::unknown ? "unknown error" - : "other error"; - std::cerr << "Unable to call '" << function_name << "' ('" << error_string - << "')!\n"; -} - -void printError(std::string_view plugin_name, ppplugin::LoadError error) -{ - using ppplugin::LoadError; - const std::string_view error_string = error == LoadError::fileNotFound ? "not found" - : error == LoadError::fileInvalid ? "invalid" - : error == LoadError::unknown ? "unknown error" - : "other error"; - std::cerr << "Unable to load '" << plugin_name << "' ('" << error_string - << "')!\n"; -} - +namespace { template std::thread initializeAndLoop(ppplugin::CppPlugin& plugin, const std::string& function_name) { - auto plugin_a = plugin.call>(function_name); - if (plugin_a) { - auto& plugin = *plugin_a; + auto result = plugin.call>(function_name); + if (result) { + auto& plugin = *result; plugin->initialize(); return std::thread { [plugin]() { plugin->loop(); } }; } // NOLINTNEXTLINE(bugprone-unchecked-optional-access); checked in previous if - return std::thread { [function_name, error = *plugin_a.error()]() { - printError(function_name, error); + return std::thread { [function_name, error = *result.error()]() { + std::cerr << "Unable to call '" << function_name + << "' ('" << ppplugin::codeToString(error.code()) << "')!\n"; } }; } +} // namespace int main(int argc, char* argv[]) { @@ -59,14 +40,14 @@ int main(int argc, char* argv[]) } auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); auto library_path = executable_dir / "libsimple_plugin.so"; - // setup manager - has to stay alive for as long as you want to use the - // plugins + // setup manager - has to stay alive for as long as you want to use the plugins ppplugin::GenericPluginManager manager; // load plugin library and exit on error - auto plugin_library = manager.loadCppPlugin(std::filesystem::path { library_path }).valueOrElse([](const auto& error) -> ppplugin::CppPlugin { - printError("simple_plugin_a", error); - std::exit(1); // NOLINT(concurrency-mt-unsafe) - }); + auto plugin_library = manager.loadCppPlugin(std::filesystem::path { library_path }) + .valueOrElse([](const auto& error) -> ppplugin::CppPlugin { + std::cerr << "Unable to load plugin: " << ppplugin::codeToString(error); + std::exit(1); // NOLINT(concurrency-mt-unsafe) + }); auto thread_a = initializeAndLoop(plugin_library, "create_a"); auto thread_b = initializeAndLoop(plugin_library, "create_b"); diff --git a/include/ppplugin/c/plugin.h b/include/ppplugin/c/plugin.h index faf1c07..94f8ac9 100644 --- a/include/ppplugin/c/plugin.h +++ b/include/ppplugin/c/plugin.h @@ -7,29 +7,18 @@ namespace ppplugin { class CPlugin { public: - static Expected load( - const std::filesystem::path& plugin_library_path) - { - if (auto shared_library = detail::boost_dll::loadSharedLibrary(plugin_library_path)) { - CPlugin new_plugin; - new_plugin.plugin_ = *shared_library; - return new_plugin; - } - return LoadError::unknown; - } + [[nodiscard]] static Expected load(const std::filesystem::path& plugin_library_path); + + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + operator bool() const; template - [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args) - { - static_assert(!std::is_reference_v, - "C does not support references for its return value!"); - static_assert(!(std::is_reference_v || ...), - "C does not support references for its arguments! " - "Consider passing the argument with an explicit cast to the desired type."); - - return detail::boost_dll::call( - plugin_, function_name, std::forward(args)...); - } + [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args); + + template + [[nodiscard]] CallResult global(const std::string& variable_name); + template + [[nodiscard]] CallResult global(const std::string& variable_name, VariableType&& new_value); private: CPlugin() = default; @@ -37,6 +26,41 @@ class CPlugin { private: boost::dll::shared_library plugin_; }; + +template +CallResult CPlugin::call(const std::string& function_name, Args&&... args) +{ + static_assert(!std::is_reference_v, + "C does not support references for its return value!"); + static_assert(!(std::is_reference_v || ...), + "C does not support references for its arguments! " + "Consider passing the argument with an explicit cast to the desired type."); + + return detail::boost_dll::call( + plugin_, function_name, std::forward(args)...); +} + +// TODO: remove code duplication here and in C++ plugin +template +CallResult CPlugin::global(const std::string& variable_name) +{ + auto p = detail::boost_dll::getSymbol(plugin_, variable_name); + if (p.hasValue()) { + return *reinterpret_cast(p.value().value()); + } + return { p.error().value() }; +} + +template +CallResult CPlugin::global(const std::string& variable_name, VariableType&& new_value) +{ + auto p = detail::boost_dll::getSymbol(plugin_, variable_name); + if (p.hasValue()) { + *reinterpret_cast(p.value().value()) = std::forward(new_value); + return {}; + } + return { p.error().value() }; +} } // namespace ppplugin #endif // PPPLUGIN_C_PLUGIN_H diff --git a/include/ppplugin/cpp/object_plugin.h b/include/ppplugin/cpp/object_plugin.h deleted file mode 100644 index 6673b3a..0000000 --- a/include/ppplugin/cpp/object_plugin.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef PPPLUGIN_CPP_OBJECT_PLUGIN_H -#define PPPLUGIN_CPP_OBJECT_PLUGIN_H - -namespace ppplugin { -/** - * Plugin that has a creator function that creates an object - * of a known base class. - */ -template -class CppObjectPlugin { -public: - // TODO -}; -} // namespace ppplugin - -#endif // PPPLUGIN_CPP_OBJECT_PLUGIN_H diff --git a/include/ppplugin/cpp/plugin.h b/include/ppplugin/cpp/plugin.h index e06eddd..867b3db 100644 --- a/include/ppplugin/cpp/plugin.h +++ b/include/ppplugin/cpp/plugin.h @@ -12,16 +12,7 @@ namespace ppplugin { class CppPlugin { public: - static Expected load( - const std::filesystem::path& plugin_library_path) - { - if (auto shared_library = detail::boost_dll::loadSharedLibrary(plugin_library_path)) { - CppPlugin new_plugin; - new_plugin.plugin_ = *shared_library; - return new_plugin; - } - return LoadError::unknown; - } + [[nodiscard]] static Expected load(const std::filesystem::path& plugin_library_path); ~CppPlugin() = default; CppPlugin(const CppPlugin&) = default; @@ -30,17 +21,15 @@ class CppPlugin { CppPlugin& operator=(CppPlugin&&) = default; // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) - operator bool() const - { - return plugin_.is_loaded(); - } + operator bool() const; template - [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args) - { - return detail::boost_dll::call( - plugin_, function_name, std::forward(args)...); - } + [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args); + + template + [[nodiscard]] CallResult global(const std::string& variable_name); + template + [[nodiscard]] CallResult global(const std::string& variable_name, VariableType&& new_value); private: CppPlugin() = default; @@ -48,6 +37,34 @@ class CppPlugin { private: boost::dll::shared_library plugin_; }; + +template +CallResult CppPlugin::call(const std::string& function_name, Args&&... args) +{ + return detail::boost_dll::call( + plugin_, function_name, std::forward(args)...); +} + +template +CallResult CppPlugin::global(const std::string& variable_name) +{ + auto p = detail::boost_dll::getSymbol(plugin_, variable_name); + if (p.hasValue()) { + return *reinterpret_cast(p.value().value()); + } + return { p.error().value() }; +} + +template +CallResult CppPlugin::global(const std::string& variable_name, VariableType&& new_value) +{ + auto p = detail::boost_dll::getSymbol(plugin_, variable_name); + if (p.hasValue()) { + *reinterpret_cast(p.value().value()) = std::forward(new_value); + return {}; + } + return { p.error().value() }; +} } // namespace ppplugin #endif // PPPLUGIN_CPP_PLUGIN_H diff --git a/include/ppplugin/detail/boost_dll_loader.h b/include/ppplugin/detail/boost_dll_loader.h index 7bb7b42..2e85ce3 100644 --- a/include/ppplugin/detail/boost_dll_loader.h +++ b/include/ppplugin/detail/boost_dll_loader.h @@ -20,6 +20,7 @@ namespace ppplugin::detail::boost_dll { [[nodiscard]] CallResult getFunctionSymbol(const boost::dll::shared_library& library, const std::string& function_name); [[nodiscard]] CallResult getFunctionPointerSymbol(const boost::dll::shared_library& library, const std::string& function_name); +[[nodiscard]] CallResult getSymbol(const boost::dll::shared_library& library, const std::string& function_name); template [[nodiscard]] CallResult call(const boost::dll::shared_library& library, const std::string& function_name, Args&&... args) diff --git a/include/ppplugin/detail/template_helpers.h b/include/ppplugin/detail/template_helpers.h index f21c8f7..b09f224 100644 --- a/include/ppplugin/detail/template_helpers.h +++ b/include/ppplugin/detail/template_helpers.h @@ -99,6 +99,23 @@ struct FirstOr { template using FirstOrT = typename FirstOr::Type; +/** + * Get second template parameter of given class. + * If the class does not have any or sufficient template parameters, + * return given DefaultType. + */ +template +struct SecondTemplateParameterOr { + using Type = DefaultType; +}; +template typename T, + typename U, typename V, typename... Ts> +struct SecondTemplateParameterOr> { + using Type = V; +}; +template +using SecondTemplateParameterOrT = typename SecondTemplateParameterOr::Type; + /** * Wrap types into given type "Wrapper" (which has to accept exactly one template argument, * but can have more defaulted template parameters) and puts them into the first given type @@ -168,9 +185,18 @@ struct IsSpecialization : std::false_type { }; template