In the article The wonders of ANY, Jakob Sagatowski shows how the data type ANY can be effectively used. In the example described, a function compares two variables to determine whether the data type, data length and content are exactly the same. Instead of implementing a separate function for each data type, the same requirements can be implemented much more elegantly with only one function using data type ANY.
Some time ago, I had a similar task. A method should be developed that accepts any number of parameters. Both the data type and the number of parameters were random.
During my first attempt to find solution, I tried to use a variable-length array of type ARRAY [*] OF ANY. However, variable-length arrays can only be used as VAR_IN_OUT and the data type ANY only as VAR_INPUT (see also IEC 61131-3: Arrays with variable length). This approach was therefore ruled out.
As an alternative to data type ANY, structure T_Arg is also available. T_Arg is declared in the TwinCAT library Tc2_Utilities and, in contrast to ANY, is also available at TwinCAT 2. The structure of T_Arg is similar to the structure used for the data type ANY (see also The wonders of ANY).
TYPE T_Arg :
STRUCT
eType : E_ArgType := ARGTYPE_UNKNOWN; // Argument data type
cbLen : UDINT := 0; // Argument data byte length
pData : UDINT := 0; // Pointer to argument data
END_STRUCT
END_TYPE
T_Arg can be used at any place, including in the VAR_IN_OUT range.
The following function adds any amount of numbers whose data type can also be random. The result is returned as LREAL.
FUNCTION F_AddMulti : LREAL
VAR_IN_OUT
aArgs : ARRAY [*] OF T_Arg;
END_VAR
VAR
nIndex : DINT;
aUSINT : USINT;
aUINT : UINT;
aINT : INT;
aDINT : DINT;
aREAL : REAL;
aLREAL : LREAL;
END_VAR
F_AddMulti := 0.0;
FOR nIndex := LOWER_BOUND(aArgs, 1) TO UPPER_BOUND(aArgs, 1) DO
CASE (aArgs[nIndex].eType) OF
E_ArgType.ARGTYPE_USINT:
MEMCPY(ADR(aUSINT), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aUSINT;
E_ArgType.ARGTYPE_UINT:
MEMCPY(ADR(aUINT), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aUINT;
E_ArgType.ARGTYPE_INT:
MEMCPY(ADR(aINT), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aINT;
E_ArgType.ARGTYPE_DINT:
MEMCPY(ADR(aDINT), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aDINT;
E_ArgType.ARGTYPE_REAL:
MEMCPY(ADR(aREAL), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aREAL;
E_ArgType.ARGTYPE_LREAL:
MEMCPY(ADR(aLREAL), aArgs[nIndex].pData, aArgs[nIndex].cbLen);
F_AddMulti := F_AddMulti + aLREAL;
END_CASE
END_FOR
However, calling the function is somewhat more complicated than with the data type ANY.
PROGRAM MAIN
VAR
sum : LREAL;
args : ARRAY [1..4] OF T_Arg;
a : INT := 4567;
b : REAL := 3.1415;
c : DINT := 7032345;
d : USINT := 13;
END_VAR
args[1] := F_INT(a);
args[2] := F_REAL(b);
args[3] := F_DINT(c);
args[4] := F_USINT(d);
sum := F_AddMulti(args);
The array passed to the function must be initialized first. The library Tc2_Utilities contains help functions that convert a variable into a structure of type T_Arg (F_INT(), F_REAL(), F_DINT(), …). The function for adding the values has only one input variable of type ARRAY [*] OF T_Arg.
The data type T_Arg is used, for example, in the function block FB_FormatString() or in the function F_FormatArgToStr() of TwinCAT. The function block FB_FormatString() can replace up to 10 placeholders in a string with values of PLC variables of type T_Arg (similar to fprintf in C).
An advantage of ANY is the fact that the data type is defined by the IEC 61131-3 standard.
Even if the generic data types ANY and T_Arg do not correspond to the generics in C# or the templates in C++, they still support the development of generic functions in IEC 61131-3. These can now be designed in such a way that the same function can be used for different data types and data structures.