{"id":16428118,"url":"https://github.com/remileduc/qt_plugin_entrypoint","last_synced_at":"2026-04-27T18:34:49.610Z","repository":{"id":75661974,"uuid":"202350806","full_name":"remileduc/qt_plugin_entrypoint","owner":"remileduc","description":"Entry points python style for qt plugins","archived":false,"fork":false,"pushed_at":"2019-09-20T08:47:38.000Z","size":39,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-07T11:06:28.071Z","etag":null,"topics":["cpp17","entrypoint","plugin","qmetatype","qt","reflection"],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/remileduc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-08-14T12:56:08.000Z","updated_at":"2019-09-20T08:47:39.000Z","dependencies_parsed_at":"2023-06-07T07:15:31.944Z","dependency_job_id":null,"html_url":"https://github.com/remileduc/qt_plugin_entrypoint","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/remileduc/qt_plugin_entrypoint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remileduc%2Fqt_plugin_entrypoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remileduc%2Fqt_plugin_entrypoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remileduc%2Fqt_plugin_entrypoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remileduc%2Fqt_plugin_entrypoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/remileduc","download_url":"https://codeload.github.com/remileduc/qt_plugin_entrypoint/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remileduc%2Fqt_plugin_entrypoint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32349843,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T17:12:42.749Z","status":"ssl_error","status_checked_at":"2026-04-27T17:12:41.658Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp17","entrypoint","plugin","qmetatype","qt","reflection"],"created_at":"2024-10-11T08:15:12.591Z","updated_at":"2026-04-27T18:34:49.602Z","avatar_url":"https://github.com/remileduc.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\n    MIT License\n\n    Copyright (c) 2019 Rémi Ducceschi\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n--\u003e\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n\nqt_plugin_entrypoint\n====================\n\nDemonstration of entry points in C++ thanks to Qt.\n\nThe goal is to be able to implement entry points, in the sense of Python ([pkg_resources entry points](https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points)), in a C++ Qt plugin architecture.\n\nSee section [Why](#why) below for more information.\n\n[How to use](#how-to-use)\n\n- [Prerequisites](#prerequisites)\n- [Compile](#compile)\n- [Run](#run)\n\n[Why](#why)\n\n[Code explanation](#code-explanation)\n\n- [Architecture](#architecture)\n- [Features](#features)\n- [How it works](#how-it-works)\n\n[Improvements](#improvements)\n\nHow to use\n----------\n\nIn order to run this project, you need to install some stuff and compile.\n\nIt should work on both Linux and windows.\n\n### Prerequisites ###\n\nYou need the following:\n\n- Qt 5.9+\n- a C++17 compiler (tested on MSVC 2019 and G++ 8.3)\n- CMake 3.10+ (only tested with 3.14)\n\n### Compile ###\n\nYou have to make the project with CMake:\n\n```bash\nmkdir build\ncd build\ncmake -G \"Unix Makefiles\" -A x64../source\n```\n\nYou can use the CMake generator of your choice: \u003chttps://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#cmake-generators\u003e\n\nIf you prefer, you can open the [source/CMakeLists.txt](./source/CMakeLists.txt) fils in QtCreator, it will automatically generate the project for you.\n\nThen, you can build with your IDE or directly with\n\n```bash\nmake\n```\n\n### Run ###\n\nOnce compiled, you can launch the created executable `MainApp` or `MainApp.exe`. If everything went fine, it will automatically load the 3 plugins that have been build next to the executable (`.dll` or `.so` files).\n\nNote that the executable should be located in the subfolder `build/main_app`.\n\nWhy\n----\n\nFor the development of [SurveyPad](https://gitlab.cern.ch/apc/susofts/interfaces/SurveyPad/), we need an architecture based on plugins. Plugins can be written by anybody, so their code is in seperated repositories. Thus, plugins can't have any dependencies between each others. And you can't assume that a plugin will necessarily always be here at run time...\n\nHowever, we have reached a point where we would like some plugin to expose, or to use, an entry point, as defined by [pkg_resources](https://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points) in Python:\n\n```\n                   +-------------+\n                   | Application |\n                   +------+------+\n                          |\n                   +------+------+\n                   |    Utils    |\n                   +------+------+\n       +--------------^   ^   ^--------------+\n       |                  |                  |\n+------+------+    +------+------+    +------+------+\n|  Plugin 1   |    |  Plugin 2   |    |  Plugin 3   |\n+-------------+    +-------------+    +-------+-----+\n         | |           |                 |\n         | +-----------+                 |\n         +-------------------------------+\n```\n\nGlobally, we would like to implement the 2 lines at the bottom, between `Plugin 1` and `Plugin 2`, and between `Plugin 1` and `Plugin 3`, without adding any dependencies between the plugins.\n\nOne solution could be to add some interfaces in `Utils`, but we don't want plugin specific code there...\n\nSo we took the other solution: the one with entry points. Globally, one plugin says in its metadata that he has an entry point (represented by a class name) that others can use, if they want. Then, another plugin that wants to use this entry point can look for it amoung all the plugins that are available at run time. If it finds the correct entry point, it can use it.\n\nIn reallity, he needs to instantiate the class exposed by the the first one in its metadata. And here we face the main problems:\n\n1. instantiate an object from its class name as a string?\n\t- in Python, you can\n\t- in C++, NO! (the closest you can have are templates, but you need to know the class at compile time...)\n2. run a method from a class you don't know\n\t- in Python, you use duck typing (don't have to inherit an interface to implement it)\n\t- in C++, NO! (you must inherit a class to implement its interface)\n\nHopefully, both points can be fixed by [QMetaObject](https://doc.qt.io/qt-5/qmetaobject.html) (another solution is to use [QMetaType](https://doc.qt.io/qt-5/qmetatype.html), see the branch [qmetatype](https://github.com/remileduc/qt_plugin_entrypoint/tree/qmetatype) for that). Dummy example:\n\n```cpp\n#include \u003cQDebug\u003e\n#include \u003cQObject\u003e\n\nclass MyClass : public QObject\n{\n    Q_OBJECT\n\npublic:\n    Q_INVOKABLE MyClass(QObject *parent = nullptr) : QObject(parent) {}\n    Q_INVOKABLE int getValue() const noexcept { return value; }\n    Q_INVOKABLE void setValue(int i) noexcept { value = i; }\n\nprivate:\n    int value;\n};\n\nint main()\n{\n    // This is the object that you can share everywhere, without knowing the class MyClass\n    const QMetaObject \u0026meta = MyClass::staticMetaObject;\n\n    // from now on, we assume we don't know MyClass anymore and we'll just work with the QMetaObject\n    QObject *qobj = meta.newInstance();\n    if (!qobj)\n        return 1;\n    // Now we can use the QObject, assuming we know it has the methods getValue() and setValue()\n    bool callok;\n    callok = QMetaObject::invokeMethod(qobj, \"setValue\", Qt::DirectConnection, Q_ARG(int, 8));\n    if (!callok)\n        return 1;\n    int value;\n    callok = QMetaObject::invokeMethod(qobj, \"getValue\", Qt::DirectConnection, Q_RETURN_ARG(int, value));\n    if (!callok)\n        return 1;\n    qDebug() \u003c\u003c \"LOOOL\" \u003c\u003c value;\n    delete qobj;\n\n    return 0;\n}\n\n#include \"main.moc\" // we need this because we are creating a class inheritting QObject in a CPP file.\n\n```\n\nNote that you need to manipulate the object through the `QMetaObject` interface. You assume that you know what methods the object has\nand howo to call them. In case it is not possible, `QMetaObject` will tell you (return `false`).\n\nCode explanation\n----------------\n\n### Architecture ###\n\nThe project is composed of 4 projects:\n\n- utils: a dynamic library used in all other projects (it is a shared library because it owns the singleton that manages the plugins and the entry points)\n- main_app: the main application\n- plugin_cat: a first plugin representing a cat\n- plugin_dog: a second plugin representing a dog\n- plugin_frog: a third plugin representing a frog\n\nIt is the same as the graphic above, with `plugin_cat` for `Plugin 1`, `plugin_dog` for `Plugin 2` and `plugin_frog` for `Plugin 3`.\n\nGlobally, we have one executable and 4 dynamic libraries (`.dll` or `.so`), 3 created thanks to the [Qt Plugin system](https://doc.qt.io/qt-5/plugins-howto.html#the-low-level-api-extending-qt-applications) and the utils one.\n\nThe `utils` project defines the interface for a plugin, this is the common point for all the others projects. Indeed, `main_app` needs to know this interface on order to be able to load the plugins that implements it. On top of that, it also has the `PluginManager` class that is a singleton, where plugins can register or use entry points.\n\n### Features ###\n\nWhen you launch the application, the following happens:\n\n- plugins are loaded in `main.cpp`\n- the main widget is created in `main.cpp`\n- now you can see what the plugins have to say by selecting a plugin from the combobox.\n\nNow, what we want is to have some communication between the plugins:\n- `plugin_cat` is an enemy of `plugin_dog`\n- `plugin_frog` is a friend of `plugin_dog`\n- `plugin_frog` tries to help `plugin_cat` and tells him when the dog is in the area\n\n### How it works ###\n\n`plugin_dog` can have enemies and friends. Both need to be a class that implements the same function: `QString cry()`. However, if you want to be friend, you need to use the entrypoint `PluginDog_friend`, and use `PluginDog_enemies` to be an enemy.\n\nLet's take a frog for instance. It want to be a friend of dogs, thus, it has a class that implements the `cry()` method: [DogFriend](./source/plugin_frog/DogFriend.hpp). This class is registered on the `PluginDog_friend` entrypoint in the constructor of [PluginFrog](./source/plugin_frog/PluginFrog.cpp#L9-L10).\n\nThus, when `plugin_dog` checks for its friends, it can see that there is an entry point registered, and try to call the `cry` method through a `QMetaObject`: [PluginDog.cpp#L34-L45](./source/plugin_dog/PluginDog.cpp#L34-L45)\n\nSame mechanism happens for the cat.\n\nImprovements\n------------\n\nAny code improvements, suggestions... are welcome!!!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremileduc%2Fqt_plugin_entrypoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fremileduc%2Fqt_plugin_entrypoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremileduc%2Fqt_plugin_entrypoint/lists"}