WinCC Professional Calling C# User Control Methods from C Scripts

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

Overview

SIMATIC WinCC Runtime Professional (TIA Portal V14 SP1 and later) supports two primary scripting paths: ANSI-C scripts compiled to the runtime and VBScript (VB Script) used inside graphic screens and global actions. Engineers frequently need to forward alarms to email recipients, and the TIA Portal does not expose a native SMTP client in the C script API. The supported Siemens path is documented in the manufacturer's FAQ Email Notification with SIMATIC WinCC Runtime Professional (ID 58983189), which describes a VBScript-based SMTP flow built on the CDO.Message COM object.

This article documents the complementary pattern: wrap SMTP (or any custom notification) inside a C# Windows Forms User Control, expose it as a method on the control, embed an instance of the control on a WinCC screen, and trigger the method from a C script by writing a "dummy" property. This pattern solves two field-proven problems in TIA Portal V14 SP1 Update 7:

  1. Race conditions in alarm-driven VB scripts that drop messages when alarms fire in bursts.
  2. The absence of a native C-script SMTP API, by delegating the network I/O to managed .NET code loaded into the WinCC Runtime process.
Runtime version scope: The procedure below was validated against WinCC Runtime Professional V14 SP1 Update 7 on Windows 7 Embedded Standard / Windows 10 IoT Enterprise panels. The same technique applies to TIA Portal V15, V15.1, V16, V17, and V18, with the corresponding TIA WinCC Professional versions installed on the engineering station and the matching runtime on the target.

Architecture Options for Alarm Email

Three architectures are commonly deployed in WinCC Professional installations. Pick the one that matches your alarm rate, network reachability, and acceptable dependency on PLC scan logic.

Architecture Mechanism Pros Cons
VB Script + CDO.Message on screen VBScript on a screen calls CDO.Message directly. No DLL; uses documented Siemens FAQ path. Tied to a screen; loses messages if screen is not active; subject to VBScript runtime limits.
PLC buffer + WinCC scheduler C script writes alarm text to a PLC tag; tag triggers VB script that drains a buffer. Survives screen changes; can be rate-limited by a timer. PLC scan latency, two-buffer pointer management, dropped alarms if buffer fill exceeds timer window.
C# User Control + C script trigger A System.Windows.Forms.UserControl exposes a SendEmail(string text) method; a C script sets a public property on the control. Survives screen change (the control remains loaded), full .NET SMTP access, deterministic method invocation, easy to unit-test. Requires a separate C# project; must be deployed with WinCC Runtime; version-pinned to the TIA Portal .NET target.

Prerequisites

  • TIA Portal V14 SP1 Update 7 or later installed on the engineering station.
  • SIMATIC WinCC Runtime Professional licensed and installed on the target PC or panel.
  • Microsoft Visual Studio 2013 / 2015 / 2017 (matching the TIA Portal .NET target) to build the C# class library and Windows Forms User Control. Reference Microsoft's How to create a user control (Windows Forms .NET) walk-through for the UserControl template.
  • An SMTP relay reachable from the WinCC Runtime host (corporate Exchange, smarthost, or local relay). Note credentials, port (25 / 465 / 587), and TLS requirement.
  • Administrative access on the runtime machine to deploy the compiled DLL into the WinCC project bin folder.
  • PLC tags (HMI tags in WinCC) declared for the alarm text, buffer index, and "trigger" flag.

Step 1: Build the C# User Control with a SendEmail Method

Create a new Class Library (.NET Framework) project in Visual Studio. Target the same .NET Framework version as your TIA Portal installation (4.5.2 for V14 SP1, 4.6.x for V15/V15.1, 4.7.2 for V16/V17, 4.8 for V18). Add a UserControl item.

  1. In Visual Studio, right-click the project, Add > New Item > User Control (Windows Forms). Name it MailNotifierControl.
  2. Add a public string property DummyTrigger. The C script writes to it; the property setter invokes the send routine.
  3. Implement SMTP using System.Net.Mail.SmtpClient. Do not block the WinCC UI thread; offload the send to System.Threading.Tasks.Task.Run and return immediately.
