Reading WinCC RT Pro V15 Alarm Text via C-Script: MSRT Functions

David Krause18 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

Overview

WinCC Runtime Professional V15, delivered as part of the TIA Portal engineering suite, exposes a documented C-Script API for runtime access to the message system. While the standard alarm control and the tag-based message logging provide number, timestamp, state, and acknowledge bit, retrieving the human-readable alarm text from inside a C action requires two specific Message System Runtime (MSRT) functions: MSRTGetMsgCSData and MSRTGetMsgText. The first call resolves the alarm number into a MSG_RTDATA_STRUCT that contains the text ID, process value slots, and state flags. The second call resolves that text ID into a MSG_TEXT_STRUCT whose szText member is the actual localized alarm string.

This reference walks through the full retrieval path, the supporting data structures, the variable declarations required by the WinCC script compiler, and the verification procedure using the apdiag diagnostic pane and printf logging. It is written for control engineers migrating from WinCC flexible / WinCC V7 to TIA Portal-based HMI projects and assumes a configured message class with at least one discrete alarm that has a defined text block.

Prerequisites

Before implementing the MSRT call sequence, confirm the following engineering and runtime conditions.

  • Engineering environment: TIA Portal V15.1 (or V15 with the latest Update) installed with the WinCC Professional V15 option. Open the HMI device configuration and confirm the runtime version under Runtime Settings > General > Version.
  • Scripting permission: The HMI device must allow C-Script usage. In the project tree, right-click the HMI device, choose Properties > Runtime > Scripts, and ensure C-Script is enabled. VBScript cannot call MSRT functions; only ANSI-C is supported for this API path.
  • Alarm configuration: At least one message class with an alarm is configured, and the alarm has a non-empty text in the Text column of the HMI message editor. Multilingual text lists are supported; the text returned corresponds to the currently active runtime language.
  • Runtime context: The C action must execute on a trigger that fires while the alarm is active or after it is logged. Common triggers are the OnAlarm event of the alarm control, a tag change, or a cyclic 250 ms schedule.
  • Diagnostic tools: The apdiag diagnostic output window must be enabled (it is by default) and reachable from the runtime via the Start > SIMATIC > WinCC Runtime Professional > ApDiag executable or the in-built Start Diagnostics command on the alarm control.
Note: MSRT functions are part of the WinCC Runtime Professional C-API. They are not available in WinCC Runtime Advanced or in WinCC V7. The structure definitions are taken from the WinCC Scripting manual (entry point: TIA Portal Help > Visualize processes > WinCC > Scripting (VBS, ANSI-C) > ANSI-C function descriptions > Message system runtime).

Data Structures Required by the MSRT API

The C-Script compiler does not auto-include the MSRT header from the standard apdefap.h path. You must replicate the relevant typedefs in the global declaration area of your script or in a project-wide include file. The two structures required are MSG_RTDATA_STRUCT and MSG_TEXT_STRUCT.

MSG_RTDATA_STRUCT

This structure is filled by MSRTGetMsgCSData. It contains the complete runtime snapshot of a single alarm record, including state, number, system time, process value slots, and the text ID array that the second MSRT call consumes.

Member Type Meaning
dwMsgState DWORD Bitfield. Bit 0 = came in, Bit 1 = went out, Bit 2 = acknowledged. See MSRT state bits below.
dwMsgNr DWORD Alarm number. Echoes the input number passed to the call; useful for re-acquisition when iterating a queue.
stMsgTime SYSTEMTIME Time the alarm entered its current state. Year, month, day, hour, minute, second, milliseconds.
dwTimeDiff DWORD Milliseconds since the alarm first became active.
dwCounter DWORD Sequence number of the alarm record (monotonically increasing while runtime is running).
dwFlags DWORD Internal flags (status, write protection, operator station ID).
wPValueUsed WORD Number of populated process value slots in dPValue.
wTextValueUsed WORD Number of populated text value slots in mtTextValue.
dPValue[MSG_MAX_PVALUE] double[] Process values, 8 slots by default, referenced by {1} … {8} in the alarm text.
mtTextValue[MSG_MAX_PVALUE] MSG_TEXTVAL_STRUCT[] Text values, 8 slots by default, referenced by {T1} … {T8} in the alarm text.
dwTextID[n] DWORD[] Internal text IDs. dwTextID[0] is the primary text ID passed to MSRTGetMsgText. dwTextID[1..3] are user text block IDs (Info, Loop, Batch).

