WinCC GMsgFunction C-Script: Configuring Alarm Event Processing

David Krause10 min read
HMI / SCADASiemensTroubleshooting
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

The GMsgFunction() is the standard ANSI-C hook in WinCC Runtime (Professional, Comfort, and V7/V8 SCADA) that fires every time an alarming message transitions state on the HMI/SCADA server. It is the only sanctioned entry point for evaluating message runtime data inside an action attached to an alarm configuration. The function receives a single char* pszMsgData argument that contains a comma-delimited payload holding the message number, state, timestamp with millisecond resolution, duration, counter, internal flags, and up to ten process value slots.

This article consolidates the field-proven procedures for wiring GMsgFunction() in TIA Portal V15 / WinCC Professional V15 and in WinCC V7 / V8.1 SCADA, including APDiag debugging, SysMalloc buffer handling, the difference between the Incoming and Status changed event signatures, and the structural pitfalls that cause scripts to compile cleanly but never fire at runtime.

Technical Background and Architecture

GMsgFunction() is registered through the WinCC Alarm Logging editor under the message property Parameter → Triggers action. Once the flag is set, the runtime routes every MSG_STATE_COME, MSG_STATE_GO, MSG_STATE_QUIT, and MSG_STATE_QUIT_SYSTEM event for that specific message number through the function. The function declaration is fixed and cannot be redefined:

BOOL GMsgFunction(char* pszMsgData);

The WinCC documentation entry "At Runtime, how can you determine the message texts or process values when a message appears" (SIOS ID 15350783) describes the canonical payload layout and the secondary MSRTGetMsgCSData() / MSRTGetMsgText() retrieval chain.

The broader scripting reference for VBS, ANSI-C, and VBA in WinCC V8.1 is documented in SIOS entry 109990013. The recommendation in that entry is: evaluate the message data in a project function called from GMsgFunction. Never replace the standard function with a user-defined one of the same name; instead, keep GMsgFunction as the dispatcher and call a project C-function for the actual logic.

Problem Symptoms

Engineers configuring alarm actions for the first time frequently encounter the following symptoms in WinCC Professional V15 and WinCC V8.1:

  1. The C-script compiles without warnings and tags are declared in the connection list, but no values are written to HMI_Tag_1, Alarm_Num, Alarm_Text, or Alarm_Class during runtime.
  2. The APDiag output window (apdiag.exe located at C:\Program Files (x86)\Siemens\Automation\SCADA-RT_V11\WinCC\uTools) stays empty when an alarm triggers.
  3. The custom function appears selectable in the alarm Events tab for Status changed but is greyed out for Incoming.
  4. The script writes the first byte of the alarm text into the tag, then truncates or produces garbage in the string tag.
  5. Build succeeds but the runtime reports Function not found in the GSC Diagnostic window.

Root Cause Analysis

Cause 1 — Naming collision with the standard function

The most common cause is creating a new ANSI-C function with the same signature as GMsgFunction() in the project scripts. WinCC Alarm Logging binds only to the entry registered under Global Script → Standard functions → Alarm → GMsgFunction. A user copy with the same name in the project scope will not receive the runtime callback, even if it is selected from the alarm event dropdown.

Cause 2 — Wrong event binding signature

The alarm event Incoming passes char* pszMsgData, while the event Status changed passes a fully populated MSG_RTDATA_STRUCT*. Mixing the two produces either a null pointer dereference or fields filled with zeros because the offset of the structure is misinterpreted as the ASCII payload.

Cause 3 — Unallocated buffer passed to SetTagChar

char aux; reserves a single byte on the stack. Passing &aux as the destination of SetTagChar or sprintf writes past the allocated byte and either silently corrupts adjacent stack memory or truncates the string to one character. SysMalloc() must be used to reserve heap memory sized to the tag's character width.

Cause 4 — APDiag launched in the wrong scope

