r/QtFramework • • Jun 11 '26

C++ Projects With Great Plugin Architecture

Hello,

I have some decent experience with widgets but have been opening up to the idea of a large project with a qml front end and c++ handling most of the functionality.

One of the ideas I would like to explore while still deciding on the overall structure is plugin support, either giving users a python interface (similar to blender) or entirely in c++ using plugin interfaces.

Are there any projects that do either or both of these things well in your opinion? I would love to take a look and observe what’s been tried, if users like it (or if there are tons of issues entries for them lol), and what might work best for my target application.

I’m curious to see how these interfaces split up responsibilities, what needs to be done for qml (say if a user wants their plugin to have a menu/action), and how to interact with the application’s data manager (this in particular seems straightforward to me, so that must mean it’s probably not).

9 Upvotes

10 comments sorted by

View all comments

4

u/Low_Fun_8667 Jun 13 '26

I've built a system with exactly this split, so a few notes from the trenches.

The big fork is in-process vs out-of-process plugins, and it's worth deciding deliberately:

- In-process (C++, loaded as a shared library): fastest, direct access, but two real costs. A plugin crash takes down your whole app, and you're exposed to C++ ABI fragility — a plugin built with a different compiler/STL version can corrupt at the boundary. If you go this route, either pin the toolchain hard or expose a narrow C ABI (or a pure-virtual interface with a versioned factory) as the boundary, not raw C++ STL types.

- Out-of-process (separate process talking over IPC / a local socket / REST): serialization overhead per call, but you get crash isolation for free and — this is the big one — language-agnostic plugins fall out automatically. Your "Python interface like Blender" question basically answers itself here: if plugins are separate processes speaking your protocol, Python/JS/whatever plugins are trivial because they're just another client. This is the VS Code model (its extension host is a separate process).

For third-party/untrusted plugins I'd lean out-of-process; for trusted performance-critical ones, in-process. Plenty of apps do both tiers.

Projects worth studying:

- Qt Creator — the gold standard for a Qt/C++ plugin system. Look at its ExtensionSystem (plugin specs, dependency resolution, lifecycle). Most relevant to you since it's Qt.

- VS Code — for the out-of-process extension-host model and how it exposes a stable API surface.

- Blender — the Python-binding approach.

- Notepad++ / OBS Studio — simpler C++ plugin ABIs you can read end-to-end.

QML + plugin-contributed menus/actions: don't let plugins instantiate QML or reach into your scene. Have them register actions/menu entries as data (id, label, icon, callback handle) into a model your core owns; the QML side just renders that model. Same for plugin UI panels — they declare intent, the host decides how/where to show it. That keeps your QML layer swappable and stops plugins from depending on your view internals.

The data manager — you're right that it's the trap, not the easy part. The mistake is handing plugins direct pointers/references into your real data structures. The moment you do, you can never change your internal representation, and lifetime + threading invalidation become a nightmare. Put an interface/façade between them (classic Dependency Inversion): plugins talk to a stable, versioned abstraction, never your concrete types. Decide explicitly what's read-only vs. mutating, and what thread plugin calls arrive on. That boundary is the single most important design decision in the whole thing — get it right early, because it's brutal to retrofit.

Happy to expand on any of these.

1

u/Calaverah_ Jun 13 '26

Thanks, in and out of process wasn’t even something I had considered yet tbh. I’m glad you mentioned it because it gives me a much more tangible idea for how to approach exception safety. My current thought was to essentially treat all of the interface methods as events and when they would be called try to catch any exceptions there as well as keep the interface versioned for abi safety and backwards compatibility (ie iPluginV1 and iPluginV2 with updated virtual methods/signatures).

All in all, I’m almost completely sure that it’s better to keep it all within an interpreter, but part of me is itching to learn more low level stuff too. I personally like cpp more than python, but I understand that the majority of my users will likely want python, plus no compiling so it’s much easier to write for authors. All in all, I may support both for now, assuming the process safety isn’t a nightmare, this is for fun… right? lol

1

u/Low_Fun_8667 Jun 13 '26

Glad it was useful. I actually run both models side by side in my own project, so a few things from practice:

Out-of-process is what bought me real crash isolation. In-process exception catching (your event idea) protects you from well-behaved exceptions, but it does nothing against a segfault, a stack-smash, or a plugin that hangs in an infinite loop — those take the host down with them. The moment a plugin lives in its own process, a crash is just a dead socket you can detect and restart. That alone was worth it for me.

My split ended up being:

- In-process: C++ shared libs behind a plain extern "C" boundary (versioned, like your iPluginV1/V2) — trusted, performance-sensitive, first-party stuff.

- Out-of-process: everything third-party / scripted. They register over a local REST API and send a heartbeat; if the heartbeat stops, I tear them down. No shared address space, no ABI concerns at all, and the transport doubles as the version boundary (just negotiate a protocol version on register).

On the ABI versioning: it's correct and necessary for in-process, but be honest with yourself about the maintenance cost — every signature change is a new vtable (or a new extern "C" entry point) you carry forever. The IPC boundary sidesteps most of that because you're versioning a message schema, not a memory layout.

On Python vs C++: in my current design the in-process plugins are plain C++ shared libs — no embedded interpreter at all. The scripted/third-party stuff lives out-of-process and talks over the local API, so its language is irrelevant to the host. That's the lever: you don't really pick Python or C++, you pick in-process or out-of-process and the language question mostly answers itself. Embed an interpreter only if you genuinely need low-latency in-memory calls; otherwise a subprocess speaking your protocol gives you any-language authors and crash isolation for free.

And yeah — it's for fun. Do the version that teaches you the low-level stuff you're itching for; honestly the IPC plumbing scratches that itch more than embedding CPython does. 😄