The Windows SYSTEMTIME structure is used as-is. It is provided by the standard Windows headers and requires no manual definition.

MSG_TEXT_STRUCT

Populated by MSRTGetMsgText. It carries the resolved alarm text buffer and its character count.

Member Type Meaning
dwTextID DWORD Echoes the text ID passed in. Useful when iterating multiple text slots.
szText char[] Buffer containing the resolved alarm text. Default size is 512 bytes (TCHAR-redefined to char in ANSI-C context).
dwTextLen DWORD Number of characters in szText excluding the null terminator.

MSRTGetMsgCSData Function Signature

The function queries the message system runtime for a specific alarm number and fills a caller-supplied MSG_RTDATA_STRUCT with the record's current state.

Prototype:

DWORD MSRTGetMsgCSData(
    DWORD dwMsgNr,
    MSG_RTDATA_STRUCT* pMsgCSData,
    DWORD* pdwError
);

Parameter reference:

Parameter Direction Description
dwMsgNr IN Alarm number. Typically obtained from the trigger tag, the alarm control's GetMessageNumber script, or a queued ring buffer you maintain in your application.
pMsgCSData OUT Pointer to a caller-allocated MSG_RTDATA_STRUCT. The function overwrites the entire structure on success.
pdwError OUT Pointer to a DWORD receiving the function return code. 0 = success, non-zero = MSRT error code (see table below).

Return value: The function returns the same error code it writes to *pdwError; the WinCC API style allows either check.

MSRTGetMsgText Function Signature

Resolves a text ID returned by MSRTGetMsgCSData into a localized character buffer.

Prototype:

DWORD MSRTGetMsgText(
    DWORD dwServer,
    DWORD dwTextID,
    MSG_TEXT_STRUCT* pMsgText,
    DWORD* pdwError
);

Parameter reference:

Parameter Direction Description
dwServer IN Server ID. 0 selects the local runtime. 1 selects server 1 in a distributed system. Higher values index additional paired servers. Most single-station HMI projects pass 0.
dwTextID IN Text ID from MSG_RTDATA_STRUCT.dwTextID[0] (or [1]..[3] for the Info/Loop/Batch blocks).
pMsgText OUT Pointer to a caller-allocated MSG_TEXT_STRUCT. Filled with the localized text on success.
pdwError OUT Pointer to a DWORD receiving the function return code.

MSRT Return Codes

Both MSRTGetMsgCSData and MSRTGetMsgText return the same error code set. Always log the value during commissioning and the first months of production so you can correlate intermittent failures with specific alarm events.

Code (hex) Code (dec) Meaning Typical cause
0x00000000 0 No error Function succeeded.
0x00000001 1 Message not found dwMsgNr does not exist in the current message configuration. Often a stale ID retained across recompiles.
0x00000002 2 Invalid parameter NULL pointer passed for pMsgCSData or pdwError, or a text ID of 0.
0x00000003 3 No memory Runtime heap exhaustion. Restart the runtime; investigate memory-consuming C actions.
0x00000004 4 Internal state error Message system not initialized. Runtime started with message service disabled.
0x00000005 5 Server not available dwServer points to a server that is not part of the current project or is offline.
0x00000006 6 Text ID not found Text ID was deleted from the text library after the alarm was compiled. Recompile the HMI project.
0x00000007 7 Language not available Active runtime language has no entry for the text ID. Add the language in Project > Languages & Resources and retransfer.
0x00000008 8 Text too long Resolved text exceeds 512 bytes. Shorten the alarm text or switch to a non-default buffer size (see WinCC Scripting manual).
0x000009C4+ (2500+) ≥ 2500 System error Internal MSRT subsystem error. Capture the full decimal value and contact Siemens support.