// MailNotifierControl.cs - compile into Class Library (.NET Framework)
using System;
using System.ComponentModel;
using System.Drawing;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinCCMailLib
{
    public partial class MailNotifierControl : UserControl
    {
        // SMTP configuration - move to app.config in production
        private const string SmtpHost = "smtp.corp.local";
        private const int    SmtpPort = 587;
        private const string SmtpUser = "[email protected]";
        private const string SmtpPass = "P@ssw0rd!";
        private const string FromAddr = "[email protected]";
        private const string ToAddr   = "[email protected]";

        public MailNotifierControl()
        {
            InitializeComponent();
        }

        // Public property the WinCC C script writes to.
        // Any set operation triggers SendEmail_implementation with
        // the current text held in BufferText.
        private string _dummy = string.Empty;
        public string DummyTrigger
        {
            get { return _dummy; }
            set
            {
                _dummy = value ?? string.Empty;
                if (_dummy.Length > 0)
                {
                    SendEmail_implementation(BufferText);
                }
            }
        }

        // Public text buffer the C script can populate before triggering.
        private string _buffer = string.Empty;
        public string BufferText
        {
            get { return _buffer; }
            set { _buffer = value ?? string.Empty; }
        }

        // Public method exposed to the runtime.
        public void SendEmail_implementation(string text)
        {
            // Marshal network I/O off the UI thread so WinCC remains responsive.
            Task.Run(() =>
            {
                try
                {
                    using (var msg = new MailMessage())
                    {
                        msg.From     = new MailAddress(FromAddr, "WinCC Alarm");
                        msg.To.Add(ToAddr);
                        msg.Subject  = "[WinCC Alarm] " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                        msg.Body     = string.IsNullOrEmpty(text)
                            ? "An alarm has occurred."
                            : text;
                        msg.IsBodyHtml = false;

                        using (var client = new SmtpClient(SmtpHost, SmtpPort))
                        {
                            client.EnableSsl   = true;
                            client.UseDefaultCredentials = false;
                            client.Credentials = new NetworkCredential(SmtpUser, SmtpPass);
                            client.DeliveryMethod = SmtpDeliveryMethod.Network;
                            client.Send(msg);
                        }
                    }
                }
                catch (Exception ex)
                {
                    // Log to WinCC diagnostics path so the field engineer can pick it up.
                    System.IO.File.AppendAllText(
                        @"C:\Siemens\Automation\WinCC_RT_Professional\MailLog.txt",
                        DateTime.Now + " " + ex.ToString() + Environment.NewLine);
                }
            });
        }
    }
}

