Calling VBScript from a WinCC C-Action: 4 Cross-Engine Methods
Siemens WinCC (SIMATIC WinCC / TIA Portal WinCC) ships with two distinct scripting engines inside the same runtime: an ANSI-C compiler for C-Actions and a VBScript host for VBS procedures. The two engines share the WinCC Tag Management and the runtime database, but they live in separate memory spaces and are scheduled by different dispatchers. Because of that, an application that wants to "fire" a VBScript function from inside a C-Action (or the reverse) cannot use a direct function call. The official Siemens position is that C and VBS cannot call each other directly in the same project.
This reference describes the four production-grade workarounds that have been used on real WinCC V7.x and WinCC RT/RC projects: the internal-tag trigger pattern, shared project functions, the WinCC Script Connector add-on, and timer-driven polling. Each method is documented with the exact C and VBS code, the tag-management configuration, the diagnostic checks required, and the failure modes that show up during commissioning. The methods are ordered from simplest to most invasive, and the article ends with a migration path to WinCC Unified where the constraint has been removed.
WinCC Scripting Architecture: C-Action vs VBScript Runtime
Understanding why a C-Action cannot call a VBScript directly requires looking at the runtime topology. WinCC loads both engines into the same WinCC Explorer process, but they execute on different stacks:
| Attribute | C-Action | VBScript (VBS) |
|---|---|---|
| Language | ANSI-C (C99 subset, WinCC-specific headers) | VBScript 5.x compatible (no .NET, no CreateObject out of the box) |
| Editor | Global Script C editor, Graphics Designer "C-Action" property | Global Script VBS editor, Graphics Designer "VBS-Action", picture events |
| Compilation | Compiled at edit time to native code, then re-linked on project load | Interpreted at runtime by the WinCC VBS host (CScript-like) |
| Trigger types | Event, Cyclic (configurable in seconds), Tag-triggered | Event (mouse/key/focus/picture), Cyclic timer, Tag change |
| Tag access | GetTag..., SetTag..., GetTagWordState... | HMIRuntime.Tags("...").Read / .Write |
| Process diagnostics | WinCC GSC Runtime (gsc.exe), apdiag output | HMIRuntime.Trace, message boxes, project log |
| Memory space | Native WinCC process, static + heap | WinCC VBS host, separate VBS engine context |
| Callable from other engine? | No direct call. Indirect via tags, project functions, or Script Connector | No direct call. Indirect via tags, project functions, or Script Connector |
Because the two engines do not share a function table, the C runtime cannot resolve a VBS function symbol, and the VBS host cannot bind a C function pointer. Anything that looks like a cross-call must therefore be implemented as a message-passing pattern: one side writes a piece of state (typically a tag), the other side reacts to that state change.
Why Direct Cross-Engine Calls Are Not Supported
Three architectural reasons prevent a direct C-to-VBS (or VBS-to-C) call inside WinCC V7:
- Separate language runtimes. C-Actions are linked into the WinCC process as native x86/x64 code. VBS functions are stored in the project database and instantiated by the VBS host. There is no symbol table shared between the two, and the C linker cannot resolve a VBS function name.
- Different event schedulers. C-Actions are dispatched by the GSC (Global Script C) scheduler using their configured cycle. VBS procedures are dispatched by the picture's event handler, the Global Script VBS scheduler, or the Alarm Logging event hooks. A direct call would require both schedulers to be synchronized, which is not how the WinCC process is architected.
- No exported entry points. Neither runtime exports a C-callable or VBS-callable API for the other's user functions. The only objects both engines can see are the WinCC tag database, the project functions, and (with the Script Connector add-on) a small COM bridge.
The practical consequence is that any project that contains a mix of legacy C-Actions and modern VBS code must implement one of the workarounds below. The pattern is essentially: "C writes a flag tag, VBS reacts; or VBS writes a flag tag, C reacts." The mechanics of that exchange are identical for both directions.
Method 1 — Internal Tag Trigger Pattern
The internal-tag trigger is the most common and the most reliable workaround. It works in both directions and has no third-party dependencies. The pattern is:
- Create an internal binary tag (e.g.,
Trig_VBS_Run) in the WinCC Tag Management. - From the C-Action, write
1to that tag usingSetTagBit()(or to a multi-bit integer tag for parameter passing). - In VBS, configure a tag-change event on the picture, the Global Script VBS editor, or a faceplate. The VBS event handler reads the tag, executes the desired logic, and writes
0back so the trigger is armed again.
The tag must be a WinCC internal tag (not a PLC tag), so the write does not generate network traffic. It is conventional to size the trigger as a 16-bit or 32-bit word so that the VBS side can also receive a numeric parameter (e.g., a recipe number, an alarm index, a screen number).
Step 1 — Create the trigger tag
In WinCC Explorer, open Tag Management → Internal Tags, right-click and choose Add New Tag:
| Property | Value |
|---|---|
| Name | Trig_VBS_Run |
| Data type | Unsigned 16-bit (or Signed 32-bit if you need parameter passing) |
| Length | 2 bytes (or 4) |
| Initial value | 0 |
| Acquisition | On demand (recommended; cyclic adds unnecessary load) |
| Limits / linear scaling | None |
Step 2 — C-Action that fires the trigger
From a C-Action attached to a button "Execute VBS Logic":
// WinCC C-Action — fires the VBS trigger
#include "apdefap.h"
void OnClick(char* lpszPictureName,
char* lpszObjectName,
char* lpszPropertyName)
{
// Optional: pass a parameter word (e.g., recipe number 7)
SetTagWord("Trig_VBS_Run", 7);
return;
}
The C function SetTagWord() is part of the WinCC C API (header apdefap.h). It writes the value synchronously to the tag database; the VBS engine observes the change on its next tag-event dispatch.
Step 3 — VBS handler on the picture
On the same picture, open the properties of the picture itself, navigate to Event → Tag change, choose the tag Trig_VBS_Run, and attach a VBS-Action:
' WinCC VBS — reacts to the C trigger
Sub OnChange(ByVal Item)
Dim trigValue
trigValue = HMIRuntime.Tags("Trig_VBS_Run").Read
' Guard against re-entry on the same change
If trigValue = 0 Then Exit Sub
' ---- Insert VBS logic here ----
HMIRuntime.Trace "Trig_VBS_Run fired with value " & trigValue & vbNewLine
Select Case trigValue
Case 1: Call RecipeLoad(trigValue)
Case 7: Call RecipeLoad(trigValue)
Case 99: Call VBS_OperatorLogout()
End Select
' Reset the trigger so it can be re-armed
HMIRuntime.Tags("Trig_VBS_Run").Write 0
End Sub
The reset is critical. If the C side writes the same value twice (e.g., 1 followed by 1), the tag change event will not fire a second time because WinCC suppresses duplicate writes unless the value actually changes. Resetting the trigger to 0 arms it for the next rising edge.
Step 4 — Verification
- Open the picture in WinCC Runtime.
- Use the WinCC Tag Simulator to manually set
Trig_VBS_Run = 1. Confirm the VBS handler runs and the tag returns to0. - Click the button that contains the C-Action. Confirm the same behavior.
- Watch
HMIRuntime.Traceoutput in the WinCC Diagnostic Pad (View → Diagnostic Pad).
Method 2 — Shared Project Functions and Standard Functions
WinCC V7 lets you author Project Functions (Global Script) that are callable from C and from VBS, but only inside their own engine. A project function written in C is callable from other C-Actions; a project function written in VBS is callable from other VBS handlers. They are not cross-callable. However, the project-function surface area is still useful as a refactor target: put the shared logic in both languages, and let the tag trigger synchronize them.
A common idiom is to write the shared algorithm in VBS (which is faster to maintain) and have a thin C wrapper whose only job is to set the trigger tag:
' VBS project function — actual business logic
Sub VBS_RecipeLoad(ByVal iRecipeNo As Integer)
HMIRuntime.Trace "VBS_RecipeLoad " & iRecipeNo & vbNewLine
' ... real logic ...
End Sub
// C wrapper — fires the trigger that calls VBS_RecipeLoad
#include "apdefap.h"
void OnClick(char* pic, char* obj, char* prop)
{
SetTagWord("Trig_VBS_Run", 5); // 5 = call VBS_RecipeLoad
return;
}
This pattern works because the C function does not need to know the VBS code — it only sets a numeric dispatch value. The VBS handler acts as a router, mapping the value to the correct project function. This is the cleanest pattern for projects that have to coexist with existing C code while new logic is written in VBS.
Method 3 — WinCC Script Connector Tool
Siemens' WinCC Script Connector is a free add-on developed by the WinCC Competence Center Mannheim. It is published on the Siemens Process Management support portal. The tool installs a small COM bridge that allows a VBS script to invoke a named C-Action by name, and a C-Action to invoke a named VBS function by name. It is the only officially supported way to make a real function call across the engine boundary.
| Attribute | Value |
|---|---|
| Vendor | Siemens AG, WinCC Competence Center Mannheim |
| Distribution | Siemens Process Management support portal (login required) |
| Supported WinCC versions | V7.0 SP3 and higher (verify with current readme) |
| Direction | VBS → C and C → VBS |
| Installation | Install on the WinCC server / client where the runtime runs |
| License | Free of charge for licensed WinCC installations |
Typical use case
The Script Connector is most often used to launch a C-Action from VBS — for example, a VBS button that needs to call a legacy C function that is already used elsewhere in the project. The reverse direction (C → VBS) is also supported but is less common because the tag-trigger pattern is simpler and has no third-party dependency.
VBS code that calls a C-Action through the Connector
' VBS — launch a C-Action by its Global Script name
Sub OnClick(ByVal Item)
Dim oConn
Set oConn = CreateObject("WinCC.ScriptConnector.1")
If Not oConn Is Nothing Then
oConn.ExecuteCAction "CA_Run_Recipe_Logic"
Set oConn = Nothing
Else
HMIRuntime.Trace "Script Connector not available." & vbNewLine
End If
End Sub
CreateObject fails with "ActiveX component can't create object", the add-on is not installed on that machine or the WinCC runtime has not picked up the new COM registration. Re-register using regsvr32 against the DLL shipped with the tool, then restart the WinCC runtime.
When to use the Connector vs the tag trigger
- Use the tag trigger when you only need to fire a small number of named routines and you want zero third-party dependencies.
- Use the Script Connector when you need to launch an existing C-Action that already encapsulates non-trivial logic, and refactoring it to VBS is not feasible.
- Avoid mixing both for the same callback. The two mechanisms have different dispatch latencies, and dual-triggering complicates diagnostics.
Method 4 — Timer-Driven Polling Between C and VBS
For low-frequency, fire-and-forget cross-engine calls (e.g., polling for new alarms every two seconds, scanning a recipe queue), a pure polling pattern is often the simplest. The C side writes state on a fixed cycle; the VBS side reads it on a picture timer. There is no event dependency, which makes the pattern tolerant of picture changes and reconnection events.
VBS picture timer (2 s, polling)
' VBS — picture timer event, 2-second cycle
Sub OnTimer(ByVal Item, ByVal Seconds)
Dim w
w = HMIRuntime.Tags("C_State_Word").Read
If w <> iLastW Then
HMIRuntime.Trace "C state changed: " & w & vbNewLine
Call VBS_HandleStateChange(w)
iLastW = w
End If
End Sub
C-Action (cyclic, 1-second)
// C — cyclic 1 s, updates the state word
#include "apdefap.h"
void OnTime(void)
{
static int iPrevState = -1;
int iState = 0;
if (GetTagBit("Alarm_Active")) iState |= 1;
if (GetTagBit("Recipe_Pending")) iState |= 2;
if (GetTagBit("Operator_Request")) iState |= 4;
if (iState != iPrevState) {
SetTagDWord("C_State_Word", (DWORD)iState);
iPrevState = iState;
}
return;
}
Polling is wasteful at high frequencies. Stick to cycles of one second or longer, and only poll tags that already exist. Each tag read on a PLC link consumes bandwidth; the same applies to VBS reads even for internal tags.
Complete C and VBS Code Pair with Tag Reset Logic
Putting methods 1, 2, and 4 together, the most robust pattern for a WinCC HMI screen is:
| Component | File / location | Responsibility |
|---|---|---|
| Trigger tag | Tag Management → Internal → Trig_VBS_Run (Word) |
Carries the request from C to VBS |
| Status tag | Tag Management → Internal → VBS_Status (Word) |
VBS acknowledges back to C |
| C dispatcher | Button OnClick C-Action | Writes Trig_VBS_Run = n and waits for VBS_Status to echo the value |
| VBS router | Picture event → Tag change on Trig_VBS_Run
|
Reads the value, runs the matching project function, writes status, resets trigger |
| VBS business logic | Global Script → Project Functions (VBS) | All real work; called by the router |
Full C dispatcher with handshake
// C-Action on a "Start" button
#include "apdefap.h"
void OnClick(char* pic, char* obj, char* prop)
{
DWORD dwAck;
int i;
// 1) request
SetTagWord("Trig_VBS_Run", 5);
// 2) wait up to 2 s for VBS acknowledgement
for (i = 0; i < 20; i++) {
dwAck = GetTagWord("VBS_Status");
if (dwAck == 5) break;
Sleep(100); // 100 ms per loop, 20 loops = 2 s
}
if (i == 20) {
// timeout — log, do not block HMI
SetTagBit("VBS_Timeout", 1);
}
return;
}
Sleep() loop runs on the UI thread and freezes the picture. For a real project, move the handshake to a C-Action with a cyclic trigger so the picture remains responsive. The example above is a teaching aid; the production version is in the next section.
Production-grade C-Action with cyclic polling
// C-Action — cyclic 100 ms — handles the handshake without freezing the HMI
#include "apdefap.h"
#define TRIG_TAG "Trig_VBS_Run"
#define STATUS_TAG "VBS_Status"
#define REQ_BIT "C_Request_Pending"
void OnTime(void)
{
static int iWaitTicks = 0;
DWORD dwAck;
if (!GetTagBit(REQ_BIT)) return; // nothing to do
dwAck = GetTagWord(STATUS_TAG);
if (dwAck == 5) {
// acknowledged
SetTagBit(REQ_BIT, 0);
SetTagWord(STATUS_TAG, 0);
iWaitTicks = 0;
return;
}
if (++iWaitTicks > 200) { // 20 s timeout
SetTagBit(REQ_BIT, 0);
SetTagBit("VBS_Timeout", 1);
iWaitTicks = 0;
}
return;
}
VBS router + business logic
' VBS — picture event, tag change on Trig_VBS_Run
Sub OnChange(ByVal Item)
Dim req
req = HMIRuntime.Tags("Trig_VBS_Run").Read
If req = 0 Then Exit Sub
Select Case req
Case 5: Call VBS_RecipeLoad(CLng(req)) ' call VBS project function
Case 10: Call VBS_OperatorLogout()
Case 99: Call VBS_AlarmAckAll()
Case Else
HMIRuntime.Trace "Unknown request: " & req & vbNewLine
End Select
' Acknowledge to C side
HMIRuntime.Tags("VBS_Status").Write req
' Re-arm trigger
HMIRuntime.Tags("Trig_VBS_Run").Write 0
End Sub
Tag Configuration: Acquisition, Events, and Limits
Tag configuration is the silent source of most "the trigger does not work" tickets. The settings below are required for the patterns in this article to function correctly.
| Property | Required value for trigger tags | Why |
|---|---|---|
| Data type | Unsigned 16-bit or Signed 32-bit | Word types are the smallest units in which WinCC exposes event changes; bool tags cannot carry a parameter |
| Acquisition mode | Cyclic continuous, with cycle ≥ 1 s, OR On demand | On demand is preferred for trigger tags: the C side writes, the VBS side reads, no scanning load |
| Update / event | Tag change event enabled on the picture (or in Global Script) | Without an event binding, VBS has no idea the tag changed |
| Initial value | 0 | A non-zero initial value can fire the VBS handler on first picture open |
| Limits | None | Limits can cause value clamping on the wrong type of tag |
| Comment / archive | Tag should be excluded from archiving | Trigger tags generate noisy archive traffic if not excluded |
Two settings that look similar but are not the same:
- Acquisition → On demand means the value is updated only when the C side writes it. Recommended for trigger tags.
- Update → On tag change means the value is also pushed to subscribers. For internal tags, both settings are usually fine; for PLC tags, On demand is ignored.
Diagnostics: GSC Runtime, VBS Trace, and Tag Inspector
When the trigger does not fire, the diagnostic flow below resolves the issue in most cases. The order matters — start at the tag, not at the script.
- Tag Inspector (WinCC Explorer → Tools → Tag Management → Tag Test). Set the trigger tag manually to a non-zero value. If the VBS handler does not fire, the problem is in the picture event, not in the C code.
-
GSC Runtime (gsc.exe). Open the WinCC C diagnostic tool. Verify that the C-Action compiled, that the trigger is being executed, and that
SetTagWord()returnsOK. If the function shows "Action not found", recompile the project (C functions are re-linked on project load). -
VBS Trace (Diagnostic Pad). In WinCC Runtime, open View → Diagnostic Pad and watch for
HMIRuntime.Traceoutput. Add trace lines at the start of every VBS handler. If the trace does not appear, the picture event is not bound correctly. -
APDiag (apdiag.exe). Run
apdiagon the runtime machine. It reports the version of the loaded action DLL, the GSC state, and any compilation errors that the editor may have hidden. - WinCC Syslog (WinCC Explorer → Tools → WinCC Syslog). Filter for "GSC", "VBS", or the action name. Re-entrancy bugs and trigger storms usually show up here first.
Common diagnostic outputs
| Symptom | First-place to look | Likely cause |
|---|---|---|
| C writes the tag, value visible in Tag Test, VBS handler does not run | Picture properties → Event → Tag change | Event not bound, or bound to the wrong tag |
| VBS handler runs once, never again | VBS code — the reset to 0 is missing | Re-arm logic not implemented |
C compiles, runs, but SetTagWord has no effect |
apdiag output / project reload | Stale C-DLL; project was not reloaded after the C edit |
| Both engines run, but values look one cycle behind | Acquisition mode | Tag is set to "Cyclic in steps of N"; switch to "On demand" |
| Trigger fires on a WebNavigator client, but the local runtime shows nothing | Picture cache on the client | Stale picture cache; clear from the WebNavigator client console |
| VBS trace says "ActiveX component can't create object" | regsvr32 of the Script Connector DLL | Connector not registered on this runtime machine |
Edge Cases, Re-Entrancy, and WebNavigator Behavior
Re-entrancy on a single picture
If a C-Action fires the trigger inside a tight loop (for example, writing 1 → 0 → 1 → 0 for diagnostic reasons), the VBS handler may be invoked multiple times in quick succession. Add a guard inside VBS:
Dim bBusy : bBusy = False
Sub OnChange(ByVal Item)
If bBusy Then Exit Sub
bBusy = True
' ... work ...
bBusy = False
End Sub
Picture change behavior
Tag-change events bound on a picture are only active while the picture is loaded. If the C trigger fires while the picture is closed, the event will not be processed. For commands that must always be handled, attach the VBS handler in Global Script rather than to a specific picture.
WebNavigator and WinCC Runtime Simulation
WebNavigator clients run the same C-Actions and VBS handlers as the local WinCC Runtime. The tag trigger pattern works unchanged. Two caveats:
- The C-Action runs on the server, not on the client. Time-critical triggers (sub-100 ms) will see additional latency from the client-server round trip.
- The Script Connector is server-side. A VBS handler running inside the WebNavigator browser context cannot reach it; the connector only works inside the WinCC Runtime, not in the browser-hosted viewer.
Alarm Logging and cross-engine calls
Alarm Logging events can fire C-Actions (via the "Actions" tab of an alarm class) and VBS handlers (via the Alarm OCX). Both engines can be triggered, but they cannot call each other. The same tag-trigger pattern applies.
Tag value type mismatch
SetTagBit() from C and HMIRuntime.Tags(...).Read from VBS round-trip a boolean. A common failure is to use SetTagBit on a tag that was declared as Word, which writes a non-zero value that the VBS side then reads as a non-zero word, firing the handler spuriously on the next tag event. Match the C function to the declared tag type.
Migration and Modernization to WinCC Unified
The dual-engine constraint described in this article does not exist in WinCC Unified (TIA Portal). Unified uses a single JavaScript / TypeScript runtime across the HMI, the PLC-side WinCC Unified PC, and the Comfort / Unified Panels. C-Actions have been retired; legacy C is migrated to JavaScript by the TIA Portal migration tool. The tag trigger pattern still works in Unified (and is sometimes the right choice), but cross-script calls can be made directly:
// Unified JavaScript — direct call, no trigger tag needed
export function Button_OnClick(item) {
let r = await VBS_RecipeLoad(5); // direct cross-module call
HMIRuntime.Trace('Recipe load returned ' + r);
}
For existing WinCC V7 projects, the recommended migration path is:
- Inventory all C-Actions and VBS handlers.
- Wrap every C-Action that is called from a VBS context with a tag-trigger wrapper (this article's Method 1 or 2) — this is the stabilization phase.
- Port the C logic to JavaScript one module at a time. Use the trigger pattern as a coexistence bridge: C and VBS during the migration, then C is removed.
- On the Unified upgrade, remove the trigger tags. The remaining VBS logic becomes a direct JS function call.
FAQ
Can a WinCC C-Action call a VBScript function directly?
No. The C and VBS engines run in separate memory spaces and the C linker cannot resolve VBS function names. Use the internal-tag trigger pattern, the WinCC Script Connector add-on, or a shared project function with a tag handshake to bridge the two engines.
What is the simplest cross-engine trigger in WinCC V7?
Create an internal Word tag such as Trig_VBS_Run. From the C-Action, write the request value with SetTagWord("Trig_VBS_Run", n). Bind a VBS picture event to the tag change, run the matching project function, then write 0 back to the tag to re-arm the trigger.
Why does the VBS handler run only once and then stop?
The trigger tag was not reset. WinCC suppresses duplicate tag change events, so writing 1 a second time will not fire the VBS event. Reset the tag to 0 at the end of the VBS handler so the next write of a non-zero value is recognized as a rising edge.
Where do I get the WinCC Script Connector?
The Script Connector is published by the Siemens WinCC Competence Center Mannheim on the Siemens Process Management support portal. It is free of charge for licensed WinCC installations. Always read the version-specific readme before installing — the COM ProgID and supported WinCC versions change between releases.
Does this trigger pattern work in WebNavigator and WinCC Runtime Simulation?
Yes, with the same caveat: C-Actions always run on the WinCC server, not on the WebNavigator client. A VBS handler bound to a picture event is dispatched on the server, so the trigger still works, but expect an extra client-to-server round trip in WebNavigator deployments. Plan sub-100 ms triggers with this latency in mind, or move the timing-critical logic to the PLC.