Search code examples
genericsstructplcsttwincat

Using generic type in Struct & Function Block


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_)


Solution

  • (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);