From 4c12ca6d96ced4d3c4f4c0829e18993adf098b03 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 26 Apr 2026 19:07:42 +0200 Subject: [PATCH] examples: Add three increasingly complex C++ plugin examples --- examples/CMakeLists.txt | 2 +- examples/cpp_plugin/CMakeLists.txt | 10 +++++ examples/cpp_plugin/cpp_plugin.cpp | 50 ++++++++++++++++++++++ examples/cpp_plugin/cpp_plugin.h | 49 +++++++++++++++++++++ examples/cpp_plugin/example_1.cpp | 33 +++++++++++++++ examples/cpp_plugin/example_2.cpp | 41 ++++++++++++++++++ examples/cpp_plugin/example_3.cpp | 68 ++++++++++++++++++++++++++++++ 7 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 examples/cpp_plugin/CMakeLists.txt create mode 100644 examples/cpp_plugin/cpp_plugin.cpp create mode 100644 examples/cpp_plugin/cpp_plugin.h create mode 100644 examples/cpp_plugin/example_1.cpp create mode 100644 examples/cpp_plugin/example_2.cpp create mode 100644 examples/cpp_plugin/example_3.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8b13789..1d46c51 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1 @@ - +add_subdirectory(cpp_plugin) diff --git a/examples/cpp_plugin/CMakeLists.txt b/examples/cpp_plugin/CMakeLists.txt new file mode 100644 index 0000000..3a1ce67 --- /dev/null +++ b/examples/cpp_plugin/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(cpp_example_1 example_1.cpp) +target_link_libraries(cpp_example_1 ${LIBRARY_TARGET}) + +add_executable(cpp_example_2 example_2.cpp) +target_link_libraries(cpp_example_2 ${LIBRARY_TARGET}) + +add_executable(cpp_example_3 example_3.cpp) +target_link_libraries(cpp_example_3 ${LIBRARY_TARGET}) + +add_library(cpp_plugin SHARED cpp_plugin.cpp) diff --git a/examples/cpp_plugin/cpp_plugin.cpp b/examples/cpp_plugin/cpp_plugin.cpp new file mode 100644 index 0000000..0fd23f6 --- /dev/null +++ b/examples/cpp_plugin/cpp_plugin.cpp @@ -0,0 +1,50 @@ +#include "cpp_plugin.h" + +#include +#include +#include + +#include + +BOOST_DLL_ALIAS(SimplePluginA::create, create_a); + +int global_variable = 0; + +void SimplePluginA::loop() +{ + while (true) { + doSomething(); + } +} + +void SimplePluginA::doSomething() +{ + std::cout << "A: " << i_ << ", global: " << global_variable << '\n'; + sleep(std::chrono::seconds { 1 }); +} + +class SimplePluginB : public SimplePluginInterface { +public: + SimplePluginB() = default; + + static std::shared_ptr create() + { + return std::make_shared(); + } + + void initialize(int value) override { i_ = std::make_unique(value); } + void doSomething() override + { + std::cout << "B: " << *i_ << ", global: " << global_variable << '\n'; + sleep(std::chrono::seconds { 2 }); + } + + // cannot be accessed by program that loads this plugin because + // it is hidden in this source file without a virtual function + // in the parent class + void inaccessible() { } + +private: + std::unique_ptr i_; +}; +BOOST_DLL_ALIAS(SimplePluginB::create, create_b); diff --git a/examples/cpp_plugin/cpp_plugin.h b/examples/cpp_plugin/cpp_plugin.h new file mode 100644 index 0000000..e48d365 --- /dev/null +++ b/examples/cpp_plugin/cpp_plugin.h @@ -0,0 +1,49 @@ +#ifndef PPPLUGIN_EXAMPLES_CPP_PLUGIN_H +#define PPPLUGIN_EXAMPLES_CPP_PLUGIN_H + +#include +#include +#include + +class SimplePluginInterface { +public: + SimplePluginInterface() = default; + virtual ~SimplePluginInterface() = default; + SimplePluginInterface(const SimplePluginInterface&) = default; + SimplePluginInterface(SimplePluginInterface&&) noexcept = default; + SimplePluginInterface& operator=(const SimplePluginInterface&) = default; + SimplePluginInterface& operator=(SimplePluginInterface&&) noexcept = default; + + virtual void initialize(int /*value*/) { } + + virtual void doSomething() = 0; + +protected: + static void sleep(std::chrono::milliseconds duration) + { + std::this_thread::sleep_for(duration); + } +}; + +class SimplePluginA : public SimplePluginInterface { +public: + SimplePluginA() = default; + + static std::shared_ptr create() + { + return std::make_shared(); + } + + // must be virtual to be accessible for the program that loads this plugin; + // otherwise, trying to call the function will cause a linker error + virtual void loop(); + + void doSomething() override; + +private: + int i_ { 0 }; +}; + +extern int global_variable; + +#endif // PPPLUGIN_EXAMPLES_CPP_PLUGIN_H diff --git a/examples/cpp_plugin/example_1.cpp b/examples/cpp_plugin/example_1.cpp new file mode 100644 index 0000000..e846171 --- /dev/null +++ b/examples/cpp_plugin/example_1.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +// library headers +#include "ppplugin/cpp/plugin.h" +#include "ppplugin/errors.h" + +// header of example plugin +#include "cpp_plugin.h" + +int main() +{ + auto&& load_result = ppplugin::CppPlugin::load("./libcpp_plugin.so"); + if (!load_result) { + std::cerr << load_result.error() << '\n'; + return 1; + } + + // loading plugin was successful, call a function that returns "std::shared_ptr" + auto&& plugin = load_result.value(); + auto&& call_result = plugin.call>("create_a"); + if (!call_result) { + std::cerr << call_result.error() << '\n'; + return 2; + } + + // unwrap instance of newly created SimplePluginA and call function that was + // unknown to this program until the plugin was loaded (defined in cpp_plugin.cpp) + auto&& simple_plugin_a = call_result.value(); + simple_plugin_a->loop(); +} diff --git a/examples/cpp_plugin/example_2.cpp b/examples/cpp_plugin/example_2.cpp new file mode 100644 index 0000000..24fa00f --- /dev/null +++ b/examples/cpp_plugin/example_2.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ppplugin/cpp/plugin.h" +#include "ppplugin/errors.h" +#include "ppplugin/plugin_manager.h" + +#include "cpp_plugin.h" + +int main(int /*argc*/, char* argv[]) +{ + auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); + auto library_path = executable_dir / "libcpp_plugin.so"; + // setup manager - has to stay alive for as long as you want to use the plugins + ppplugin::PluginManager manager; + // load plugin library and exit on error + auto plugin = manager.loadCppPlugin(std::filesystem::path { library_path }) + .valueOrElse([](const auto& error) -> ppplugin::CppPlugin { + std::cerr << "Unable to load plugin: " << error; + std::exit(1); // NOLINT(concurrency-mt-unsafe) + }); + + auto simple_plugin_a = plugin.call>("create_a").valueOr(nullptr); + // create instance of type SimplePluginB which is fully unknown to this program; + // the entire class definition is in cpp_plugin.cpp which is not compiled into this binary + auto simple_plugin_b = plugin.call>("create_b").valueOr(nullptr); + + if (simple_plugin_a) { + simple_plugin_a->initialize(1000); + simple_plugin_a->doSomething(); + } + if (simple_plugin_b) { + simple_plugin_a->initialize(2000); + simple_plugin_b->doSomething(); + } +} diff --git a/examples/cpp_plugin/example_3.cpp b/examples/cpp_plugin/example_3.cpp new file mode 100644 index 0000000..f226455 --- /dev/null +++ b/examples/cpp_plugin/example_3.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ppplugin/cpp/plugin.h" +#include "ppplugin/errors.h" +#include "ppplugin/plugin_manager.h" + +#include "cpp_plugin.h" + +namespace { +std::mutex output_mutex; + +template +std::thread initializeAndLoop(ppplugin::CppPlugin& plugin, const std::string& function_name) +{ + auto result = plugin.call>(function_name); + if (result) { + auto& plugin = *result; + plugin->initialize(100); + return std::thread { [plugin]() { + while (true) { + std::lock_guard lock { output_mutex }; + plugin->doSomething(); + } + } }; + } + 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[]) +{ + try { + if (argc < 1) { + return -1; + } + auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); + auto library_path = executable_dir / "libcpp_plugin.so"; + // setup manager - has to stay alive for as long as you want to use the plugins + ppplugin::PluginManager manager; + // load plugin library and exit on error + 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.code()); + std::exit(1); // NOLINT(concurrency-mt-unsafe) + }); + + auto thread_a = initializeAndLoop(plugin_library, "create_a"); + auto thread_b = initializeAndLoop(plugin_library, "create_b"); + thread_a.join(); + thread_b.join(); + } catch (const std::exception& exception) { + std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; + return 1; + } catch (...) { + std::cerr << "An unknown fatal error occurred!\n"; + return 1; + } + return 0; +}