APDiag captures printf() output from runtime C-scripts only when started after the WinCC Runtime has loaded the project. If APDiag is launched before RT, the output stream is not connected. Additionally, output is redirected only when the runtime has the GSC runtime component enabled; in TIA Portal this is the Script execution setting in the HMI device configuration.

Prerequisites

  • TIA Portal V15 / V15.1 / V16 / V17 with WinCC Professional, or WinCC V7.4 / V7.5 / V8.0 / V8.1 SCADA.
  • Alarm Logging licensed and active on the runtime machine.
  • HMI tags HMI_Tag_1 (Bool), Alarm_Num (Word / UInt), Alarm_Text (WString or 32-char String), Alarm_Class (String), and Alarm_Type (String) declared in the tag table.
  • User rights for editing the project on the engineering station and for running the runtime service on the target.
  • APDiag installed (default in WinCC Professional/SCADA installations).

Step-by-Step Configuration

Step 1 — Locate GMsgFunction in Global Script

Open the project in TIA Portal. In the project tree, expand HMI → Runtime settings → Scripts → Global Script C. The standard function GMsgFunction appears under Standard functions → Alarm → GMsgFunction. Double-click to open the read-only template that contains the dispatcher comment block.

Step 2 — Create a project C-function for the parsing logic

Do not edit the standard GMsgFunction. Create a new project C-function (for example ParseAlarmPayload) and call it from the dispatcher. The dispatcher code is:

BOOL GMsgFunction(char* pszMsgData)
{
    if (pszMsgData == NULL) return FALSE;
    return ParseAlarmPayload(pszMsgData);
}

Step 3 — Implement the payload parser

The string delivered to pszMsgData follows the canonical layout defined in SIOS 15350783:

<MsgNr>,<MsgState>,<YYYY.MM.DD>,<hh:mm:ss:mmm>,<TimeDiff>,<Counter>,<Flags>,<PValueUsed>,<TextValueUsed>

Reference implementation:

BOOL ParseAlarmPayload(char* pszMsgData)
{
    MSG_RTDATA_STRUCT mRT;
    CMN_ERROR        pError;
    memset(&mRT, 0, sizeof(MSG_RTDATA_STRUCT));

    sscanf(pszMsgData,
        "%ld,%ld,%04d.%02d.%02d,%02d:%02d:%02d:%03d,%ld,%ld,%ld,%d,%d",
        &mRT.dwMsgNr,
        &mRT.dwMsgState,
        &mRT.stMsgTime.wYear,
        &mRT.stMsgTime.wMonth,
        &mRT.stMsgTime.wDay,
        &mRT.stMsgTime.wHour,
        &mRT.stMsgTime.wMinute,
        &mRT.stMsgTime.wSecond,
        &mRT.stMsgTime.wMilliseconds,
        &mRT.dwTimeDiff,
        &mRT.dwCounter,
        &mRT.dwFlags,
        &mRT.wPValueUsed,
        &mRT.wTextValueUsed);

    printf("Nr=%ld St=0x%lx %04d-%02d-%02d %02d:%02d:%02d.%03d Diff=%ld Cnt=%ld Fl=%ld\r\n",
        mRT.dwMsgNr, mRT.dwMsgState,
        mRT.stMsgTime.wYear, mRT.stMsgTime.wMonth, mRT.stMsgTime.wDay,
        mRT.stMsgTime.wHour, mRT.stMsgTime.wMinute,
        mRT.stMsgTime.wSecond, mRT.stMsgTime.wMilliseconds,
        mRT.dwTimeDiff, mRT.dwCounter, mRT.dwFlags);

    if (mRT.dwMsgState == MSG_STATE_COME)
    {
        MSG_CSDATA_STRUCT sM;
        MSG_TEXT_STRUCT    tEstacion, tClase, tTipo, tMeld;

        MSRTGetMsgCSData(mRT.dwMsgNr, &sM, &pError);
        MSRTGetMsgText(0, sM.dwTextID[1], &tEstacion, &pError);
        MSRTGetMsgText(0, sM.wClass,      &tClase,    &pError);
        MSRTGetMsgText(0, sM.wTyp,        &tTipo,     &pError);
        MSRTGetMsgText(0, sM.dwTextID[0], &tMeld,     &pError);

        SetTagBit("HMI_Tag_1", 1);
        SetTagWord("Alarm_Num", (WORD)mRT.dwMsgNr);
        SetTagChar("Alarm_Text",  tEstacion.szText);
        SetTagChar("Alarm_Class", tClase.szText);
        SetTagChar("Alarm_Type",  tTipo.szText);
    }
    return TRUE;
}

