Back to Home

OPC UA in MFC: integration of open62541

The article describes the technical implementation of an OPC UA client in an MFC application in C++ using open62541. Covered are connecting to Siemens S7 controllers, setting up subscriptions, security, working with SQL Server and features of integration into Windows GUI.

OPC UA in MFC: technical guide for developers
Advertisement 728x90

OPC UA in MFC Applications: Integrating open62541 for Industrial SCADA on Windows

Industrial automation systems are increasingly moving away from monolithic, proprietary SCADA solutions in favor of flexible, secure, and cross-platform OPC UA-based platforms. In this context, developing a lightweight SCADA system based on Microsoft Foundation Classes (MFC) with open62541 integration becomes a technically sound choice for medium-sized enterprises—especially when compatibility with legacy Windows infrastructures and strict latency requirements for communication with Siemens S7-1200/S7-1500 controllers are needed.

Architectural Shift: From Proprietary SCADA to OPC UA–Driven Integration

Traditional SCADA platforms (WinCC, InTouch, EcoStruxure) are built around closed protocols and vendor lock-in. Their deployment requires licensing, extensive configuration, and often results in bloated software installations—setup packages can reach 20 GB, even though only 15–20% of the functionality is actually used. For small and medium-sized businesses, this is economically unjustified.

OPC UA addresses three systemic challenges:

Google AdInline article slot
  • Interoperability: A unified address space (NodeId), typed variables, and a single subscription mechanism allow data aggregation from equipment by different manufacturers without intermediate gateways.
  • Out-of-the-Box Security: Support for X.509 certificates, security policies (None, Basic128Rsa15, Basic256Sha256), and protocol-level encryption and authentication.
  • Deployment Flexibility: Clients can be native Windows applications (MFC/Win32), web services, or microservices running on Linux—without modifying the server-side logic.

In industrial practice, this means replacing expensive SCADA systems with custom solutions that connect directly to controllers via OPC UA, integrate with SQL Server for storing recipes and historical data, and enable remote monitoring through charts and equipment status displays.

Integrating open62541 into MFC: Technical Features and Limitations

open62541 is a C99-compliant, zero-dependency open-source library designed for embedded and desktop systems. Using it in an MFC project requires meeting several conditions:

  • The project must compile in /MD mode (dynamic linking to CRT), since open62541 relies on the standard C library.
  • Explicit initialization of the TLS/SSL stack is required when using secure policies—in Windows, this involves Wincrypt.h and CryptInitialize().
  • All objects such as UA_Client, UA_CreateSubscriptionRequest, and UA_MonitoredItemCreateRequest must be managed manually: memory allocation, calling UA_Client_disconnect(), and freeing with UA_Client_delete().

A critical limitation is the lack of a built-in thread pool. In an MFC application with a GUI thread, all calls to UA_Client_* must be executed in a separate worker thread (AfxBeginThread) or via PostMessage/WM_USER to avoid blocking the interface. Directly calling UA_Client_connect() in CMainFrame::OnInitialUpdate() will cause the window to freeze.

Google AdInline article slot

Implementing Connection and Subscription in C++/MFC

The function CMainFrame::ConnectCPUOnline() demonstrates a minimally viable scenario for connecting to an OPC UA server. It initializes the client, configures settings, and establishes the connection. Key points include:

  • Using UA_String_fromChars() to convert CT2A strings into UA_String—a mandatory requirement of the open62541 API.
  • Setting cc->securityMode = UA_MESSAGESECURITYMODE_NONE is only acceptable in isolated networks; in real-world projects, you need to choose UA_MESSAGESECURITYMODE_SIGNANDENCRYPT and load certificates via UA_ClientConfig_setDefaultEncryption().
  • Logging at the UA_LOGLEVEL_ERROR level strikes the optimal balance between diagnostics and performance in production.

After connecting, a subscription is created and monitoring items are configured. Below is a snippet of how to create a subscription and add the first tag:

// Creating a subscription
UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
request.requestedPublishingInterval = 500.0; // ms
request.requestedMaxKeepAliveCount = 10;
request.requestedLifetimeCount = 30;

UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(
    StrCPUADR[iAdr].g_clientOnline, request, NULL, NULL, NULL);

if (response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
    return false;
}
StrCPUADR[iAdr].g_subscriptionId = response.subscriptionId;

// Configuring monitoring for the tag
UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default();
monRequest.itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, StrDBINI[i].iIDUA);
monRequest.itemToMonitor.attributeId = UA_ATTRIBUTEID_VALUE;
monRequest.monitoringMode = UA_MONITORINGMODE_REPORTING;

UA_MonitoredItemCreateResult monResponse = UA_Client_MonitoredItems_createSingle(
    StrCPUADR[iAdr].g_clientOnline,
    StrCPUADR[iAdr].g_subscriptionId,
    &monRequest);

Note that iIDUA is the controller’s NodeId obtained from the database (e.g., ns=2;s=Channel1.Device1.Tag1). It must exactly match the server’s address space. Errors in the NodeId result in UA_STATUSCODE_BADNODEIDUNKNOWN, but do not cause a crash—checking monResponse.statusCode is essential.

Google AdInline article slot

Working with Data: From Reading to Logging in SQL Server

The system uses three key structures to manage data:

  • StrDBCP—configuration for connections to controllers: IP address, online/offline status, pointer to UA_Client*, subscription ID, and an arrayValue buffer for receiving values.
  • StrDBINI—tag metadata: iIDUA (NodeId), iTYPEID (data type: UA_TYPES_INT32, UA_TYPES_DOUBLE, UA_TYPES_BOOLEAN), and a bSaveDb flag for logging.
  • StrDBVAL—historical value table with timestamps, CTime, iNOMEQ, iItem, and iVal.

The logic for updating values is implemented in the callback function UA_Client_DataChangeNotification. When a new value is received:

  • Check the iTYPEID and safely cast the type using UA_Variant_getScalarCopy().
  • Write to arrayValue[i] taking into account sizearray.
  • If bSaveDb == true, form a parameterized INSERT statement in SQL Server via CDatabase::ExecuteSQL().

Important: All operations on arrayValue must be protected by a critical section (CCriticalSection), since data comes from a background thread while the GUI thread may simultaneously request it for chart rendering.

What Matters

  • OPC UA in an MFC application requires manual management of the UA_Client lifecycle: initialization → connection → subscription → reading → unsubscription → disconnection → deletion.
  • Security is not optional: SECURITYPOLICY_NONE is only acceptable in test environments; in industrial networks, X.509 certificates and SIGNANDENCRYPT are mandatory.
  • open62541 does not provide asynchronous APIs for Windows GUI—all network operations must be performed in a separate thread and then synchronized via PostMessage.
  • The StrDBCP, StrDBINI, and StrDBVAL structures form an abstraction layer over OPC UA, allowing switching between different controllers without changing the data-handling logic.
  • Supporting Siemens S7-1200/S7-1500 requires enabling the OPC UA server in the controller firmware and properly configuring User Access Control (UAC) in TIA Portal.

— Editorial Team

Advertisement 728x90

Read Next