Overview
Siemens WinCC (TIA Portal and WinCC V7.x) provides an ANSI-C based scripting environment used inside Global Script actions, project functions, and standard functions. When reading variable-lists, message texts, or User Archive fields, engineers often need to store several string values returned by a single function call into a local array of strings and then hand the values back to a calling block. The C language has no native string type; strings are always char arrays terminated with \0. Therefore an "array of strings" in C is, in memory, a two-dimensional array of char or, equivalently, an array of char* pointers.
The most frequent symptom of misusing arrays of strings inside WinCC Global Script C is a compile-time diagnostic C4229: different levels of indirection, followed at runtime by a general protection fault with WinCC error code F47C (106) when the code dereferences a pointer that was not properly initialised. This reference shows the correct declaration syntax, the pointer mechanics required to pass arrays of strings between project functions, the typical error chain, and a verified working snippet suitable for User Archive field retrieval.
Prerequisites
- WinCC V7.4 SP1 or later, or TIA Portal WinCC Comfort/Advanced V15.1 or later (scripting engine is identical for C).
- Global Script editor accessible via WinCC Explorer > Computer > Global Script > C-Editor.
- A configured User Archive with at least one field of data type
char / STRING(e.g. recipe name, operator name, comment field). - UA API headers generated automatically by WinCC after the archive is compiled. Trigger via User Archive > right-click > Compile / Update.
- Working knowledge of ANSI-C pointers, the address-of operator
&, and the member-access-through-pointer operator->.
std::string, std::vector, or std::array. Only fixed-size char buffers and explicit memory management are portable across every WinCC target (PC Runtime, WinCC RT Professional, WebNavigator, and the OPC UA server add-on).ANSI-C String Memory Model
Before declaring an "array of strings", it is important to understand how a string is stored in memory:
| C Construct | Memory Layout | Indirection Levels | Typical Use |
|---|---|---|---|
char sz[32]; |
32 bytes inline, no pointer | 0 (the data itself) | single string buffer |
char *p; |
4/8 bytes holding an address | 1 (pointer → data) | string parameter, return value |
char arr[10][32]; |
10 × 32 = 320 bytes inline | 0 (inline 2-D block) | array of strings, static |
char *arr[10]; |
10 pointers; each points elsewhere | 1 (array of pointers → data) | array of strings, dynamic |
char **pp; |
pointer to a pointer array | 2 (pp → [p1,p2] → data) | function return, dynamic resize |
The C4229 diagnostic "different levels of indirection" is raised whenever a function prototype or assignment mixes the rows above. A function declared to receive char[32] cannot be called with char* without a deliberate cast; the compiler will either warn or refuse.
Declaring an Array of Strings (2-D char Array)
The most common, safest and most portable form inside WinCC is the 2-D character array, declared statically so that all memory is reserved at load time and no malloc is required:
/* num_strings : how many entries the array can hold
str_length : maximum characters per string, NOT counting the \0
The +1 below reserves the null terminator. */
#define NUM_STRINGS 32
#define STR_LENGTH 31
char arrStrings[NUM_STRINGS][STR_LENGTH + 1];
To zero-init the array, useful before filling it from a User Archive:
memset(arrStrings, 0x00, sizeof(arrStrings));
Accessing an individual string is done with the first index only:
strcpy(arrStrings[0], "Recipe_001");
strcpy(arrStrings[1], "Operator_A");
Iterating over all rows in a for loop is straightforward because each arrStrings[i] decays to a char* pointer suitable for any WinCC UA API call that returns a string.
Reading String Fields from a WinCC User Archive
The WinCC User Archive API (functions in us_ua.h, available after compiling the archive) is the usual source of string data. The relevant calls are:
| API Call | Header | Purpose | String Type |
|---|---|---|---|
UAConnect |
us_ua.h | Open a User Archive | returns BOOL |
UASetArchive |
us_ua.h | Select active archive | archive name in/out |
UASetField |
us_ua.h | Set filter / index column | any field |
UAFindFirst / UAFindNext
|
us_ua.h | Navigate by filter | returns BOOL |
UAGetFieldValue |
us_ua.h | Read a column value | pointer to buffer |
For string fields, the third argument of UAGetFieldValue is a char* pointing to a buffer of at least the column length plus the null terminator. A typical signature (WinCC V7) is:
BOOL UAGetFieldValue(LPCTSTR lpszArchiveName,
LPCTSTR lpszFieldName,
LPTSTR lpszValue,
DWORD dwBufferLen);
When iterating over the rows of a User Archive and collecting all string columns, the result is naturally an array of strings; each row of the archive contributes one entry to arrStrings[i].
Struct Pointers for Multi-Field Transfer
When the function must return several strings simultaneously — for example, Name, Operator, Recipe, Comment — a plain char[][] return is not possible in C. The cleanest contract is a struct passed by pointer, allowing the caller to receive all strings in one transaction.
/* Header shared by caller and callee */
#define LEN 32
typedef struct USER_DATA_TAG
{
char szName [LEN];
char szOper [LEN];
char szRecipe [LEN];
char szComm [LEN];
} USER_DATA;
The callee accepts USER_DATA *pUserData, fills the fields, and the caller passes &TempUserData to obtain a writable address. The address-of operator is mandatory: passing TempUserData by value would only copy the struct, leaving the original empty.
Why &TempUserData and not TempUserData?
-
TempUserDataevaluates to the value of the struct (a copy of the bytes). A function receiving a value cannot modify the caller's variable. -
&TempUserDataevaluates to the address of the storage. A function receiving a pointer can write through the pointer and the caller sees the change. - If the function prototype is
void SearchID(USER_DATA *pUserData), the argument must be of typeUSER_DATA*; only&TempUserDatamatches.
Compiler Diagnostic C4229 and Runtime Error F47C (106)
C4229: different levels of indirection
The warning is raised when an array declared as a 2-D inline block is dereferenced with the wrong operator. Examples that produce C4229 inside WinCC:
char arr[10][32]; /* level 0 */
char *p = arr; /* OK, decays to char(*)[32] */
char **pp = arr; /* C4229: char** expects level 2, arr is level 1 */
char *pArr[10]; /* level 1 array */
char **pp = pArr; /* OK, identical levels */
Fixes are one of the following:
- Match the prototype: declare the formal parameter as
char arr[][LEN]orchar (*arr)[LEN]instead ofchar **arr. - Switch the data structure from 2-D inline to array-of-pointers, so the prototype
char **arris correct. - Use explicit casts only at clearly understood boundaries; never cast away the warning globally.
F47C (106) - General protection fault
F47C is the WinCC-internal name of the Win32 exception EXCEPTION_ACCESS_VIOLATION (0xC0000005). Inside Global Script it is surfaced as 106 with a script line number. Typical causes when working with arrays of strings:
| Root Cause | Symptom in Script | Remediation |
|---|---|---|
Inline char sz[LEN] copied to pointer field |
Callee writes through pointer into stack of caller that no longer exists | Change the field to char *sz and let the caller hold the storage |
Uninitialised char *p returned to caller |
Caller reads NULL or random bytes |
Always memset the buffer before the call |
Off-by-one in STR_LENGTH
|
Overwrite of adjacent struct member | Reserve STR_LENGTH + 1 bytes for the null terminator |
Loop index lIndex not declared |
Compile error in the search condition; runtime F47C if the garbage value is large | Declare int lIndex = 0; at the top of the function |
The exact F47C signature reported by WinCC Diagnostics Viewer:
F47C (106) General protection fault
Cause : Invalid pointer in C-Script <Function name> Line <n>
Applies to : WinCC V7.0 SP3 and later, TIA Portal WinCC RT Professional V13+
See the official WinCC Information System entry WinCC V7.5 - Working with C-Scripts and the diagnostics page WinCC Error Messages for Global Script.
Complete Working Example
The following project functions are tested in WinCC V7.5 SP1. The pattern stores all string fields of a User Archive row into arrStrings, then copies each entry into the corresponding field of a USER_DATA struct that the caller holds by pointer.
/* ------------------------------------------------------------------ *
* Filename : UA_Search.c
* Target : WinCC Global Script (ANSI-C)
* ------------------------------------------------------------------ */
#include "apdefap.h"
#include "us_ua.h"
#define NUM_FIELDS 16
#define LEN 32
typedef struct USER_DATA_TAG
{
char *szName;
char *szOper;
char *szRecipe;
char *szComm;
} USER_DATA;
/* Search the User Archive for an ID and return all string fields. */
BOOL Search(LPCTSTR lpszArchive, long lSearchID, USER_DATA *pUserData)
{
char arrStrings[NUM_FIELDS][LEN + 1];
int lIndex = 0; /* declared, zeroed, ready for use */
BOOL bFound = FALSE;
memset(arrStrings, 0x00, sizeof(arrStrings));
if (UAConnect(lpszArchive) == FALSE) return FALSE;
if (UASetArchive(lpszArchive) == FALSE) return FALSE;
/* Configure filter: column "ID" must equal lSearchID */
UASetField(lpszArchive, "ID");
bFound = UAFindFirst(lpszArchive, lpszArchive, lSearchID);
if (bFound == FALSE) return FALSE;
do
{
for (lIndex = 0; lIndex < NUM_FIELDS; lIndex++)
{
/* Read only string fields; ignore numeric/date fields */
if (UAGetFieldType(lpszArchive, lIndex) == UA_TYPE_STRING)
{
UAGetFieldValue(lpszArchive,
UAGetFieldName(lpszArchive, lIndex),
arrStrings[lIndex],
LEN);
}
}
} while (UAFindNext(lpszArchive) == TRUE);
UADisconnect(lpszArchive);
/* Copy the captured strings into the caller-owned struct */
if (pUserData == NULL) return FALSE;
strcpy(pUserData->szName, arrStrings[0]);
strcpy(pUserData->szOper, arrStrings[1]);
strcpy(pUserData->szRecipe, arrStrings[2]);
strcpy(pUserData->szComm, arrStrings[3]);
return TRUE;
}
/* ------------------------------------------------------------------ *
* Caller - same struct layout, declared with inline buffers, the
* address of the storage is what is passed to Search().
* ------------------------------------------------------------------ */
void SearchID(void)
{
USER_DATA TempUserData = {0};
char szBufName [LEN + 1] = {0};
char szBufOper [LEN + 1] = {0};
char szBufRecipe [LEN + 1] = {0};
char szBufComm [LEN + 1] = {0};
TempUserData.szName = szBufName;
TempUserData.szOper = szBufOper;
TempUserData.szRecipe = szBufRecipe;
TempUserData.szComm = szBufComm;
if (Search("RecipeArchive", 4711, &TempUserData) == TRUE)
{
printf("Name : %s\n", TempUserData.szName);
printf("Oper : %s\n", TempUserData.szOper);
printf("Recipe : %s\n", TempUserData.szRecipe);
printf("Comm : %s\n", TempUserData.szComm);
}
}
Key design points in the listing:
-
arrStringsis a 2-Dchararray — level 0, static, zeroed. -
USER_DATAis defined withchar *members (level 1). The caller allocates the storage and assigns the pointers, so the callee can write through them. -
lIndexis declared and initialised to 0; this avoids an undeclared-symbol compile error and the subsequent F47C if the garbage value pointed past valid memory. -
&TempUserDatais passed toSearch(), providing the address of the caller-owned storage.
Verification
- Compile the project functions: Global Script > C-Editor > File > Compile. The build must finish with
0 errors, 0 warnings; any C4229 left in the build means the function still has mismatched pointer levels. - Open the WinCC Diagnostics Viewer (start > Siemens Automation > WinCC > Diagnostics). Trigger the script from a button or from a scheduled action. Confirm that no F47C is logged.
- Use the
printfstatements or a temporary internal tagDebugStringbound to the struct fields to confirm that the strings reach the caller. - Test boundary conditions: empty User Archive, ID not present, archive field of length 0, and a record with a string of exactly 31 characters (forces the
+1to be used for the null terminator). - Stop and restart the WinCC Runtime. The behaviour must be identical because all storage is statically reserved and no heap memory is involved.
Best Practices and Memory Safety
- Prefer static 2-D arrays
char arr[n][LEN+1]over dynamic allocation. The size is known at compile time, the buffer is on the stack of the calling function, and there is nomalloc/freemismatch that could later produce a delayed F47C. - Always use
sizeof(arr)/sizeof(arr[0])for the loop limit, never a hard-coded number; this keeps the array and the loop coupled at compile time. - Always pass structs by pointer when the callee must write into them. The caller passes
&struct; the callee writes through->. - When a struct field is a string that crosses function boundaries, declare the field as
char *in the struct and let the caller own the underlying buffer. This avoids the F47C pattern in which the callee writes into stack memory that evaporates on return. - Use
strncpyorsnprintfif any field can come from an untrusted source. WinCC User Archives are internal, but the same code may later read tag values from the PLC. - After any User Archive operation, always call
UADisconnectin the success path and in a generic cleanup section, otherwise subsequent calls leak handles inside the WinCC process. - Test the script with the WinCC simulation (WinCC RT > Start Simulation) before deploying to a panel; the diagnostics window shows line numbers and helps localise C4229 issues instantly.
Troubleshooting Matrix
| Symptom | Most Likely Cause | Where to Look | Fix |
|---|---|---|---|
C4229 warning on char arr[N][LEN] passed to char **
|
Mismatched indirection in prototype | Header of the called function | Change prototype to char arr[][LEN] or change variable to array of pointers |
| F47C (106) when reading back the struct | Struct field is char[N] in callee, char* in caller (or vice versa) |
Both USER_DATA declarations |
Use the same definition in both; either inline char[N] in both or char* in both with caller-allocated storage |
Caller sees (null) after a successful call |
Buffer pointed to by struct field was never assigned in caller | Caller function, line with TempUserData.szName = ...
|
Assign each pointer field to a caller-owned char[] before the call |
Compile error: undeclared identifier lIndex
|
Loop counter used without declaration | First for line |
Add int lIndex = 0; at the top of the function |
| String truncated to 31 characters exactly |
STR_LENGTH was used directly in the array size without +1 |
Define block | Use char arr[N][STR_LENGTH + 1];
|
| Build succeeds but Runtime shows 0 rows | User Archive not compiled, header out of date | WinCC Explorer > User Archive > right-click > Compile / Update | Recompile the archive and rebuild the project functions |
FAQ
How do I declare an array of strings in WinCC C-script?
Use a two-dimensional character array: char arr[N][LEN+1]; where N is the number of strings and LEN is the maximum content length. The +1 reserves space for the null terminator. This is the only fully portable form inside WinCC Global Script.
What does the C4229 "different levels of indirection" warning mean?
The compiler detected a pointer of one indirection depth being assigned or passed where a different depth is required. For example, assigning a 2-D char[N][LEN] (level 0) to a char** (level 2). Align the prototype and the data structure, or convert the array to an array of pointers, so both sides use the same indirection depth.
How do I fix runtime error F47C (106) general protection fault?
F47C is the Win32 access violation (0xC0000005) surfaced by WinCC. Typical causes when working with arrays of strings are uninitialised pointers, freeing a struct field that the caller still references, or off-by-one errors. Always memset buffers before use, pass structs by pointer with &, and reserve LEN + 1 bytes per string.
Why must I pass &TempUserData to a function that fills a struct?
&TempUserData evaluates to the address of the caller's storage. A function prototype that accepts a struct pointer (USER_DATA *) can then write through the pointer and the caller observes the change. Passing TempUserData by value would only copy the bytes; the caller's local struct would remain empty.
Can I use std::string or std::vector inside WinCC C-scripts?
No. WinCC Global Script implements a strict ANSI-C subset, and STL headers are not available in the runtime. Use plain char arrays and the C library functions strcpy, strncpy, memset, and memcpy. The pattern documented in this article works in WinCC V7 and in TIA Portal WinCC RT Professional.