I would like to create a type-generic STRUCT
and a paired Function Block
that accept and return variables of a generic type (assumed ANY_NUM
).
This is desired to condense many existing STRUCT
and FB
pairs in the same format using generic number types probably belonging to the ANY_NUM
type into a single generic pair.
In C++, the Generic Structure would be accomplished with a Template Class
, but I cannot find a similar structure in Structured Text.
I tried the generic Function Block on Beckhoff's ANY/ANY_(TYPE) page, however it quickly failed to convert type 'LREAL' to type '__SYSTEM.AnyType'
.
Question:
To what extent can I accomplish this goal in Structured Text?
EDIT:
I mistakenly assumed that ANY
is the only ST generic of relevance.
I have been directed to type T_Arg as a potentially viable candidate.
Example Format of Attempt:
Structure:
TYPE Bounded_Value:
STRUCT
Value : ANY_NUM;
Min_ : ANY_NUM;
Max_ : ANY_NUM;
END_STRUCT
END_TYPE
Function Block:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
END_VAR
(Implementation would bind value_struct.Value
to between value_struct.min_
and value_struct.max_
)
(I gleaned the solution to my problem from Stefan Henneken's blog post on T_Arg.)
The goal can be accomplished, but not particularly cleanly. There are two generic types (that I have found so far) that are applicable: ANY_NUM
and T_Arg
.
(I use ANY_NUM
because it is most relevant to this example. ANY
, ANY_REAL
, or ANY_INT
would also be reasonable options)
Both options have similar structures and function similarly. Each is a structure containing information on the stored variable: its type, a pointer to it, and its size.
Yet there are pros and cons to each. To fulfill this problem most accurately, we would use T_Arg
.
Here is the difference:
Advantage: Variable conversion to ANY_NUM
is done implicitly when a variable is assigned. The input variable needs no pre-conversion before being input into the function, cutting down on the size of code.
Furthermore, it only accepts variables belonging to its domain, so strings will not be used accidentally.
Disadvantage: ANY_NUM
cannot be declared outside of a VAR_INPUT
block and, in fact, provides this error message upon attempt:
Variables of type 'ANY_NUM' only allowed as input of functions.
Therefore ANY_NUM
cannot be used as a STRUCT
variable, even if that STRUCT
is declared as an input to a function. This is why it cannot be used to solve this particular problem.
Advantage: T_Arg
can be declared and used anywhere.
Disadvantage: T_Arg
requires conversion functions for any expected variable type, e.g.:
F_INT()
,F_REAL()
,F_DINT()
, etc.
Therefore type-checking needs to be performed before and after input.
Unfortunately, a variable stored in T_Arg
cannot be directly manipulated. It is necessary to move the stored variable to a temporary variable to use it. So Value
, Min_
, and Max_
would each need conversion from type T_Arg
into type REAL
/INT
/etc.
As we are trying to only use one STRUCT
, Value
would need to be converted again into T_Arg once Bind_Value
finishes manipulating it.
In total, Value
would be converted three times when instantiated and twice per call after.
Structure:
TYPE Bounded_Value:
STRUCT
Value : T_Arg;
Min_ : T_Arg;
Max_ : T_Arg;
END_STRUCT
END_TYPE
Function Block:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
// Other variable type declarations
END_VAR
VAR
val_int : INT;
max_int : INT;
min_int : INT;
END_VAR
CASE (value_struct.Value.eType) OF
E_ArgType.ARGTYPE_INT: // If the struct's Value's type is INT
// Copy generic pointer information into typed pointer
MEMCPY(ADR(val_int), value_struct.Value.pData, value_struct.Value.cbLen);
MEMCPY(ADR(max_int), value_struct.Max_.pData, value_struct.Max_.cbLen);
MEMCPY(ADR(min_int), value_struct.Min_.pData, value_struct.Min_.cbLen);
IF val_int > max_int THEN
value_struct.Value.pData := value_struct.Max_.pData;
ELSIF val_int < min_int THEN
value_struct.Value.pData := value_struct.Min_.pData;
END_IF
// Other variable type handlings
END_CASE
MAIN:
PROGRAM MAIN
VAR
val : INT := -1; //Change this to test
minim : INT := 0;
maxim : INT := 5;
newVal : INT;
bv : Bounded_Value;
bind : Bind_Value;
END_VAR
// Convert INT variables to T_Arg in structure
bv.Value:= F_INT(val);
bv.Max_ := F_INT(maxim);
bv.Min_ := F_INT(minim);
// Bind_Value.value_struct := bv;
bind(value_struct := bv);
// Copy result to newVal
MEMCPY(ADR(newVal), bv.Value.pData, bv.Value.cbLen);