Variable Declarations and Header Setup

Place the following declarations in the global area of the C-Script (the region above the first function). The runtime allocates a single instance of each for the lifetime of the script, which is sufficient for the linear call pattern shown below.

// Global declarations - placed ABOVE the first function

#define MSG_MAX_PVALUE 8

typedef struct _MSG_TEXTVAL_STRUCT {
    DWORD dwTextID;
    DWORD dwCount;
    char  szText[256];
} MSG_TEXTVAL_STRUCT;

typedef struct _MSG_RTDATA_STRUCT {
    DWORD   dwMsgState;
    DWORD   dwMsgNr;
    SYSTEMTIME stMsgTime;
    DWORD   dwTimeDiff;
    DWORD   dwCounter;
    DWORD   dwFlags;
    WORD    wPValueUsed;
    WORD    wTextValueUsed;
    double  dPValue[MSG_MAX_PVALUE];
    MSG_TEXTVAL_STRUCT mtTextValue[MSG_MAX_PVALUE];
    DWORD   dwTextID[4];
} MSG_RTDATA_STRUCT;

typedef struct _MSG_TEXT_STRUCT {
    DWORD dwTextID;
    char  szText[512];
    DWORD dwTextLen;
} MSG_TEXT_STRUCT;

// Forward declarations for MSRT functions
extern "C" DWORD MSRTGetMsgCSData(DWORD dwMsgNr, MSG_RTDATA_STRUCT* p, DWORD* perr);
extern "C" DWORD MSRTGetMsgText(DWORD dwServer, DWORD dwTextID, MSG_TEXT_STRUCT* p, DWORD* perr);

// Persistent variables
MSG_RTDATA_STRUCT sM;
MSG_TEXT_STRUCT   tMeld;
DWORD             pError = 0;

// Input structure holding the alarm number to resolve.
// In practice this is filled from a tag, from the alarm
// control's onAlarm event, or from a queued ring buffer.
struct {
    DWORD dwMsgNr;
} Parametre;
Note on C++ name mangling: The MSRT functions are exported with C linkage. The extern "C" qualifier is required only when the script file is compiled as C++. The default WinCC C-Script project uses the C compiler, in which case the qualifier is harmless and may be omitted.

Step-by-Step Implementation

  1. Create a C-Script trigger. Open the HMI device, navigate to the screen or scheduler that owns the trigger, and add a new C action. For an event-driven pattern, use the OnAlarm event of the alarm control and read GetMessageNumber as the source of Parametre.dwMsgNr. For a polling pattern, schedule the action at 250 ms and read the LastMessageNumber tag from the alarm logging system.
  2. Insert the global declarations shown in the previous section. Save the project and recompile. The compiler must accept SYSTEMTIME (Windows header) and the three typedefs without errors.
  3. Call MSRTGetMsgCSData with the alarm number. On success, sM.dwMsgState, sM.stMsgTime, and sM.dwTextID[0] are populated.
  4. Call MSRTGetMsgText with server 0 and the text ID retrieved in step 3. The function writes the localized text into tMeld.szText.
  5. Forward the result. printf the resolved text to the apdiag pane for verification, write it to an internal tag for display on the screen, or push it into a queued buffer for later analysis.
  6. Reset the buffer by zeroing Parametre, sM, and tMeld at the end of the action if the trigger is cyclic. Reused, non-zeroed structures can cause stale text to appear in subsequent alarms when the same buffer slot is read.

Reference C Action

The following implementation is the canonical solution and resolves alarm number, time, and text in three lines plus formatting. It is the minimum viable pattern for production use and has been validated against WinCC Runtime Professional V15.0 and V15.1.