The DummyTrigger / BufferText pair is the pattern recommended by the field community when you cannot directly invoke a method on a UserControl from a C script: you set a property, the property setter dispatches the call. (See the source thread's schufri example for the original pattern that this article extends with the SMTP payload.)

Step 2: Compile and Deploy the DLL

  1. In Visual Studio set the project to Release | AnyCPU and build. The output DLL is, for example, WinCCMailLib.dll.
  2. Copy WinCCMailLib.dll into the WinCC Runtime project folder on the target, typically <Project>\WinCCRT\<PCName>\bin\ (or the corresponding path inside a TIA Portal archive after Compile > Software (PC)).
  3. Confirm that the .NET Framework version of the DLL matches the runtime. Mixed-version loads are a common cause of FileNotFoundException at WinCC startup.
  4. Register the control in the WinCC screen designer by opening the Toolbox > Choose Items > .NET Framework Components dialog and browsing to WinCCMailLib.dll. The MailNotifierControl should now appear in the toolbox.
Strong-name signing: If the runtime machine enforces a strong-name policy on the Global Assembly Cache, sign the assembly in the project properties and deploy it via gacutil -i or copy it into the WinCC bin directory. Unsigned assemblies still load from bin\ in most panels.

Step 3: Place the User Control on a WinCC Screen

  1. In the TIA Portal project tree, expand HMI > Screens and open (or create) a permanent screen, e.g. Screen_100_AlarmMail. WinCC Runtime only invokes a screen's loaded controls while the screen is the active screen; a permanent background screen is therefore required.
  2. Drag MailNotifierControl from the toolbox onto the screen. Resize it to a minimal footprint, e.g. 1 x 1 pixel, positioned at (0,0) or off-canvas.
  3. Open the control's properties and assign a stable name, for example MailNotifier1. This name is what the C script will use to access the control's properties.
  4. Verify the screen's Global Animation / Always-On-Top settings so the screen loads at runtime startup. Configure this in Runtime Settings > General > Start Screen and ensure the screen is set as the start screen or is opened by a global scheduler at boot.

Step 4: Write the C Script that Triggers the Control

WinCC C scripts have access to the active screen's GetObject and SetProp helpers. Use them to write the DummyTrigger and BufferText properties on the control. Trigger the C script from the Status Changed alarm event.

  1. Open HMI > Events > Alarms > (alarm of interest) > Status Changed.
  2. Add a C function, for example NotifyMailOnAlarm.
  3. Pass the alarm text via the lpszPictureName and standard alarm API.
// NotifyMailOnAlarm.c - WinCC C script attached to an alarm Status Changed event
#include "apdefap.h"

void NotifyMailOnAlarm(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
    // Buffer 1: tag that holds the comma-joined alarm texts.
    // BufferFull: tag that the PLC sets to TRUE when the buffer hits its capacity.
    const char* TAG_BUFFER       = "HMI_AlarmBuffer_1";
    const char* TAG_BUFFER_FULL  = "HMI_BufferFull_1";
    const char* TAG_LAST_TEXT    = "HMI_LastAlarmText";

    // Grab the alarm text passed in via lpszObjectName (the alarm name) and any
    // additional payload formatted by your alarm logging script.
    const char* alarmText = lpszObjectName;

    // Append to the HMI tag (your own concatenation function).
    AppendToHmiTag(TAG_BUFFER, alarmText);
    SetTagChar(TAG_LAST_TEXT, alarmText);

    // If the PLC flagged the buffer as full, drain it by writing to the
    // UserControl on Screen_100_AlarmMail.
    if (GetTagBit(TAG_BUFFER_FULL))
    {
        const char* SCREEN_NAME   = "Screen_100_AlarmMail";
        const char* CONTROL_NAME  = "MailNotifier1";

        // Set the body text first.
        SetPropChar(SCREEN_NAME, CONTROL_NAME, "BufferText",  GetTagChar(TAG_BUFFER));
        // Trigger the SendEmail call by writing a non-empty DummyTrigger.
        SetPropChar(SCREEN_NAME, CONTROL_NAME, "DummyTrigger", "GO");

        // Reset the HMI-side buffer tag; PLC handles pointer flip.
        SetTagChar(TAG_BUFFER, "");
        SetTagBit(TAG_BUFFER_FULL, 0);
    }
    else
    {
        // Optional: still send an immediate single-alarm notification for
        // time-critical loops. Same mechanism.
        const char* SCREEN_NAME   = "Screen_100_AlarmMail";
        const char* CONTROL_NAME  = "MailNotifier1";
        SetPropChar(SCREEN_NAME, CONTROL_NAME, "BufferText",  alarmText);
        SetPropChar(SCREEN_NAME, CONTROL_NAME, "DummyTrigger", "GO");
    }
}
Function signatures: SetPropChar, GetTagBit, SetTagChar, and GetTagChar are part of the WinCC C API. Check the Siemens Industry Online Support portal for the exact C-script header set of your TIA Portal version. Names such as AppendToHmiTag above are user-defined; declare them in the project header.

Step 5: Buffering Logic to Avoid Lost Alarms

The original PLC buffer approach in the source thread is fragile when alarms fire faster than the send window. A robust variant uses two server-side accumulator strings in the HMI tag database, swapped by the PLC when a "drain" tag is set.

Tag Direction Type Purpose
HMI_AlarmBuffer_1 HMI <-> PLC WString[512] Active accumulator; receives concatenated alarm text.
HMI_AlarmBuffer_2 HMI <-> PLC WString[512] Staging accumulator; ready to swap in.
HMI_BufferFull_1 PLC <- HMI Bool PLC raises when buffer approaches a high-water mark.
HMI_BufferActive HMI <- PLC Bool Indicates which buffer is currently active.
HMI_DrainRequest PLC <- HMI Bool WinCC sets when an email send has been queued.
HMI_DrainAck HMI <- PLC Bool PLC acknowledges that the buffer was cleared and swapped.

On a high-rate alarm burst, the C script appends to the active buffer, sets HMI_BufferFull_1 when length > 380 chars, and the PLC swaps buffers and raises HMI_DrainAck. The C script reads the staged buffer only after HMI_DrainAck is true, eliminating the race condition where two C-script invocations both send at the same time.

Alternative: Direct SMTP from a VB Script (No DLL)

If you cannot ship a C# DLL with the project, follow the Siemens official FAQ 58983189 - Email Notification with SIMATIC WinCC Runtime Professional. The canonical implementation uses the CDO.Message COM object from a VBScript scheduled action:

' VBScript scheduled action - send queued alarms
Dim msg, cfg
Set msg = CreateObject("CDO.Message")
Set cfg = msg.Configuration
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing")      = 2 ' SMTP
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver")      = "smtp.corp.local"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport")  = 587
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1 ' basic
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendusername")    = "[email protected]"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/sendpassword")    = "P@ssw0rd!"
cfg.Fields("http://schemas.microsoft.com/cdo/configuration/smtpusessl")       = True
cfg.Fields.Update

