DelegateMQ is a modular C++ messaging library with a header-only core. It provides a unified, thread-safe API for invoking any callable (e.g., function, method, lambda) across different execution contexts:
- Synchronous & Asynchronous: Local thread-safe callbacks, blocking or fire-and-forget.
- Signal / Slot: Decoupled, multicast event handling with built-in async support.
- DataBus (DDS Lite): Topic-based publish/subscribe with thread-safe async data distribution.
- Remote: Inter-process (IPC) and inter-processor communication over any transport.
The library is unit-tested, built for portability (Windows, Linux, RTOS, bare metal), and lets you include only the features you need without unwanted overhead.
Applications typically use one mechanism for callbacks, another for inter-thread messaging, and a third for inter-processor communication — each with its own API and data-passing rules. Yet all three solve the same underlying problem: move argument data to a target function and invoke it.
DelegateMQ unifies them in a single library. Bind a delegate to any callable — free function, method, or lambda — and invoke it synchronously, on another thread, or on another processor. The library handles the copying, marshalling, and dispatch; the call site stays the same.
Why choose DelegateMQ over a callback, signal/slot, or messaging library.
| Advantage | Description |
|---|---|
| One invocation model | Sync, async, and remote calls share the same dmq::MakeDelegate syntax — promoting a local callback to async or remote is a one-line change. |
| Targeted thread dispatch | Handlers specify the thread they run on; the library queues and dispatches the call, so code always executes in the correct thread context. |
| Application owns every thread | The library creates no internal threads — scheduling, stack sizes, and watchdogs stay explicit and auditable. |
| Off-target development | Embedded application logic builds and runs on a Windows or Linux host for testing; moving to hardware swaps only the thread port and transport. |
| Location transparency | DataBus subscribers receive data identically whether the publisher is in the same thread, another process, or a remote processor. |
Numerous platform, serialization, and transport integrations are available out of the box. Adding support for a new OS, serializer, or transport requires only implementing a small pure-virtual interface.
| Category | Supported |
|---|---|
| Operating Systems | Windows, Linux, FreeRTOS, ThreadX, Zephyr, CMSIS-RTOS2, Qt, Bare-metal |
| Serialization | MessagePack, RapidJSON, Cereal, Bitsery, MessageSerialize |
| Transport | ZeroMQ, NNG, MQTT, Serial Port, TCP, UDP, ARM LwIP, ThreadX NetX/Duo, Zephyr Networking, data pipe, memory buffer |
CMake is used to create the project build files on any Windows or Linux machine. DelegateMQ supports Visual Studio, GCC, Clang, and ARM toolchains.
Clone and build the main delegate application. No third-party libraries needed.
git clone https://github.com/DelegateMQ/DelegateMQ.git
cd DelegateMQ
cmake -B build
cmake --build buildRun the built executable:
# Windows
build\delegate_app\Debug\delegate_app.exe
# Linux
./build/delegate_app/delegate_appSee Example Projects for stand-alone projects demonstrating core features like Signal/Slot and async delegates, remote communication (TCP, UDP, ZeroMQ, MQTT), and more.
A delegate is a type-safe wrapper around any callable — function, method, or lambda — that can be stored and invoked later, in a different context or on a different thread. DelegateMQ builds everything on this one primitive: the same delegate that fires a local callback can also dispatch onto a worker thread or a remote processor.
The OS, transport, and serializer sit behind small pure-virtual interfaces, so underlying technologies swap without changing application logic:
dmq::MakeDelegate– Creates a delegate bound to any callable.dmq::os::Thread– A cross-platform thread class. Passed todmq::MakeDelegateto dispatch a call onto a specific worker thread.dmq::Signal<Sig>– Thread-safe multicast signal.Connect()returns admq::ScopedConnectionthat auto-disconnects on scope exit.dmq::MulticastDelegateSafe– Thread-safe delegate container for broadcast invocation without RAII connection management.
Synchronous delegates invoke the target function anonymously within the current execution context. No external library or OS dependencies are required.
#include "DelegateMQ.h"
size_t MsgOut(const std::string& msg)
{
std::cout << "[" << std::this_thread::get_id() << "] " << msg << std::endl;
return msg.size();
}
int main()
{
// 1. Synchronous Invocation
auto sync = dmq::MakeDelegate(&MsgOut);
sync("Invoke MsgOut sync!");
return 0;
}Asynchronous delegates simplify multithreaded programming by allowing you to invoke functions across thread boundaries safely and effortlessly. The library automatically marshals all arguments—whether passed by value, pointer, or reference—ensuring thread safety without manual locking or complex queue management.
dmq::os::Thread thread("WorkerThread");
thread.CreateThread();
// 1. Asynchronous Invocation (Non-blocking / Fire-and-forget)
auto async = dmq::MakeDelegate(&MsgOut, thread);
async("Invoke MsgOut async (non-blocking)!");
// 2. Asynchronous Invocation (Blocking / Wait for result)
auto asyncWait = dmq::MakeDelegate(&MsgOut, thread, dmq::WAIT_INFINITE);
size_t size = asyncWait("Invoke MsgOut async wait (blocking)!");
// 3. Asynchronous Invocation with Timeout
auto asyncWait1s = dmq::MakeDelegate(&MsgOut, thread, std::chrono::seconds(1));
auto retVal = asyncWait1s.AsyncInvoke("Invoke MsgOut async wait (blocking max 1s)!");
if (retVal.has_value()) // Async invoke completed within 1 second?
size = retVal.value(); // Get return valueMultiple asynchronous delegates can also be stored in a dmq::MulticastDelegateSafe container and invoked as a group — a common pattern for completion callbacks, where each registered callback executes on its own target thread:
// Asynchronous Callback Pattern (broadcast to all registered callbacks)
dmq::MulticastDelegateSafe<void(int)> onComplete;
onComplete += dmq::MakeDelegate([](int result) {
// Callback executed on 'thread'
}, thread);
onComplete(123);dmq::Signal<Sig> is a thread-safe multicast signal. Emit it like a function call; each connected slot receives the call independently. DelegateMQ signals support both synchronous and asynchronous dispatch, meaning slots can be executed in the caller's context or automatically queued onto a target worker thread chosen at connect time. Connect() returns a dmq::ScopedConnection that auto-disconnects when it goes out of scope — no manual unsubscribe needed.
Declare the signal as a plain class member — no shared_ptr or heap allocation required:
class Button
{
public:
dmq::Signal<void(int buttonId)> OnPressed; // plain member
void Press(int id) { OnPressed(id); } // emit to all connected slots
};Connect a slot and store the dmq::ScopedConnection for automatic lifetime management:
class UI
{
public:
UI(Button& btn) : m_thread("UIThread")
{
m_thread.CreateThread();
// Slot dispatched to m_thread on every Press()
m_conn = btn.OnPressed.Connect(
dmq::MakeDelegate(this, &UI::HandlePress, m_thread)
);
}
// No explicit disconnect needed — m_conn disconnects when UI is destroyed
private:
void HandlePress(int buttonId) { std::cout << "Button " << buttonId << "\n"; }
dmq::os::Thread m_thread;
dmq::ScopedConnection m_conn;
};
Button btn;
{
UI ui(btn);
btn.Press(1); // UI::HandlePress queued on UIThread
} // ui destroyed -> m_conn disconnects
btn.Press(2); // safe: no subscribers, nothing happensdmq::databus::DataBus is a type-safe, topic-based publish/subscribe system (similar to a lightweight DDS or MQTT) built on DelegateMQ delegates. It works across local threads and remote network nodes alike. Each subscriber specifies the thread that receives its data, so delivery always lands in the correct thread context with no manual queuing or synchronization.
DataBus creates no internal threads and is small enough for embedded targets, unlike full DDS systems. Quality of Service (QoS) options such as Last Value Cache (LVC) deliver the most recent value to new subscribers immediately.
#include "DelegateMQ.h"
// 1. Subscribe to a topic (dispatched to a worker thread)
auto conn = dmq::databus::DataBus::Subscribe<float>("sensor/temp", [](float value) {
std::cout << "Received temp: " << value << std::endl;
}, &workerThread);
// 2. Publish data to the topic
dmq::databus::DataBus::Publish<float>("sensor/temp", 25.5f);
// 3. Optional: Enable Last Value Cache (LVC)
dmq::databus::QoS qos;
qos.lastValueCache = true;
auto conn2 = dmq::databus::DataBus::Subscribe<int>("status", [](int s) {
// New subscribers get the last published value immediately
}, &workerThread, qos);Remote delegates send a function call across a process or network boundary. Both sides share a message ID; the receiver binds a member function to it, and the sender's invocation is serialized, transported, and executed remotely — just like a normal function call.
constexpr dmq::DelegateRemoteId TEMPERATURE_ID = 1;
// --- Receiver process: bind a handler to the ID ---
dmq::RemoteChannel<void(float)> rx(transport, serializer);
rx.Bind(&logger, &DataLogger::OnTemperature, TEMPERATURE_ID);
dmq::RegisterEndpoint(TEMPERATURE_ID, rx.GetEndpoint());
// --- Sender process: invoke the channel to send ---
dmq::RemoteChannel<void(float)> tx(transport, serializer, TEMPERATURE_ID);
tx(25.5f); // invoke; argument is serialized and sent to the receiverAny transport (ZeroMQ, TCP, UDP, serial, ...) and serializer (MessagePack, Cereal, ...) can carry the call. See Design Details for the complete pattern and Example Projects for working programs with real transports.
DelegateMQ includes three diagnostic TUI (Terminal User Interface) consoles for real-time monitoring of DataBus traffic, network topology, and thread performance. Monitoring runs through an asynchronous bridge and never blocks the application. Built with FTXUI, the consoles run cross-platform in any terminal and support regex filtering for traffic analysis:
| Tool | Purpose |
|---|---|
dmq-spy |
Real-time live feed of all DataBus messages — acts as a "Software Logic Analyzer" |
dmq-monitor |
Live network topology view — shows all active nodes, status, uptime, and published topics |
dmq-thread |
Performance dashboard — monitors thread health, queue depths, and dispatch latency (Avg/Max) |
DelegateMQ at a glance.
| Category | DelegateMQ |
|---|---|
| Purpose | Unify callable invocation across threads, processes, and networks |
| Usages | Callbacks (synchronous and asynchronous), asynchronous API's, communication and data distribution, and more |
| Library | Allows customizing data sharing between threads, processes, or processors |
| Object Lifetime | Thread-safe management via smart pointers (std::weak_ptr) prevents async invocation on destroyed objects (no dangling pointers). |
| Complexity | Lightweight and extensible through external library interfaces and full source code |
| Threads | No internal threads. External configurable thread interface portable to any OS (dmq::IThread). |
| Watchdog | Configurable timeout to detect and handle unresponsive threads. |
| Signal and Slots | Standard Signal-Slot pattern (dmq::Signal<Sig>). Connect() returns a dmq::ScopedConnection for RAII auto-disconnect. Thread-safe by default; no shared_ptr required. |
| Multicast | Broadcast invoke anonymous callable targets onto multiple threads |
| DataBus | Topic-based middleware distribution system (DDS Lite) across threads or remote nodes |
| Interop | C#, Python, and others supported via a Shared Native Core architecture. See Cross-Language Interop. |
| Message Priority | Asynchronous delegates support prioritization to ensure timely execution of critical messages |
| Serialization | External configurable serialization data formats, such as MessagePack, RapidJSON, or custom encoding (dmq::ISerializer) |
| Transport | External configurable transport, such as ZeroMQ, TCP, UDP, serial, data pipe or any custom transport (dmq::transport::ITransport) |
| Transport Reliability | Provided by the built-in reliability layer (dmq::util::ReliableTransport) or communication library (e.g. ZeroMQ, nng, TCP/IP stack). |
| Message Buffering | Remote delegate message buffering provided by a communication library (e.g. ZeroMQ) or custom solution within transport |
| Dynamic Memory | Heap or DelegateMQ fixed-block allocator |
| Debug Logging | Debug logging using spdlog C++ logging library |
| Error Handling | Configurable for return error code, assert or exception |
| Embedded Friendly | Yes. Any OS such as Windows, Linux and FreeRTOS. An OS is not required (i.e. "super loop"). |
| Operating System | Any. Custom dmq::IThread implementation may be required. |
| Language | C++17 or higher |
| Guide | Description | Key Focus |
|---|---|---|
| Tutorial | Step-by-step system evolution | Adoption & Use Cases |
| Build | Build & configuration guide | CMake & Setup |
| Signals | Decoupled event handling | Signal/Slot RAII |
| DataBus | Topic-based pub/sub middleware | DDS Lite & Networking |
| Design Details | Deep technical architecture | API Reference |
| Porting Guide | OS & Hardware abstraction | Platform Support |
| Interop | Multi-language integration | C# & Python |
| Tools | Diagnostic TUI dashboards | Spy & Monitor |
| Comparison | Middleware benchmarks | Tradeoff Analysis |
Repositories utilizing the DelegateMQ library.
| Project | Description |
|---|---|
| Integration Test Framework | A multi-threaded C++ software integration test framework using Google Test and DelegateMQ libraries. |
| Active-Object State Machine in C++ | A modern active-object C++ finite state machine providing RAII-safe asynchronous dispatch and pub/sub signals. |
| Async-SQLite | An asynchronous SQLite thread-safe wrapper implemented using an asynchronous delegate library. |
| Async-DuckDB | An asynchronous DuckDB thread-safe wrapper implemented using an asynchronous delegate library. |
| Async-HTTP | An asynchronous HTTP thread-safe client wrapper implemented using an asynchronous delegate library. |