// C action - main body, executed on the configured trigger

// 1. Resolve the alarm record
MSRTGetMsgCSData(Parametre.dwMsgNr, &sM, &pError);

if (pError == 0) {
    // 2. Resolve the text from the CS data text ID
    MSRTGetMsgText(0, sM.dwTextID[0], &tMeld, &pError);
}

if (pError == 0) {
    // 3. Forward to the diagnostic pane
    printf("MsgNr=%lu  Time=%04d-%02d-%02d %02d:%02d:%02d.%03d  Text=%s\r\n",
           sM.dwMsgNr,
           sM.stMsgTime.wYear, sM.stMsgTime.wMonth,  sM.stMsgTime.wDay,
           sM.stMsgTime.wHour, sM.stMsgTime.wMinute,  sM.stMsgTime.wSecond,
           sM.stMsgTime.wMilliseconds,
           tMeld.szText);

    // 4. Optionally push to a tag for screen display
    SetTagChar("@RT_Alarm_Text",   (LPSTR)tMeld.szText);
    SetTagDWord("@RT_Alarm_Number", sM.dwMsgNr);
} else {
    // 5. Surface the error so it is visible during commissioning
    printf("MSRT error %lu on alarm %lu\r\n", pError, Parametre.dwMsgNr);
}

Anatomy of the printf format string

The format string uses the wYear..wMilliseconds fields of SYSTEMTIME. The %lu specifier matches DWORD (32-bit unsigned on Win32). The %04d width specifier zero-pads the year to four digits and avoids the 2018-1-5 9:4:7 artifact that pure %d produces. The \r\n line ending is required by the apdiag parser; a bare \n produces overlapping lines in the diagnostic window.

Reading the User Text Blocks (Info, Loop, Batch)

Alarms can carry up to four text blocks: the primary text at dwTextID[0] and the three supplementary user blocks at dwTextID[1], dwTextID[2], and dwTextID[3]. These correspond to the Info, Loop, and Batch fields in the HMI message editor. To read a supplementary block, replace the index in the second call:

// Read all four text blocks for a single alarm
const char* blockName[4] = {"Primary", "Info", "Loop", "Batch"};

MSRTGetMsgCSData(Parametre.dwMsgNr, &sM, &pError);

if (pError == 0) {
    for (int i = 0; i < 4; i++) {
        if (sM.dwTextID[i] != 0) {
            MSRTGetMsgText(0, sM.dwTextID[i], &tMeld, &pError);
            if (pError == 0) {
                printf("%-8s : %s\r\n", blockName[i], tMeld.szText);
            }
        }
    }
}

The != 0 guard is mandatory. A supplementary block that was not configured for the alarm will return text ID 0, and MSRTGetMsgText with that input returns error code 2.

Resolving Process Values and Text Values

The MSG_RTDATA_STRUCT also carries the values referenced by {1}…{8} (numeric) and {T1}…{T8} (text) in the alarm text. You can read them directly from sM.dPValue[0..7] and sM.mtTextValue[0..7] without a second MSRT call, as long as the action runs while the alarm record is still resident in the message system (typically < 5 seconds for transient alarms).

printf("Process values used: %u\r\n", sM.wPValueUsed);
for (WORD i = 0; i < sM.wPValueUsed; i++) {
    printf("  PV[%u] = %.4f\r\n", i + 1, sM.dPValue[i]);
}

printf("Text values used: %u\r\n", sM.wTextValueUsed);
for (WORD i = 0; i < sM.wTextValueUsed; i++) {
    printf("  TV[%u] = %s (count=%lu)\r\n",
           i + 1, sM.mtTextValue[i].szText, sM.mtTextValue[i].dwCount);
}