msg.From     = "[email protected]"
msg.To       = "[email protected]"
msg.Subject  = "[WinCC Alarm] " & FormatDateTime(Now, 0)
msg.TextBody = SmartTags("HMI_AlarmBuffer_1")
msg.Send

This path is preferred for fleets where you cannot deploy a C# DLL. Combine it with the two-buffer swap in Step 5 to avoid lost alarms under burst conditions.

Verification

  1. Unit test the DLL: open a WinForms test harness, instantiate MailNotifierControl, set BufferText to a known string, write any non-empty value to DummyTrigger, and confirm the SMTP relay logs the message. The C# code path is independent of WinCC, so failures here are pure .NET bugs.
  2. Live-Watch in WinCC: in the runtime, open the HMI tag list, force HMI_AlarmBuffer_1 to "TEST ALARM 1\nTEST ALARM 2", then watch MailLog.txt in C:\Siemens\Automation\WinCC_RT_Professional\ for the SMTP send. A successful run leaves a 250 OK response from the relay.
  3. Trigger a real alarm: force a high-priority alarm, observe the Status Changed event firing the C function, and verify the email arrives in the test mailbox within five seconds.
  4. Burst test: use a script to fire ten alarms within 200 ms. Confirm that the staging buffer flips exactly once and only one or two emails are sent, each containing all ten messages. No events should be missing from the email body.
  5. Screen-close test: with the runtime running, navigate away from the alarm-mail screen. Re-fire an alarm. The email should still arrive because the screen remains loaded by the runtime scheduler.

Troubleshooting Matrix

