Siemens EasyScreen FCT External DLL Implementation Reference
EasyScreen is the lightweight scripting environment shipped with legacy SIMATIC HMI panels (OP77A/B, OP170B, TP170A/B, TP177A/B, TP270, OP270, MP270, MP370, plus the integrated SIMATIC Panels C7-635 and C7-636 running the ProTool/WinCC Flexible runtime). It exposes a small set of reserved keywords — PI, PO, PV, PS, FCT, UP, DOWN, CLR, SET, RESET — that can be assigned to keys and events inside a project. Of these, the FCT keyword is the bridge to native code: it lets a screen script invoke a function exported by a Win32 dynamic-link library (DLL). This reference documents the calling contract, the data structures that cross the runtime boundary, the rules that govern the return-value buffer, and the build chain required to produce a DLL that the EasyScreen runtime will actually load.
HMIRuntime API or the ScreenItems scripting layer instead.1. EasyScreen FCT External Function Overview
The FCT keyword binds a screen event (typically a function key, a bit trigger, or a scheduled job) to a fully-qualified function name exported from a DLL. The EasyScreen runtime resolves the function pointer at load time, marshals the argument list into a C-compatible structure array, and dispatches the call. The runtime is a 32-bit Windows CE or Windows XP Embedded process on the panel, so the DLL must be compiled for the same architecture (ARM or x86, depending on the panel family) using the standard Win32 calling convention.
The runtime resolves a call through the following chain:
- Parse the EasyScreen source and locate the FCT reference. The reference includes the DLL file name and the exported function name.
- At project transfer, copy the DLL into the panel's active runtime directory (typically
\Flash\<project>\<dllname>.dllor the equivalent\Storage Card2\...path on flash-based panels). - On the first call, the runtime invokes
LoadLibraryagainst the DLL. If the load fails, the message "Unable open the dll file" is written to the EasyScreen log buffer and the call is aborted. - On
GetProcAddress, the runtime validates the exported symbol and stores the function pointer. - For every invocation, the runtime allocates two
ExtFctStructarrays — one for the input parameters and one for the return-value buffer — and calls the function through the stdcall-equivalent pointer.
The runtime does not generate a stub or wrapper: the DLL author owns the entire marshalling contract. That contract is encapsulated in two structures that every FCT implementation must include verbatim: ExtFctStruct and its CFI_VARIANT union.
2. The ExtFctStruct and CFI_VARIANT Data Structures
The Siemens documentation defines a single, fixed ABI for parameter passing. Both the input array and the return-value buffer are arrays of the same struct. The typedefs in the original Siemens manual fragment contain several typographical errors that must be corrected before the headers will compile under MSVC; the corrected form, which has been used successfully on Win32 panel runtimes, is shown below.
/* extfct.h - canonical EasyScreen FCT ABI (Win32) */
#ifndef EXT_FCT_H
#define EXT_FCT_H
#ifdef __cplusplus
extern "C" {
#endif
/* CFI_VARIANT union - one slot holds exactly one elementary value.
* The runtime writes the active member through the ctyp discriminator. */
typedef union CFI_VARIANT_Tag {
char b; /* byte, boolean (ctyp = 1) */
short int i; /* 16-bit integer (ctyp = 2) */
double r; /* IEEE-754 double (ctyp = 3) */
char* s; /* NUL-terminated ASCII string (ctyp = 4) */
} CFI_VARIANT;
/* ExtFctStruct - one slot per parameter or return value. */
typedef struct ExtFctStructTag {
unsigned char ctyp; /* discriminator; see table below */
CFI_VARIANT value;
} ExtFctStruct;
typedef ExtFctStruct* ExtFctStructPtr;
#ifdef __cplusplus
}
#endif
#endif /* EXT_FCT_H */
Three rules follow directly from the structure definition:
-
Size matches the panel runtime. On a 16-bit panel runtime the integer slot is 16 bits; on a 32-bit runtime it is still 16 bits because the ABI was frozen early. Do not widen
short inttointorlongwithout re-validating the marshalling logic on the target panel. -
The
ctypfield is mandatory. The runtime readsctypto decide which union member is meaningful. If you leave it uninitialised, the runtime may read garbage bytes. -
The string slot (
s) is borrowed, not owned. The runtime passes a pointer to an internal buffer. The DLL must notfree()it and must not retain the pointer past the call.
3. Function Signature and Naming Conventions
The exported function signature is rigid. Siemens documents it as:
extern "C" void __declspec(dllexport) InitConnection(
ExtFctStructPtr FctRet,
ExtFctStructPtr FctPar,
char cNrFctPar);
InitConnection is a placeholder; the actual name you export is the one referenced in the EasyScreen FCT keyword. The three arguments are fixed in order and meaning:
| Argument | Direction | Meaning |
|---|---|---|
FctRet |
Output | Pointer to an array of ExtFctStruct. The DLL fills each slot with one return value. The array length is fixed by the EasyScreen project configuration. |
FctPar |
Input | Pointer to an array of ExtFctStruct. The runtime pre-fills each slot with the converted value of the corresponding EasyScreen argument. |
cNrFctPar |
Input | Signed character holding the count of valid input slots. The runtime guarantees 0 <= cNrFctPar and cNrFctPar <= 32 on every panel family that supports the FCT interface. |
The export directive must include __declspec(dllexport) under MSVC or the equivalent __attribute__((visibility("default"))) under GCC if you target the Win32 cross-toolchain. The extern "C" linkage disables C++ name mangling so that GetProcAddress can resolve the symbol by plain name. The calling convention is __cdecl by default, which is what the EasyScreen runtime expects.
4. Permanent vs. Variable Call Parameters
The Siemens documentation distinguishes two kinds of EasyScreen arguments bound to an FCT call. The distinction governs when the runtime re-evaluates the source expression.
| Argument class | When evaluated | Typical use |
|---|---|---|
Permanent parameter (PI, PS) |
Once, when the EasyScreen script is compiled. The runtime substitutes the literal value into the parameter list at compile time. | Constants such as a station number, a fixed scaling factor, or a tag path that does not change at runtime. |
Variable parameter (PV, PO) |
On every FCT invocation. The runtime reads the current value of the underlying tag or expression and pushes it into the FctPar buffer. |
Live process values, operator entries, or any value whose semantics depend on the moment of the call. |
From the DLL's perspective, both classes arrive in the same ExtFctStruct array and are indistinguishable at runtime. The classification only changes how often the runtime marshals the value. Plan for the worst case: never cache or assume that two consecutive calls will receive identical input.
5. Returning Multiple Values from an FCT Function
The original question in the source material is whether a function with three out parameters can be expressed through FCT. The short answer is yes — but only because out parameters in C# are an illusion over a ref-style buffer, and the EasyScreen ABI already provides that buffer as the FctRet array.
Concretely, the C# method
public void function(double in1, double in2, double in3, double in4,
out double ret1, out double ret2, out double ret3)
{
ret1 = in1;
ret2 = in2;
ret3 = in3 + in4;
}
translates to the C signature
extern "C" void __declspec(dllexport) my_function(
ExtFctStructPtr FctRet,
ExtFctStructPtr FctPar,
char cNrFctPar)
{
double in1 = FctPar[0].value.r;
double in2 = FctPar[1].value.r;
double in3 = FctPar[2].value.r;
double in4 = FctPar[3].value.r;
/* Three return slots: indices 0, 1, 2 of FctRet. */
FctRet[0].ctyp = 3; /* real */
FctRet[0].value.r = in1;
FctRet[1].ctyp = 3;
FctRet[1].value.r = in2;
FctRet[2].ctyp = 3;
FctRet[2].value.r = in3 + in4;
}
On the EasyScreen side, the project must declare three return slots whose target tags receive the values written into FctRet. The runtime copies the slots back into the configured tags after the function returns. There is no syntactic limit on the number of return slots other than the configured buffer size, which is fixed per FCT reference.
6. Parameter Type Encoding (ctyp Values)
The ctyp discriminator tells the runtime which union member is meaningful and which target tag types are compatible. The values documented for the EasyScreen FCT interface are:
ctyp |
Active union member | EasyScreen tag types accepted | C representation |
|---|---|---|---|
1 |
value.b |
Bit, byte |
unsigned char / BOOL
|
2 |
value.i |
16-bit signed integer | short int |
3 |
value.r |
32-bit float, 64-bit double (panel-dependent) | double |
4 |
value.s |
String (max length depends on panel, typically 128 or 256 bytes) |
char* (NUL-terminated ASCII) |
When writing return values, you must set ctyp on every slot you intend to populate. The runtime uses the field to select the conversion path into the destination tag. If you leave ctyp at zero, the slot is treated as uninitialised and the destination tag is left untouched.
For string outputs, copy at most tag_max - 1 bytes into the buffer that FctRet[i].value.s points to and always NUL-terminate. The runtime owns the buffer; do not allocate or free it from the DLL.
7. Building the DLL with Visual Studio
The field report specifies Visual Studio 6.0 as the reference toolchain. In practice the EasyScreen runtime accepts any MSVC compiler that produces a Win32 DLL with the correct ABI, including Visual Studio 2008 (the version the source author is using), Visual Studio 2010, and recent Visual Studio Build Tools as long as the platform toolset is left at v141_xp or earlier and the target is x86. Newer toolchains that default to the UCRT may require a static UCRT link to avoid missing-VCRUNTIME errors on the panel.
Follow this procedure for a Visual Studio 2008 project:
- Open Visual Studio 2008 and choose File › New › Project.
- Select Visual C++ › Win32 › Win32 Console Application (use Win32 Project in newer VS).
- Enter the project name and a path under your EasyScreen project directory. Click OK.
- In the Win32 Application Wizard, click Next ›.
- Under Application type, select DLL. Tick Export symbols only if you want the wizard to emit
__declspec(dllexport)stubs for you. Otherwise leave it cleared. - Click Finish. The wizard generates a DLL skeleton with
dllmain.cpp,stdafx.h, and an empty<project>.cpp. - Add the
extfct.hheader shown earlier to the project. Do not rename or re-define the structures in any other header — the layout must match the runtime's view exactly. - Open the project properties and verify the following settings:
- Configuration Properties › General › Character Set: Use Multi-Byte Character Set (EasyScreen strings are ASCII).
- C/C++ › Code Generation › Runtime Library: Multi-threaded (/MT) for static CRT linkage, or Multi-threaded DLL (/MD) if you ship the MSVCRT redistributable to the panel.
-
C/C++ › Language › Conformance Mode: No on VS2015 and later, otherwise
extern "C"blocks will warn or be partially ignored. - Linker › Advanced › Target Machine: MachineX86 (/MACHINE:X86).
- Implement your function as
extern "C"with__declspec(dllexport). - Build the project. The output file ends up in
Debug\<project>.dllorRelease\<project>.dll.
dumpbin /headers <dll>.dll must report machine (x86). For ARM-based panels (TP177A with CE), recompile with the appropriate Windows CE cross-toolchain and validate against the panel's SDK.8. Step-by-Step: Implementing a Working FCT DLL
The following end-to-end example implements the multi-output function from the field report. The C source compiles into easyscreen_demo.dll; the EasyScreen project binds an F8 key to FCT easyscreen_demo.dll::MyFunction with four inputs and three returns.
/* easyscreen_demo.c */
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include "extfct.h"
/* Helper to populate a real-valued return slot. */
static void put_real(ExtFctStruct* slot, double v)
{
slot->ctyp = 3; /* real */
slot->value.r = v;
}
/* Helper to populate a boolean return slot. */
static void put_bool(ExtFctStruct* slot, char v)
{
slot->ctyp = 1; /* byte / boolean */
slot->value.b = v ? 1 : 0;
}
/* Helper to populate a string return slot. */
static void put_string(ExtFctStruct* slot, const char* src)
{
slot->ctyp = 4;
/* Slot->value.s is owned by the runtime; copy into it. */
strncpy(slot->value.s, src, 127);
slot->value.s[127] = '\0';
}
extern "C" __declspec(dllexport)
void MyFunction(ExtFctStructPtr FctRet,
ExtFctStructPtr FctPar,
char cNrFctPar)
{
/* Defensive input validation. The runtime already enforces the
* count, but never trust the boundary from a cross-DLL call. */
if (FctPar == 0 || FctRet == 0 || cNrFctPar < 4) {
put_bool(&FctRet[0], 0); /* status flag = FALSE */
return;
}
/* Read the four input slots. */
double in1 = FctPar[0].value.r;
double in2 = FctPar[1].value.r;
double in3 = FctPar[2].value.r;
double in4 = FctPar[3].value.r;
/* Compute the three outputs. */
put_real (&FctRet[0], in1);
put_real (&FctRet[1], in2);
put_real (&FctRet[2], in3 + in4);
}
/* Required DllMain for Windows DLLs. */
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Corresponding EasyScreen script excerpt:
; screen.scr - bind F8 to the external function
F8: FCT easyscreen_demo.dll::MyFunction
PI 100.0, 200.0, 5.0, 7.0
PO Tag_Result1, Tag_Result2, Tag_Result3
The PI line supplies four permanent input parameters; the PO line writes the three return slots into the corresponding tags. After transfer to the panel, pressing F8 triggers the DLL call and the runtime copies the three doubles into Tag_Result1, Tag_Result2, and Tag_Result3.
9. Deployment and Runtime Configuration
The compiled DLL has to land in a location the EasyScreen runtime scans. The canonical path is the active project directory on the panel:
\Flash\<project_name>\easyscreen_demo.dll
\Storage Card2\<project_name>\easyscreen_demo.dll ; on flashless panels
Use ProSave or the WinCC Flexible transfer dialog to push the DLL alongside the compiled project. The transfer tool must be configured to copy all files, not only the HMI binaries, or the file will be silently skipped on older transfer dialogs.
Considerations for production deployment:
- Bitness. Confirm the panel's CPU architecture (x86 vs ARM) before choosing the build configuration. The runtime will not load an architecture-mismatched DLL.
-
Dependencies. If the DLL links against the C runtime dynamically, the runtime CRT must be present on the panel. Statically linking with
/MTremoves this constraint but increases the DLL size by roughly 0.5 MB. -
Code page. Strings cross the boundary as raw ASCII. Do not assume UTF-8 or any extended code page; restrict output to
[0x20 .. 0x7E]. - Side effects. The runtime invokes DLLs from its own process context. A hang or unhandled exception inside the DLL will freeze the panel; deploy watchdog logic defensively.
10. Troubleshooting: Common Errors and Fixes
The EasyScreen runtime emits short, English error strings when a call fails. The most common messages, their root causes, and the recommended remediation are summarised below.
| Symptom in EasyScreen log | Root cause | Resolution |
|---|---|---|
| "Unable open the dll file" | Runtime failed to LoadLibrary. Typical causes: DLL not transferred, 32/64-bit mismatch, missing dependency, or path not resolvable from the project directory. |
Verify the DLL is on the panel at the expected path. Run dumpbin /headers on the host to confirm x86 machine. Use Dependency Walker on a Windows host to surface missing imports. |
| "Function not found in dll" |
GetProcAddress could not resolve the symbol. Most often a name-mangling issue: missing extern "C" or __declspec(dllexport). |
Inspect the exported symbol with dumpbin /exports. The name must appear undecorated. Add extern "C" and re-build. |
| Call appears to succeed but return tags remain zero |
ctyp was never set, or the wrong union member was assigned. |
Set FctRet[i].ctyp before writing the value. Use the helper functions in the example. |
| String output is truncated at the first NUL | The destination string tag length is smaller than the buffer the runtime allocated for FctRet[i].value.s. |
Match the destination tag length to the longest possible output. Always NUL-terminate at tag_max - 1. |
| Panel hangs on FCT key press | Infinite loop, blocking I/O, or unhandled exception inside the DLL. | Add a watchdog timer around any blocking call. Avoid scanf, console I/O, or thread joins from inside the DLL. |
Compile error on ExtFctStructTag
|
The header was copied verbatim from the Siemens PDF, which uses square-bracket typos for parentheses in the union / struct definitions. | Replace the PDF fragment with the corrected header shown in Section 2. |
| Linker warning LNK4049 / LNK4217 | Locally-defined symbol has the same name as an imported one — typically because the DLL declares a symbol that the CRT already exports under the same name. | Rename the offending symbol or use #pragma push_macro / pop_macro in the C source. |
For runtime debugging, attach WinDbg over Ethernet to the panel or temporarily route diagnostic output through a TCP socket opened by the DLL against a host listener. The EasyScreen log buffer is too small for verbose traces.
11. Verification and Field Commissioning
After transfer, run the following checklist on the panel before signing off:
- Open the project on the panel and navigate to the screen that contains the FCT key.
- Press the configured function key. The EasyScreen log shows no error on this invocation.
- Inspect the three return tags (via tag simulation or by mapping them to visible indicators). They should reflect the values computed inside the DLL.
- Verify boundary behaviour:
- Minimum input (e.g.
in1 = in2 = in3 = in4 = 0) → outputs equal to the inputs. - Maximum input within the tag range → outputs equal to the inputs.
- String outputs: pass a 128-byte string and confirm truncation is safe.
- Minimum input (e.g.
- Power-cycle the panel and re-trigger the call. The runtime should resolve the DLL on first use without re-transfer.
- Document the DLL version and CRC in the commissioning report so that field replacements can be validated against a known-good build.
The external-function contract in EasyScreen is intentionally narrow, but the same pattern — fixed-ABI C call surface, runtime-owned buffer, externally managed transfer — reappears in adjacent systems such as the CA 2E external-function interface. If you need to validate a similar calling contract in a different runtime, the manufacturer documentation for testing an external function in CA 2E shows the same debugging workflow — define a program stub, expose the function via a known call command, and validate the parameters end-to-end before shipping the integration.
For ongoing maintenance, keep the DLL in version control alongside the EasyScreen source and the project configuration. A dll_version string baked into a debug string slot is a low-cost way to confirm the panel is running the build you expect.
Can an EasyScreen FCT function return more than one value?
Yes. The function declares every return slot up front in the EasyScreen project, and the runtime allocates an ExtFctStruct array of that length for the FctRet argument. The DLL sets FctRet[i].ctyp and the corresponding union member for each slot. Three outputs, as in the original C# example, are supported with no special syntax.
Why does EasyScreen report "Unable open the dll file"?
The runtime could not LoadLibrary the DLL. The four most frequent causes are: the DLL was not transferred to the active project directory, the build is 64-bit while the panel expects 32-bit, the DLL has an unresolved external dependency, or the path in the FCT keyword does not match the on-panel file name. Run dumpbin /headers and Dependency Walker on the host to isolate the cause.
What is the difference between permanent and variable call parameters?
Permanent parameters (PI / PS) are evaluated once when the EasyScreen script is compiled and embedded as literal values in the parameter list. Variable parameters (PV / PO) are re-read from their source tag on every invocation. The DLL sees no difference between the two — both arrive through the same FctPar array — but the classification controls how often the runtime marshals the value.
Must the DLL be compiled with Visual Studio 6.0, or will newer toolchains work?
Visual Studio 6.0 is the canonical toolchain in the Siemens documentation, but any MSVC version that produces a Win32 x86 DLL with the correct ABI works. Visual Studio 2008, 2010, 2015, 2017, 2019, and 2022 have all been used in production, provided the platform toolset stays on v141_xp or earlier and the target machine is x86. For ARM-based panels, recompile with the Windows CE cross-toolchain and the matching panel SDK.
How many input parameters can an FCT function accept?
The runtime passes the count through cNrFctPar, which is a signed character. The practical limit is 32 input slots, which matches the editor's FCT configuration dialog. Validate the count inside the DLL against cNrFctPar before reading any slot to avoid out-of-bounds reads when the project is reconfigured.
What does the ctyp field in ExtFctStruct specify?
ctyp is the type discriminator that tells the runtime which CFI_VARIANT union member is active. The values documented for EasyScreen are 1 = byte / boolean (value.b), 2 = 16-bit integer (value.i), 3 = real / double (value.r), and 4 = string (value.s). The DLL must set ctyp explicitly on every return slot it populates.