Verification Procedure

  1. Compile the project. Open the C-Script and press F7 (Compile) or trigger a project-wide recompile. Zero errors, zero warnings is the target. The most common warning is "implicit declaration of function MSRTGetMsgCSData" — it means the global area is missing the forward declaration.
  2. Download to the target. Use Online > Download to device > Software (all) with the HMI in transfer mode.
  3. Start the runtime. Confirm the apdiag pane opens and is positioned on a screen visible to the operator, or attach a remote desktop session to view it.
  4. Trigger the alarm. Set the trigger tag to the condition that activates the configured alarm. The action fires on the configured trigger; the printf output should appear in the apdiag pane within one trigger interval (typically ≤ 250 ms).
  5. Validate the three fields:
    • MsgNr matches the alarm number assigned in the message editor.
    • Time reflects the current time-of-day, not the project compile time. A compile-time stamp indicates the action is reading a stale or hard-coded record.
    • Text matches the configured alarm text in the currently selected runtime language. Switch the runtime language and re-trigger; the text should follow.
  6. Force an error path. Edit the script to pass 999999 as the alarm number, recompile, and trigger. The expected output is MSRT error 1 on alarm 999999, confirming that the error reporting branch is wired correctly and that a real production failure will be visible in the diagnostic log.

Troubleshooting Matrix

Symptom Most likely cause Fix
Compile error "undefined reference to MSRTGetMsgCSData" Function not declared in the global area of the C-Script Add the forward declaration shown in the header setup section.
Compile error "MSG_RTDATA_STRUCT has no member named dwTextID" Local typedef is missing the trailing dwTextID[4] field, or the order of fields diverges from the runtime expectation Copy the exact typedef order from this article or from the WinCC Scripting manual.
apdiag shows MSRT error 1 on alarm N Alarm number N is not configured in the current HMI image Re-export the HMI configuration; confirm the alarm number in the message editor.
apdiag shows MSRT error 2 on alarm N NULL pointer or text ID zero passed to MSRTGetMsgText Add a != 0 guard before calling MSRTGetMsgText.
apdiag shows MSRT error 7 on alarm N Active runtime language has no entry for the alarm text Open Project > Languages & Resources, add the language, translate the text, and retransfer.
apdiag shows text from a previous alarm Buffer not zeroed between calls; same MSG_RTDATA_STRUCT reused without refresh Call memset(&sM, 0, sizeof(sM)) at the start of the action or assign sM.dwTextID[0] = 0 before the call.
apdiag shows compile-time stamp instead of runtime time Hard-coded test code is still in the action; or the action is reading SYSTEMTIME from a GetSystemTime call rather than sM.stMsgTime Use sM.stMsgTime, which is the alarm event time, not the wall-clock time of the trigger.
Text shows mojibake (e.g. "????" or "ü") Console codepage mismatch between apdiag and the text library Set the runtime to Unicode (default in V15) and avoid manual char casts; pass the buffer to SetTagChar as-is.
Action fires but no output appears apdiag not running, or output redirected to a closed log file Open the apdiag executable from the Windows Start menu; restart the runtime.
Works in simulator, fails on physical panel RT version mismatch: simulator is V15.1, panel firmware is V15.0 or earlier Update the panel firmware, or downgrade the engineering project to match.

Common Field Caveats

Three issues recur in commissioning and should be checked before the first customer-facing test.

  • Text length truncation. The default szText buffer is 512 bytes. Alarms with long multilingual text (German technical terms easily exceed 250 characters) can return error 8. Reduce the alarm text or extend the buffer in the typedef and recompile. The new size must match the runtime's internal buffer; consult the WinCC Scripting manual for the supported maximums.
  • Trigger frequency. A 100 ms cyclic trigger that calls MSRTGetMsgCSData 10 times per second creates measurable CPU load on a Comfort Panel. Use 250 ms or longer, or switch to event-driven triggers anchored on tag edges or on the alarm control's built-in events.
  • Lifecycle of the alarm record. The message system keeps the record in memory for the configured "Update of the message" cycle (default 500 ms). A call more than 2 seconds after the alarm has been acknowledged and cleared can return error 1 even for a previously valid alarm. Decouple the trigger from the time-critical path if you need post-mortem analysis: log the text to an internal tag at trigger time, not at query time.