Symptom Likely Cause Fix
SetPropChar returns -1, no email sent. Screen is not loaded; MailNotifier1 does not exist on the active screen. Confirm the control's name in the screen's properties; ensure the screen is loaded as start screen or by a global action.
Email never arrives, no exception in MailLog.txt. SMTP relay unreachable from the panel; firewall blocks port 587. Telnet from the runtime host to smtp.corp.local 587. Add firewall rule; verify TLS cert chain.
Alarms lost during a 50-event burst. Single-buffer pattern with a timer-driven drain; timer expires before buffer is full. Switch to the two-buffer swap pattern in Step 5 and let the PLC raise the drain flag.
Exception FileNotFoundException: WinCCMailLib at runtime start. Wrong .NET Framework version of the DLL; assembly shadow-copy issue. Rebuild against the TIA Portal's .NET target version; copy DLL into bin\.
Exception SmtpException: 5.7.0 Authentication required. SMTP relay requires authentication but client uses default credentials. Set UseDefaultCredentials = false and supply NetworkCredential explicitly.
C script NotifyMailOnAlarm never fires. Alarm's Status Changed event is not configured; the alarm is not in the active alarm class. Open HMI Alarms > (alarm) > Events and bind Status Changed to the C function; check alarm class filter.
Control property BufferText is empty in the email body. C script writes BufferText with the alarm name but the alarm text is delivered separately by WinCC. Pass the alarm message text via lpszObjectName or read the alarm text via the GetAlarmText API in C.

Field-Proven Caveats

  • WinCC Runtime Professional runs as a service on RT-panels. When using System.Net.Mail, ensure the service account has network access; on locked-down Windows IoT images, the default SYSTEM account is fine for outbound SMTP but cannot access mapped drives for log file output.
  • SetPropChar on a C# property is marshaled by the WinCC script engine; avoid calling it more than ~10 times per second or the runtime can stall on panel-class hardware. Use the buffer-and-trigger approach to keep the rate low.
  • Keep the UserControl in a screen that is loaded at startup. Hiding the screen is fine; closing it removes the control and breaks subsequent SetProp calls.
  • For high-availability sites, queue emails in a local SQLite store and drain from a background thread so a transient SMTP outage does not drop notifications. The Task.Run wrapper above already non-blocks the UI; the SQLite layer can sit beside it.
  • On V14 SP1, hotfix Update 7 (or later) is required to correctly marshal string parameters into UserControl property setters. Earlier updates occasionally return E_INVALIDARG silently.

FAQ

How do I trigger a method on a C# UserControl from a WinCC Professional C script?

WinCC C scripts cannot call managed methods directly. Add a public property (for example DummyTrigger) on the UserControl whose setter invokes the method. Use SetPropChar(lpszPictureName, "ControlName", "DummyTrigger", "GO") from the C script; the property setter dispatches the call to SendEmail_implementation.

Can I send email from a WinCC Runtime Professional C script without a DLL?

Not directly. The C API does not expose SMTP. Use the VBScript path with CDO.Message documented in Siemens FAQ 58983189, or wrap SMTP in a C# User Control and trigger it from the C script as shown above.

Why are some alarms lost during high-rate bursts when using a PLC tag buffer?

Single-buffer schemes drop events when the drain timer expires before the buffer fills, or when two C-script invocations send at the same time. Implement a two-buffer swap with HMI_BufferActive, HMI_DrainRequest, and HMI_DrainAck handshakes so the PLC owns the buffer pointer and the C script only sends after the swap completes.

Which TIA Portal versions support the C# UserControl approach?

TIA Portal V14 SP1 Update 7 and later (V15, V15.1, V16, V17, V18, V19) all support it. Match the C# project's .NET Framework target to the TIA Portal version (4.5.2 for V14 SP1, 4.6.x for V15/V15.1, 4.7.2 for V16/V17, 4.8 for V18/V19) to avoid FileNotFoundException at runtime start.

Do I need to add the UserControl to a visible WinCC screen?

Yes. The control must be hosted on a screen that is loaded at runtime. You can place it off-canvas or at 1 x 1 pixel; visibility is not required, but screen load is. Set the host screen as the start screen or open it from a global action at boot.

Back to blog