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.