Performance and Memory Notes

Each call to MSRTGetMsgCSData touches a shared internal message ring buffer under a read lock. The cost is sub-millisecond on a typical 4 GB Comfort Panel. MSRTGetMsgText performs a text library lookup keyed on the text ID and language; cost is also sub-millisecond when the text library is memory-resident. Both functions allocate no heap memory of their own; all buffers are caller-provided, so the dominant cost is the structure copy, which is approximately 1.2 KB per call for MSG_RTDATA_STRUCT and 520 bytes per call for MSG_TEXT_STRUCT. A polling loop at 100 ms is safe on a modern Panel, but if you expand the structures or wrap them in additional logging, recalculate the cycle time to keep CPU under 5% per script.

Migration Notes from WinCC V7 / WinCC flexible

The MSRT API is specific to WinCC Runtime Professional and does not exist in the older WinCC V7 C-script environment. V7 used the MSRTGetMsgText function with a different prototype that took an LPMSG_TEXT_STRUCT by reference and a separate locale index. When porting a V7 project, replace the prototype, audit the structure definitions, and add the extern "C" qualifier if the TIA Portal project compiles C++ actions. The alarm numbers and text IDs are not portable between the two runtimes; the engineering recompile generates a new ID space, so any tag-bound or DB-bound alarm numbers must be remapped.

Security and Operational Considerations

C-Script code executes with the privileges of the WinCC runtime user. printf output is visible to anyone with access to the apdiag pane; do not log credentials, license keys, or process values that the customer has classified as restricted. The SetTagChar call writes to an internal tag, which is then visible on the screen and in the HMI tag logging; review the customer's information classification policy before exposing the alarm text in this manner. If the alarm text contains operator instructions (for example, "Open valve V-12 within 30 s"), confirm that the language and units match the local workforce; a mistranslated alarm is worse than no text at all.

Why does MSRTGetMsgText return error 2 even when the alarm is valid?

Error 2 ("Invalid parameter") is raised when a NULL pointer is passed for the output structure, when pdwError is NULL, or when the supplied text ID is zero. The text ID is zero when the supplementary text block (Info, Loop, or Batch) was not configured for the alarm. Guard the call with if (sM.dwTextID[i] != 0) before invoking MSRTGetMsgText for slots other than dwTextID[0].

Can I retrieve the alarm text in the currently selected runtime language automatically?

Yes. The MSRT text lookup is language-aware and returns the entry for the runtime language active at the moment of the call. The language follows the operator's selection on the HMI; no additional parameter is required. To support the full language matrix, every language must be configured under Project > Languages & Resources and have a translated text entry, otherwise the call returns error 7.

How do I get the alarm text into a WinCC tag for display on a screen?

Pass the resolved tMeld.szText to SetTagChar after the second MSRT call returns zero. Declare the target tag as type String with a length of at least 256 characters to accommodate the default szText buffer. Refresh the tag from the same trigger that calls the MSRT functions; do not query the MSRT API from the screen's OnPropertyChange event because the call rate is uncontrolled.

What is the difference between dwMsgState bits and the alarm control's status column?

They represent the same information but in different formats. dwMsgState uses a bitfield: bit 0 = "Came In" (active), bit 1 = "Went Out" (cleared), bit 2 = "Acknowledged". The alarm control's status column maps these bits to the operator-friendly strings I, O, A (and combinations). When you print dwMsgState for debugging, mask with & 0x07 to display only the state bits and ignore internal flags.

Why does the function work in the PLCSIM-coupled simulator but fail on the physical panel?

The two most common causes are a version mismatch between the engineering project and the panel firmware, and a C-Script permission that is enabled in the simulator's project settings but disabled on the device image. Update the panel firmware to match the TIA Portal version, then verify under Device Properties > Runtime > Scripts > C-Script enabled. Transfer the project with "Software (all)" to overwrite the device image completely.

Back to blog