Step 4 — Wire the alarm event

In the HMI alarm editor, select the target message and open the Properties dialog. Switch to Parameter, tick Triggers action, then go to Events → Status changed (recommended) and select GMsgFunction from the dropdown. The Incoming event is also supported but only with the pszMsgData signature.

Note: In WinCC Professional V15, the entry is named Status changed and accepts only functions that match the MSG_RTDATA_STRUCT* signature. The same dropdown will appear empty for user functions whose parameter list does not match exactly. If the dispatcher is implemented correctly, GMsgFunction will appear in the dropdown.

Step 5 — Reserve memory with SysMalloc

When an auxiliary buffer is needed for sprintf output destined for an HMI tag, always use SysMalloc to allocate runtime-safe memory. malloc and alloca are not guaranteed to survive between scheduled C-script invocations in WinCC Runtime.

char* AllocAlarmBuffer(int nLen)
{
    char* pBuf = (char*)SysMalloc(nLen);
    if (pBuf != NULL) memset(pBuf, 0, nLen);
    return pBuf;
}

Release the buffer with SysFree() once the tag has been written. For tag writes performed with SetTagChar(), the runtime copies the buffer before the call returns, so the buffer can be freed immediately afterward.

APDiag Debugging Procedure

APDiag is the canonical capture utility for runtime printf() traffic. The procedure described in SIOS entry 22196775 is:

  1. Compile the project and start WinCC Runtime on the target.
  2. Launch apdiag.exe from %ProgramFiles(x86)%\Siemens\Automation\SCADA-RT_V11\WinCC\uTools (path varies with version: V15 = SCADA-RT_V11, V16 = SCADA-RT_V12, V17 = SCADA-RT_V13).
  3. In APDiag, choose File → Open and select GSC Diagnostics. The printf() stream from every active C-script becomes visible.
  4. Trigger the alarm on the runtime HMI and verify that the payload fields are printed.
  5. For local-only debugging on TIA Portal WinCC Professional, the GSC Diagnostic window inside the runtime also captures the same stream.
Tip: Add a one-shot printf("GMsgFunction entered: %s\r\n", pszMsgData); at the very top of the dispatcher to confirm the function is firing at all. If this line never appears, the event binding is broken; if it appears but the parsed fields are zero, the sscanf format string or struct offsets are wrong.

Status Changed vs Incoming Event

Property Incoming Status changed
Trigger fires on MSG_STATE_COME only COME, GO, QUIT, QUIT_SYSTEM, acknowledged
Argument type char* pszMsgData MSG_RTDATA_STRUCT* (or LPMSG_RTDATA_STRUCT)
Need for sscanf Yes — ASCII payload No — fields are already populated
MSRTGetMsgCSData usable Yes (pass dwMsgNr) Yes (pass dwMsgNr)
Recommended for Capture-only flows Bidirectional state machines, acknowledgements

If the dispatcher is bound to Status changed, it must declare MSG_RTDATA_STRUCT* pRT and dereference fields such as pRT->dwMsgNr directly. The sscanf path is only valid for the Incoming event with pszMsgData.

User Diagnostic Alarms from S7-1200

