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:
- 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, orAlarm_Classduring runtime. - The APDiag output window (
apdiag.exelocated atC:\Program Files (x86)\Siemens\Automation\SCADA-RT_V11\WinCC\uTools) stays empty when an alarm triggers. - The custom function appears selectable in the alarm Events tab for Status changed but is greyed out for Incoming.
- The script writes the first byte of the alarm text into the tag, then truncates or produces garbage in the string tag.
- 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), andAlarm_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.
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:
- Compile the project and start WinCC Runtime on the target.
- Launch
apdiag.exefrom%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). - In APDiag, choose File → Open and select GSC Diagnostics. The
printf()stream from every active C-script becomes visible. - Trigger the alarm on the runtime HMI and verify that the payload fields are printed.
- For local-only debugging on TIA Portal WinCC Professional, the GSC Diagnostic window inside the runtime also captures the same stream.
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
- Trigger the configured alarm in runtime and confirm that
HMI_Tag_1toggles to 1 within the polling cycle of the C-script runtime. - Read
Alarm_Numfrom a screen I/O field and confirm it matches the message number in Alarm Logging. - Confirm
Alarm_Textdisplays the message text configured in the alarm properties. - Force the alarm to GO state and verify that
HMI_Tag_1clears if the dispatcher implements the GO branch. - Open APDiag after RT start and confirm at least one
Nr=...line per alarm event.
Best Practices and Field Notes
- Always call
MSRTGetMsgCSData()only whendwMsgState == MSG_STATE_COME. Reading on GO/QUIT frequently returns stale or empty structures. - Never store process-value buffers longer than necessary;
SysMallocleaks 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.