(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:
ANY / ANY_NUM / ETC
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.
T_Arg
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.
Example Solution
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);