IO-Handler C++ Example
Description
Section titled “Description”This example demonstrates how to write an IO-Handler that connects to an RTDB-based application as a TCP/IP client, using the TcpipClient library. The example is built with C++ Qt Widgets (6.11.1) and targets the IO_Example StateWORKS specification project.
Architectural note
Section titled “Architectural note”In a real deployment, an IO-Handler reads physical hardware inputs (sensors, switches) and writes their values into the RTDB, and conversely receives RTDB output values and drives physical hardware (relays, actuators). Despite its close relationship to hardware, the IO-Handler has no privileged architectural role — it connects to the RTDB server as a TCP/IP client, exactly like a UI or monitoring tool would.
This example reflects that: it is a regular TcpipClient that both pokes input values into the RTDB and receives output values from it. In production, the manual UI controls would be replaced by actual hardware calls. The client role stays the same.
API surface
Section titled “API surface”The functions available via TcpipClient are declared in TcpipClient_Dll.h:
// Initialize with a callback function called on every RTDB eventvoid Initialize( void (*ReplyOrEvent)(int Rep, const string &stName, const string &stVal, void *pOwner), void *pOwner);
// Connect to / disconnect from the RTDB serverbool Connect(char *pstIPAddress = HOST, int nPort = PORTNUMBER);bool Connected();void Disconnect(bool bWithUnAdvise = false);
// Read a value on demandunsigned int Request(char *pstItemName, e_ItemAttributes eIAtt = IAtt_None, char *pstValue = NULL);
// Write a value into the RTDBunsigned int Poke(char *pstItemName, char *pstValue, e_ItemAttributes eIAtt = IAtt_None);
// Subscribe to / unsubscribe from value change eventsunsigned int AdviseStart(char *pstItemName, e_ItemAttributes eIAtt = IAtt_None, char *pstValue = NULL);unsigned int AdviseStop(char *pstItemName, e_ItemAttributes eIAtt = IAtt_None);void UnAdviseAll();Code walkthrough
Section titled “Code walkthrough”InitializeRTDBServerConnection() handles the full connection setup using three library functions:
- initializes the callback
RepEv()usingInitialize(), called whenever an event arrives from the RTDB server - connects to the RTDB server using
Connect() - subscribes to output value changes using
AdviseStart()
The callback RepEv() may receive the RawData value of Do:001, the DataValue of No:001, or notification that the RTDB server has exited.
OnDi1Clicked() and OnNi1EditingFinished() use Poke() to write Di:001 and Ni:001 into the RTDB — simulating what a real hardware driver would do.
~MainWindow() calls Disconnect() when the application closes.
Two further library functions are available but not explicitly called here:
Receive()— delivers a requested value from the RTDB on demand, at any timeAdviseStop()— seldom called explicitly; invoked internally byDisconnect()
The mainwindow.h declares the callback dispatch slots and the connection. Full implementation mainwindow.cpp shows the complete client lifecycle:
#include "mainwindow.h"#include "ui_mainwindow.h"#include "tcpipclient_dll.h"
// The callback function — called by the TcpipClient event thread on every RTDB event.// Dispatch UI updates back to the main thread via QMetaObject::invokeMethod.void RepEv(int Rep, const std::string &stName, const std::string &stVal, void *pOwner){ Q_UNUSED(Rep); MainWindow *wnd = static_cast<MainWindow *>(pOwner); if (!wnd) return;
if (stName == "Do:001.Raw") { QString value = QString::fromStdString(stVal); QMetaObject::invokeMethod( wnd, [wnd, value]() { wnd->UpdateDo1(value); }, Qt::QueuedConnection); } if (stName == "No:001.Dat") { QString value = QString::fromStdString(stVal); QMetaObject::invokeMethod( wnd, [wnd, value]() { wnd->UpdateNo1(value); }, Qt::QueuedConnection); } if (stName == "IL" && stVal == "exited?") { QMetaObject::invokeMethod(wnd, [wnd]() { wnd->Terminate(); }, Qt::QueuedConnection); }}
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow){ ui->setupUi(this); connect(ui->connect_pushButton, &QPushButton::clicked, this, &MainWindow::OnConnectClicked); connect(ui->Di1_low_radioButton, &QRadioButton::toggled, this, &::MainWindow::OnDi1Clicked); connect(ui->Di1_high_radioButton, &QRadioButton::toggled, this, &::MainWindow::OnDi1Clicked); connect(ui->Ni1_lineEdit, &QLineEdit::editingFinished, this, &MainWindow::OnNi1EditingFinished); ui->statusbar->showMessage("Disconnected");}
MainWindow::~MainWindow(){ Disconnect(true); // unadvise all before exit delete ui;}
// Initialize callback, connect to RTDB, and subscribe to outputs.bool MainWindow::InitializeRTDBServerConnection(){ Initialize(RepEv, this);
QByteArray host = ui->ip_lineEdit->text().toLocal8Bit(); int port = ui->port_lineEdit->text().toInt();
bool ok = Connect(host.data(), port); ui->statusbar->showMessage(ok ? "Connected to RTDB server" : "Could not connect to RTDB server");
if (Connected()) { char cValue[8]; // AdviseStart triggers RepEv immediately with the current value, // so no explicit Request() call is needed on startup. AdviseStart("Do:001", IAtt_RawData, &cValue[0]); AdviseStart("No:001", IAtt_DataValue, &cValue[0]); return true; } return false;}
void MainWindow::Terminate(){ close();}
void MainWindow::UpdateDo1(const QString &value){ ui->Do1_label->setText(value);}
void MainWindow::UpdateNo1(const QString &value){ ui->No1_label->setText(value);}
void MainWindow::OnConnectClicked(){ if (!InitializeRTDBServerConnection()) close(); ui->connect_frame->setDisabled(true); ui->action_frame->setEnabled(true);}
// Poke Di:001 into the RTDB — simulates a hardware digital input changing state.void MainWindow::OnDi1Clicked(){ char c = ui->Di1_high_radioButton->isChecked() ? '1' : '0'; Poke("Di:001", &c, IAtt_RawData);}
// Poke Ni:001 into the RTDB — simulates a hardware analog input changing value.void MainWindow::OnNi1EditingFinished(){ QByteArray data = ui->Ni1_lineEdit->text().toLatin1(); Poke("Ni:001", data.data(), IAtt_RawData);}Testing
Section titled “Testing”Start SWExecStandard.exe first. On first launch you will be asked for the specification file to execute (.../Examples-Web/IO_Example/Conf/IO_Example.swd). The path is stored in .RTDB_Conf.par and reused on subsequent starts. To switch specification files, delete .RTDB_Conf.par or pass -cNAME on the command line.
Then start the IO handler application and connect.

The window shows:
| Inputs | Outputs |
|---|---|
DI — Digital input | DO — Digital output |
NI — Numerical (analog) input | NO — Numerical (analog) output |
| → Manually set to simulate hardware input | → Driven by the RTDB and displayed here |
Use SWMon alongside this window for testing. Setting DI and NI here simulates incoming hardware signals — watch the effects in SWMon. Conversely, changes to DO and NO triggered via SWMon will be reflected in this window.
