Building a WinCC ODK C++ Application for Reading and Writing Tags
Siemens WinCC Open Development Kit (ODK) is a C/C++ and .NET API surface that lets external Windows processes read and write runtime tags, subscribe to value changes, and interact with the WinCC Data Manager (DM) without going through the HMI screens. This reference walks through a complete, working WinCC ODK C++ client using DMConnect, DMGetValueEx, and DMSetValue, the three functions engineers reach for first when integrating WinCC with custom C++ services, historians, or test harnesses.
The patterns shown target WinCC V7.x with ODK 7.3 / 7.4 / 7.5 (Data Manager API, classic WinCC) and also note the differences when targeting the newer WinCC Unified Runtime ODK (RT Unified) shipping with TIA Portal V17 and later.
1. ODK Architecture Overview
WinCC ODK is a thin, in-process or out-of-process interface to the WinCC Data Manager. From the C++ side the relevant boundary is the set of exported C-style entry points declared in dmclient.h, dmdef.h, and dmvars.h (ANSI/Unicode pair ...A / ...W). The functions are exported from dmclient.dll, which is loaded implicitly by linking against the import library dmclient.lib.
| Component | Default path (WinCC V7.5) | Purpose |
|---|---|---|
dmclient.dll |
<WinCC>\bin |
Exported C API, loaded at runtime |
dmclient.lib |
<WinCC>\lib |
Import library for Visual Studio linkage |
dmclient.h, dmdef.h, dmvars.h
|
<WinCC>\include |
Public C/C++ headers |
CCDMCliSrv.exe |
<WinCC>\bin |
Out-of-process data manager server (used when client is not on the WinCC station) |
ODKHelp.chm |
<WinCC>\help |
Function reference (compiled HTML) |
Two architectural modes are available:
-
In-process (local) — the C++ DLL is loaded into a WinCC process (Graphics Designer action, global action, or background task).
DMConnectsucceeds and the client shares the process address space of the Data Manager. -
Out-of-process (remote) — the C++ client runs as a standalone Windows service or Win32 console. The Data Manager is reached across an RPC channel serviced by
CCDMCliSrv.exeon the WinCC station.
For external applications that are not running inside WinCC, the ODK must be installed on both the WinCC station and the client machine, and a connection (DMConnect) is made by name. The WinCC project must be in Runtime (not just open in the configuration studio) for tag access to return live values.
2. Prerequisites
- WinCC V7.3 / 7.4 / 7.5 with the ODK option installed. Confirm via Start Menu » Siemens Automation » WinCC » ODK » ODK Documentation.
- Visual Studio 2015, 2017, 2019, or 2022 with the "Desktop development with C++" workload and the Windows 10/11 SDK. The ODK 7.3 import library uses the MSVC toolchain; MinGW is not supported.
-
Platform target:
x86for the in-process case (WinCC V7 is 32-bit).x64is only required for out-of-process services that must be 64-bit.A common gotcha: building the client asx64against the 32-bitdmclient.libproduces a linker error. Match the platform to the ODK install. - WinCC project running in Runtime on the target machine, with the tags you wish to read/write defined in the Tag Management.
- For Unified, the equivalent toolchain is described in the official Creating a minimal ODK client (RT Unified) guide (C# and C++ clients).
3. DMConnect: Establishing a Data Manager Session
DMConnect is the mandatory first call. Per the ODK documentation, only one DMConnect may be active in a given process; subsequent calls return DM_E_ALREADY_CONNECTED. The ANSI and Unicode variants are functionally identical except for string encoding:
BOOL WINAPI DMConnectA(
LPCSTR lpszAppName,
DM_NOTIFY_PROCA lpfnNotify,
LPVOID lpvUser,
LPCMN_ERRORA lpdmError);
BOOL WINAPI DMConnectW(
LPCWSTR lpszAppName,
DM_NOTIFY_PROCW lpfnNotify,
LPVOID lpvUser,
LPCMN_ERRORW lpdmError);
Argument semantics:
-
lpszAppName— a free-form name registered with the Data Manager. Each connected client must use a unique name; reusing the name of an active connection is one of the typical causes ofDM_E_ALREADY_CONNECTEDon the same workstation. -
lpfnNotify— optional callback of typeDM_NOTIFY_PROCfor asynchronous value-change notifications. PassNULLfor read/write-only clients. -
lpvUser— user-defined context pointer delivered to the notification callback. Ignored whenlpfnNotifyisNULL. -
lpdmError— output structure populated with aCMN_ERRORcode on failure. The function returnsFALSEon error,TRUEon success.
Minimal connect (C++):
#include <windows.h>
#include <tchar.h>
#include "dmclient.h"
#include "dmdef.h"
#include "dmvars.h"
int _tmain(int argc, TCHAR* argv[])
{
CMN_ERROR cmnErr = { 0 };
cmnErr.dwError1 = 0;
cmnErr.dwError2 = 0;
cmnErr.szErrorText[0] = 0;
if (!DMConnect(_T("ODK_CPP_Sample"), NULL, NULL, &cmnErr))
{
_tprintf(_T("DMConnect failed: 0x%08X / 0x%08X\n"),
cmnErr.dwError1, cmnErr.dwError2);
return 1;
}
// ... read/write here ...
DMDisconnect(&cmnErr);
return 0;
}
DMDisconnect. If the calling process exits without disconnecting, the name remains registered with the Data Manager for the lifetime of the WinCC Runtime, which is the second most common cause of DM_E_ALREADY_CONNECTED on a hot-restarted test harness.4. Reading Tags with DMGetValue / DMGetValueEx
Once connected, the ODK exposes a family of read functions. DMGetValueEx is the recommended entry point because it handles both single items and arrays in a single call and supports the full variable type set:
BOOL WINAPI DMGetValueExA(
LPDM_VARKEYA lpdmVarKey,
DWORD dwItems,
LPDM_VAR_UPDATE_STRUCTEXA lpdmvus,
LPCMN_ERRORA lpdmError);
BOOL WINAPI DMGetValueExW(
LPDM_VARKEYW lpdmVarKey,
DWORD dwItems,
LPDM_VAR_UPDATE_STRUCTEXW lpdmvus,
LPCMN_ERRORW lpdmError);
The structures are defined in dmdef.h and dmvars.h. A typical read populates a DM_VARKEY with the tag name and the type, then passes an output buffer in DM_VAR_UPDATE_STRUCTEX. dwItems is the array length (use 1 for scalars). The buffer must be large enough to hold the type requested; the ODK does not bounds-check user memory.
BOOL ReadTagFloat(const LPCWSTR szName, float* pfOut)
{
DM_VARKEY vkey = { 0 };
DM_VAR_UPDATE_STRUCTEX vus = { 0 };
CMN_ERROR err = { 0 };
vkey.szName = (LPWSTR)szName;
vkey.dwNameLen = (DWORD)wcslen(szName);
vkey.dwType = DM_FLOAT;
vkey.lpvData = pfOut;
vkey.dwDataLen = sizeof(float);
vkey.lpdwState = NULL;
vkey.dwStateLen = 0;
vus.dwType = DM_FLOAT;
vus.lpvData = pfOut;
vus.dwDataLen = sizeof(float);
vus.dwState = 0;
if (!DMGetValueEx(&vkey, 1, &vus, &err))
{
_tprintf(_T("DMGetValueEx '%s' failed: 0x%08X\n"),
szName, err.dwError1);
return FALSE;
}
return TRUE;
}
| Constant | WinCC type | C buffer type |
|---|---|---|
DM_BOOL |
Binary tag |
BOOL / DWORD
|
DM_BYTE |
8-bit unsigned | BYTE |
DM_WORD |
16-bit unsigned | WORD |
DM_DWORD |
32-bit unsigned | DWORD |
DM_SHORT |
16-bit signed | short |
DM_LONG |
32-bit signed | long |
DM_FLOAT |
32-bit IEEE 754 | float |
DM_DOUBLE |
64-bit IEEE 754 | double |
DM_TEXT |
String tag (8-bit) | char[N] |
DM_TEXT_UNICODE |
String tag (UTF-16) | WCHAR[N] |
DM_RAW |
Raw/process tag | BYTE[N] |
For DM_TEXT tags, the buffer must include the null terminator and dwDataLen must equal the buffer size in bytes. The ODK fills the buffer up to dwDataLen - 1 characters and null-terminates.
5. Writing Tags with DMSetValue
The write side mirrors the read side. DMSetValue accepts one or more items and is the only path that performs a value "set" (as opposed to a request) on internal tags. The signature is intentionally similar to the read API so an application can prepare a batch and dispatch with one call:
BOOL WINAPI DMSetValueA(
LPDM_VARKEYA lpdmVarKey,
DWORD dwItems,
LPCMN_ERRORA lpdmError);
BOOL WINAPI DMSetValueW(
LPDM_VARKEYW lpdmVarKey,
DWORD dwItems,
LPCMN_ERRORW lpdmError);
BOOL WriteTagFloat(const LPCWSTR szName, float fValue)
{
DM_VARKEY vkey = { 0 };
CMN_ERROR err = { 0 };
vkey.szName = (LPWSTR)szName;
vkey.dwNameLen = (DWORD)wcslen(szName);
vkey.dwType = DM_FLOAT;
vkey.lpvData = &fValue;
vkey.dwDataLen = sizeof(float);
if (!DMSetValue(&vkey, 1, &err))
{
_tprintf(_T("DMSetValue '%s' failed: 0x%08X\n"),
szName, err.dwError1);
return FALSE;
}
return TRUE;
}
DMSetValue on a tag whose "Operator Control" attribute is disabled returns DM_E_NO_AUTHORIZATION even if the caller has Runtime Administrator rights. Engineers writing test harnesses that drive HMI tags from C++ frequently hit this; enable operator control in the tag properties or use an internal tag.6. Putting It Together: A Minimal VC++ Project
The complete project layout for a console-mode ODK client in Visual Studio 2019/2022 is intentionally small:
OdkCppClient/
+- OdkCppClient.cpp // main + read/write helpers
+- OdkCppClient.vcxproj // MSBuild project
+- x64\Release\OdkCppClient.exe
+- (output) OdkCppClient.exe depends on dmclient.dll at runtime
Project settings (Release | x86):
-
C/C++ » General » Additional Include Directories:
C:\Program Files (x86)\Siemens\WinCC\include -
Linker » General » Additional Library Directories:
C:\Program Files (x86)\Siemens\WinCC\lib -
Linker » Input » Additional Dependencies:
dmclient.lib -
Linker » System » SubSystem:
Console (/SUBSYSTEM:CONSOLE) -
Character Set:
Use Unicode Character Set(recommended) — matches...Wprototypes.
At runtime, dmclient.dll must be on the PATH. Either add %ProgramFiles(x86)%\Siemens\WinCC\bin to the system PATH (or to the user PATH for the test account), or drop a copy next to the .exe during development. For deployment, install the ODK runtime on the target machine and let the WinCC installer register the DLL.
6.1 Full source: OdkCppClient.cpp
// OdkCppClient.cpp - WinCC ODK V7 sample
// Target: WinCC V7.3 / V7.4 / V7.5, x86, Unicode
// Build: cl /EHsc /W4 /O2 OdkCppClient.cpp /I"%ProgramFiles(x86)%\Siemens\WinCC\include" dmclient.lib /link /SUBSYSTEM:CONSOLE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "dmclient.h"
#include "dmdef.h"
#include "dmvars.h"
static void PrintError(LPCTSTR where, LPCMN_ERROR err)
{
_tprintf(_T("[%s] error 0x%08X / 0x%08X %s\n"),
where,
err->dwError1,
err->dwError2,
err->szErrorText);
}
int _tmain(void)
{
CMN_ERROR err = { 0 };
if (!DMConnect(_T("ODK_CPP_Sample"), NULL, NULL, &err))
{
PrintError(_T("DMConnect"), &err);
return 1;
}
_tprintf(_T("Connected to WinCC Data Manager.\n"));
// Read tag "MyFloatVar"
float fVal = 0.0f;
{
DM_VARKEY vk = { 0 };
DM_VAR_UPDATE_STRUCTEX vus = { 0 };
vk.szName = (LPWSTR)L"MyFloatVar";
vk.dwNameLen = (DWORD)wcslen(vk.szName);
vk.dwType = DM_FLOAT;
vk.lpvData = &fVal;
vk.dwDataLen = sizeof(float);
vus.dwType = DM_FLOAT;
vus.lpvData = &fVal;
vus.dwDataLen = sizeof(float);
if (!DMGetValueEx(&vk, 1, &vus, &err))
PrintError(_T("DMGetValueEx"), &err);
else
_tprintf(_T("MyFloatVar = %.3f\n"), fVal);
}
// Write tag "MySetpoint"
{
float fSet = 42.5f;
DM_VARKEY vk = { 0 };
vk.szName = (LPWSTR)L"MySetpoint";
vk.dwNameLen = (DWORD)wcslen(vk.szName);
vk.dwType = DM_FLOAT;
vk.lpvData = &fSet;
vk.dwDataLen = sizeof(float);
if (!DMSetValue(&vk, 1, &err))
PrintError(_T("DMSetValue"), &err);
else
_tprintf(_T("MySetpoint <- %.3f\n"), fSet);
}
DMDisconnect(&err);
return 0;
}
7. ODK 1500S and WinCC Unified Runtime: What Changes
The classic WinCC ODK described above is the API for WinCC V7.x on a Windows station. Siemens has shipped an additional ODK surface for SIMATIC S7-1500 controllers with ODK 1500S and a separate, more modern ODK for the WinCC Unified Runtime (TIA Portal V17+). The two are not interchangeable.
- ODK 1500S — C/C++ application on a SIMATIC S7-1500 CPU with ODK 1500S license. Sample projects include HelloWorld (Managed C++) and Hello World (C#). Reference: SIMATIC ODK 1500S Examples V2.0 (PDF, Siemens support entry 109781803 ID family).
-
WinCC Unified RT ODK — the Runtime API is exposed as a .NET / C++ client that lives on the same PC as the Unified Runtime and is loaded into the UMC (Unified Management Console) host. The entry points use
IRuntimeApplication/IRuntimeProjectin the .NET assemblySiemens.Runtime.HmiUnified. A minimal client walkthrough is in the official TIA Portal help: Creating a minimal ODK client (RT Unified). The example document on the Siemens support site, Creation of ODK applications for WinCC Unified Runtime, shows the same pattern with a project template and the published C#/C++ example.
The Unified Runtime ODK does not use DMConnect/DMGetValueEx/DMSetValue. Engineers porting code from V7 ODK to Unified must replace those calls with the IRuntimeProject tag access methods. The functional goal — read a tag, write a tag, react to a change — is the same, but the namespace, the deployment model, and the licensing are different.
8. Error Codes and Diagnostics
The CMN_ERROR structure returned through every ODK call carries a primary code (dwError1), an optional secondary code (dwError2), and a human-readable text buffer. The primary codes follow the DM_E_* convention:
| Code (hex) | Symbol | Likely cause | First check |
|---|---|---|---|
| 0x80040E00 | DM_E_ALREADY_CONNECTED |
Process already connected, or name already used by another process | Unique lpszAppName; verify DMDisconnect on shutdown |
| 0x80070005 | DM_E_ACCESS_DENIED |
User not in WinCC user group, or UAC blocking | Run as WinCC user; check User Administrator in WinCC |
| 0x80040E03 | DM_E_NO_AUTHORIZATION |
Tag has no operator authorization | Enable operator control on the tag |
| 0x80040E04 | DM_E_UNKNOWN_TAG |
Tag does not exist in the active project | Spelling, prefix (e.g. \), Runtime not active |
| 0x80040E05 | DM_E_TYPE_MISMATCH |
Requested dmType does not match the actual tag type |
Match Table 2 types to WinCC tag type |
| 0x80040E06 | DM_E_VALUE_OUT_OF_RANGE |
Value exceeds configured limits | Check tag limits, raw-tag byte counts |
| 0x80040E0A | DM_E_NOT_CONNECTED |
DMConnect not called or already disconnected |
Call order; check for early DMDisconnect
|
| 0x80004005 | Generic Win32 error | RPC failure, missing dmclient.dll
|
PATH includes ...\Siemens\WinCC\bin
|
Diagnostic workflow:
- Verify Runtime is active on the target machine (green WinCC Explorer icon, no popup "Runtime not started").
- Confirm the user is a member of the "SIMATIC HMI" / "WinCC" group.
- Open the WinCC tag in Tag Management » Runtime to confirm it is not a configuration-only tag.
- Enable GCS_PAS or the WinCC diagnostic tool (
CCDiagnostic.exe) and reproduce. The Data Manager log shows the rejected client name and the reason code. - Use a minimal client (this article's sample) with the failing tag name to isolate the API call from any custom wrapper code.
9. Subscribing to Value Changes (Notification Variant)
Polling is fine for slow test harnesses, but production bridges usually want to push values on change. The ODK notification path uses a callback registered with DMConnect:
VOID CALLBACK MyNotifyProc(
LPVOID lpvUser,
LPDM_VARKEY lpdmVarKey,
DWORD dwItems,
DWORD dwReason)
{
if (dwReason == DM_REASON_VALUE_CHANGE && dwItems > 0)
{
if (lpdmVarKey->dwType == DM_FLOAT)
{
float f = *(float*)lpdmVarKey->lpvData;
// ... forward to consumer ...
}
}
}
CMN_ERROR err = { 0 };
DMConnect(_T("ODK_Notif"), MyNotifyProc, NULL, &err);
Subscriptions are added with DMRegisterValue / DMRegisterValueEx. The callback runs on a Data Manager worker thread, so the user function must be re-entrant and must not block. For multi-tag subscriptions, allocate a copy of the value in the callback or push the raw pointer into a lock-free queue.
10. Deployment Checklist
- Install the ODK option on the WinCC Runtime station.
- Copy the client executable and its dependencies (no WinCC redistributable) to the deployment folder.
- Either install ODK on the client (recommended for production) or copy
dmclient.dllnext to the .exe for dev machines.Do not bundledmclient.dllinto your installer in a way that conflicts with the version installed by WinCC. Let the WinCC installer own that file. - Set the Windows service account to a user that is a member of the WinCC user group, with "Log on as service" right.
- If the client is on a different machine than WinCC, open the DCOM / RPC ports used by
CCDMCliSrv.exeand add the client to the "SIMATIC HMI" group on the WinCC host. - Verify with the minimal client in this article before adding wrapper code.
11. Verification Procedure
After a clean build, the verification sequence confirms the entire stack: build → load → connect → read → write → disconnect.
- Start the WinCC project in Runtime on the target machine.
- Run
OdkCppClient.exefrom a command prompt on the same machine (in-process path) or the remote client (out-of-process path). - Expected output:r>
Connected to WinCC Data Manager. MyFloatVar = 12.345 MySetpoint <- 42.500 - In the WinCC Graphics Designer, place a Numeric I/O field bound to
MyFloatVarand an Output field bound toMySetpoint. The read should match the I/O field's current value, and the write should change the Output field's value within one cycle (typically 250–500 ms). - In the WinCC Tag Logging, confirm a log entry for the setpoint change within the configured archive cycle.
- Stop the client with Ctrl-C and confirm no zombie
ODK_CPP_Samplename appears in the WinCC Connection list (WinCC Explorer » Tools » Connections).
12. Troubleshooting Matrix
| Symptom | Most likely cause | Remediation |
|---|---|---|
Linker error LNK2019 on DMConnect
|
Missing dmclient.lib or wrong platform |
Add library, switch to x86 for V7 ODK |
Load-time error 126 loading dmclient.dll
|
PATH does not include ...\Siemens\WinCC\bin
|
Add to PATH or install ODK on client |
DM_E_ALREADY_CONNECTED on every run |
Previous process exited without DMDisconnect
|
Restart WinCC Runtime to clear registrations, or use unique names |
DM_E_UNKNOWN_TAG for a valid tag |
Runtime not started, or tag is configuration-only | Start Runtime; verify tag in Tag Management » Runtime |
| Write succeeds but value reverts | Tag has operator authorization disabled | Enable "Operator control" in tag properties |
| Callback never fires |
DMRegisterValue not called or wrong tag |
Confirm dwItems count and tag name |
Crash inside DMGetValueEx
|
Buffer too small for the requested type | Size lpvData buffer for the type in Table 2 |
Which ODK version do I need for WinCC V7.5?
WinCC V7.5 ships ODK 7.5. The Data Manager API (DMConnect, DMGetValueEx, DMSetValue) is source-compatible with ODK 7.3 and 7.4. The import library and headers are in <WinCC>\lib and <WinCC>\include.
Do I need WinCC Runtime running on the client PC?
For in-process ODK, yes. For out-of-process clients, install the ODK runtime on the client and reach WinCC over the Data Manager RPC served by CCDMCliSrv.exe on the WinCC host. The host must be in Runtime.
Why does DMSetValue return success but the HMI value does not change?
The tag is likely configured with operator authorization disabled, or the write was redirected to a different Runtime instance. Enable operator control on the tag and verify only one WinCC Runtime is active on the host.
Can I mix WinCC V7 ODK calls with WinCC Unified Runtime ODK in the same binary?
No. The two are different APIs with different headers, different DLLs, and different licensing. The Unified Runtime ODK uses the .NET Siemens.Runtime.HmiUnified assembly, not the V7 dmclient.dll.
How do I read an array tag (e.g. 100-element float array) in one call?
Set DM_VARKEY.dwItems to 100, point lpvData at a buffer of 100 floats, and pass dwDataLen = 100 * sizeof(float). DMGetValueEx reads the entire array in a single call; do not loop one element at a time.