Building a WinCC ODK C++ Application for Reading and Writing Tags

David Krause14 min read
SiemensTutorial / How-toWinCC
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

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.

Why this exists. The ODK documentation ships in compiled HTML form inside the WinCC installation media, but the examples are typically fragments, not full Visual Studio projects. Engineers integrating C++ services against a live WinCC station commonly search for a complete DMConnect-to-DMSetValue skeleton, and that is what this article delivers.

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.

Table 1 - WinCC ODK runtime files (WinCC V7.x)
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). DMConnect succeeds 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.exe on 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

  1. WinCC V7.3 / 7.4 / 7.5 with the ODK option installed. Confirm via Start Menu » Siemens Automation » WinCC » ODK » ODK Documentation.
  2. 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.
  3. Platform target: x86 for the in-process case (WinCC V7 is 32-bit). x64 is only required for out-of-process services that must be 64-bit.
    A common gotcha: building the client as x64 against the 32-bit dmclient.lib produces a linker error. Match the platform to the ODK install.
  4. WinCC project running in Runtime on the target machine, with the tags you wish to read/write defined in the Tag Management.
  5. 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 of DM_E_ALREADY_CONNECTED on the same workstation.
  • lpfnNotify — optional callback of type DM_NOTIFY_PROC for asynchronous value-change notifications. Pass NULL for read/write-only clients.
  • lpvUser — user-defined context pointer delivered to the notification callback. Ignored when lpfnNotify is NULL.
  • lpdmError — output structure populated with a CMN_ERROR code on failure. The function returns FALSE on error, TRUE on 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;
}
Always balance with 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;
}
Table 2 - Common DM_VARKEY type codes
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;
}
Authorization matters. A 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 ...W prototypes.

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 / IRuntimeProject in the .NET assembly Siemens.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:

Table 3 - Frequent ODK error codes (WinCC V7.x, ODK 7.3+)
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:

  1. Verify Runtime is active on the target machine (green WinCC Explorer icon, no popup "Runtime not started").
  2. Confirm the user is a member of the "SIMATIC HMI" / "WinCC" group.
  3. Open the WinCC tag in Tag Management » Runtime to confirm it is not a configuration-only tag.
  4. 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.
  5. 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

  1. Install the ODK option on the WinCC Runtime station.
  2. Copy the client executable and its dependencies (no WinCC redistributable) to the deployment folder.
  3. Either install ODK on the client (recommended for production) or copy dmclient.dll next to the .exe for dev machines.
    Do not bundle dmclient.dll into your installer in a way that conflicts with the version installed by WinCC. Let the WinCC installer own that file.
  4. Set the Windows service account to a user that is a member of the WinCC user group, with "Log on as service" right.
  5. If the client is on a different machine than WinCC, open the DCOM / RPC ports used by CCDMCliSrv.exe and add the client to the "SIMATIC HMI" group on the WinCC host.
  6. 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.

  1. Start the WinCC project in Runtime on the target machine.
  2. Run OdkCppClient.exe from a command prompt on the same machine (in-process path) or the remote client (out-of-process path).
  3. Expected output:r>
    Connected to WinCC Data Manager.
    MyFloatVar = 12.345
    MySetpoint <- 42.500
  4. In the WinCC Graphics Designer, place a Numeric I/O field bound to MyFloatVar and an Output field bound to MySetpoint. 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).
  5. In the WinCC Tag Logging, confirm a log entry for the setpoint change within the configured archive cycle.
  6. Stop the client with Ctrl-C and confirm no zombie ODK_CPP_Sample name appears in the WinCC Connection list (WinCC Explorer » Tools » Connections).

12. Troubleshooting Matrix

Table 4 - Common failure modes and remediation
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.

Back to blog