When the alarm originates in an S7-1200 CPU rather than locally in the HMI, the alarm still arrives in WinCC Alarm Logging and triggers GMsgFunction in the same way. Generating user diagnostic alarms from the PLC side is documented under the TIA Portal instruction reference for Gen_UsrMsg — Generate User Diagnostic Alarm. The instruction sets the EV_CLASS / EV_ID pattern that WinCC recognizes as a discrete diagnostic event.

Troubleshooting Matrix

Symptom Likely cause Remediation
Script never fires Event not bound, or custom function replaced standard Restore the standard GMsgFunction; bind via Status changed
APDiag empty Launched before RT started, or RT script execution disabled Restart RT first, then APDiag; enable script execution in HMI device config
Only one character in tag char aux; not allocated Use SysMalloc(N) with N >= tag width + 1
Function not selectable in dropdown Signature mismatch Ensure the user C-function is invoked from GMsgFunction, not bound directly
Wrong values written sscanf format string drift across version Cross-check against SIOS 15350783, rebuild project
Function not found at runtime Function not generated / compiled Open project scripts, recompile, restart RT
Tags not updated but printf works Tags external or wrong connection Verify connection in tag table; check PLC/HMI connection status
Crash on MSG_STATE_GO Only MSG_STATE_COME branch handled, NULL deref on dwTextID Guard every branch; check sM.wClass != 0 before MSRTGetMsgText

Verification Checklist

  1. Trigger the configured alarm in runtime and confirm that HMI_Tag_1 toggles to 1 within the polling cycle of the C-script runtime.
  2. Read Alarm_Num from a screen I/O field and confirm it matches the message number in Alarm Logging.
  3. Confirm Alarm_Text displays the message text configured in the alarm properties.
  4. Force the alarm to GO state and verify that HMI_Tag_1 clears if the dispatcher implements the GO branch.
  5. Open APDiag after RT start and confirm at least one Nr=... line per alarm event.

Best Practices and Field Notes

  • Always call MSRTGetMsgCSData() only when dwMsgState == MSG_STATE_COME. Reading on GO/QUIT frequently returns stale or empty structures.
  • Never store process-value buffers longer than necessary; SysMalloc leaks accumulate and degrade runtime stability after weeks of operation.
  • If the project must work on WinCC Professional V15 and V17, keep the payload parser in a project C-function; only the dispatcher uses the version-sensitive event binding.
  • Limit printf() traffic in production. Use it for commissioning only and remove or guard with a debug flag before delivery.
  • Document the message number → tag mapping in the alarm properties comment to ease handover.

Where is GMsgFunction located in WinCC Professional V15?

Under Project tree → HMI → Runtime settings → Scripts → Global Script C → Standard functions → Alarm → GMsgFunction. The standard function is read-only; create a project function for your logic and call it from the dispatcher.

Why does APDiag show an empty output window?

APDiag must be launched after WinCC Runtime has loaded the project so the GSC diagnostic channel is registered. Restart the runtime first, then open APDiag and choose GSC Diagnostics. Also verify that Script execution is enabled in the HMI device configuration.

What is the difference between the Incoming and Status changed events?

Incoming passes char* pszMsgData and fires only on MSG_STATE_COME. Status changed passes MSG_RTDATA_STRUCT* and fires on all four states (COME, GO, QUIT, QUIT_SYSTEM). The format string of sscanf only applies to the Incoming payload.

Why is only one character written to my text tag?

The auxiliary variable was declared as char aux;, which reserves a single byte. Pass an unallocated byte to SetTagChar() and the string is truncated. Use SysMalloc(N) with N at least equal to the tag character width plus the null terminator.

Can I trigger user diagnostic alarms from an S7-1200 that WinCC picks up?

Yes. Use the Gen_UsrMsg instruction in the PLC. WinCC Alarm Logging will register the alarm and trigger any GMsgFunction bound to that message number with the same payload format.

Back to blog