Inheritence of struct,

pernockham
2025-03-05
2025-03-14
  • pernockham - 2025-03-05

    Im looking for a way to define predefined version of the same structures through "extends"/inheritance. What I want to do is best shown with an example:

    TYPE log_item_val_type :
    (
        BOOL_ := 0,
        INT_,
        REAL_,
        STRING_
    );
    END_TYPE
    
    TYPE LOG_DATA_BASE
    STRUCT
        val_type : log_item_val_type;
        (* value, name etc *)
    ENDSTRUCT
    ENDTYPE
    
    (* this/below is not possible as I understand from the compiler?? *)
    TYPE LOG_DATA_BOOL extends LOG_DATA_BASE :
    STRUCT
        val_type : log_item_val_type := log_item_val_type.BOOL_;
    END_STRUCT
    END_TYPE
    
    TYPE LOG_DATA_INT extends LOG_DATA_BASE :
    STRUCT
        val_type : log_item_val_type := log_item_val_type.INT_;
    END_STRUCT
    END_TYPE
    

    etc. for LOG_DATA_REAL and ..STRING.

    The system will not allow me to "re-define" "val_type" however. Instead I must do the work-around of defining four different types with all fields individually defined.

    The benefit if I could extend the base item would be that Im (more) sure the structures are identical. In usage I can call in a specific type rather use the base type and assign which type. Usage by pointer/ref to log_data_base would also be easier..

    (* Usage: *)
    log_data : log_data_int := (name, value etc.)
    (* Instead of: *)
    log_data : log_data_base := (name, value, val_type).. 
    

    Is this possible to achieve in some way that I have missed?

     
  • pernockham - 2025-03-05

    Easier that I thought, by ALIAS

    TYPE log_item_val_type :
    (
        BOOL_ := 0,
        INT_,
        REAL_,
        STRING_
    );
    END_TYPE
    
    TYPE LOG_DATA_BASE
    STRUCT
        val_type : log_item_val_type;
        (* value, name etc *)
    ENDSTRUCT
    ENDTYPE
    
    TYPE LOG_DATA_BOOL : LOG_DATA_BASE := (val_type := log_item_val_type.BOOL_); END_TYPE
    TYPE LOG_DATA_INT : LOG_DATA_BASE := (val_type := log_item_val_type.INT_); END_TYPE
    TYPE LOG_DATA_REAL : LOG_DATA_BASE := (val_type := log_item_val_type.REAL_); END_TYPE
    TYPE LOG_DATA_STRING : LOG_DATA_BASE := (val_type := log_item_val_type.STRING_); END_TYPE
    
     
  • pernockham - 2025-03-07

    Apparently my construction will not work as any of the sub-types are not be able to accept any (further) value-initiation.

    Ie:
    string_item : log_data_base := (string_value := 'test', val_type := STRING_);

    is not eq to (the string_value will not be assigned):
    string_item_2 : log_data_string := (string_value := 'test');

    Because effectively for the compiler this is eq. to a "double" assignment where the compiler sees only the first assignment (my guess).
    string_item_2 : log_data_base := (val_type := STRING_) := (string_value := 'test');

    Im not sure this should be treated as a bug, but it would surely be nice to be able to use this construction (compiler is 3.5.20.40).

    Anyone see any other alternative for solution (other than 4 complete STRUCTS with all fields individually defined)?

     

    Last edit: pernockham 2025-03-07
  • pernockham - 2025-03-08

    Interesting, thanks! Didnt know about ANY before, and will sure find a use-case for it. Im not sure it will help me here though, it seems to me that an ANY will not accept a string assignment?

    I actually think that the "ALIAS" should work, that was a hard found error. I had a fair bit of scratching my head looking for misdirected pointers before I understood what was (not) happening. I will use the work-around of individual structs instead.

     
  • Strucc.c

    Strucc.c - 2025-03-09

    Strangely reminds me to my struggles... Want to do something "Elegant", reusable, universal, practical... In CODESYS??? πŸ™ƒ

    First of all, before you get too deep into this:
    If you could find a way, to make a "universal" log entry object, containing the variable length data itself, you wouldn't be able to store them in an array, or access them like an array, or pass them by value as a type. (please correct me, if I'm wrong, incorrect, or not precise).

    Because... Basically you can't declare a type with variable memory footprint. This is a very deeply embedded characteristic of CODESYS, and all IEC 61131-3 systems, and it has many reasons behind. And yes, it is a very common trap / mistake, to forget about.

    So, with a log entry - I guess - it's pretty much the purpose: store data and metadata together, and then handle it in a uniform way. There are ways to handle this, really depends on what is the purpose. For example:

    1. Entries with fixed length
    (Maybe it is not as evil as it looks for the first time. Depends on the situation, but definitely the fastest and easiest code)

    You can have your base object, with an internal, fixed length string or byte array variable. I would go with a string, and call it _Data.; And then you can make properties, like
    As_Bool, As_Int, As_Real...

    In the 'set' accessors, you can do like:

    pReal := ADR(_Data); // POINTER TO REAL
    As_Real := pReal^;
    

    In the 'get' accessors, evidently:

    pReal := ADR(_Data); // POINTER TO REAL
    pReal^ := AS_Real;
    

    Or, can use ANY type, if you are not obsessed with variable / property like access:

    2. Fixed length, but nicer

    First, some disadvantage to any values:
    - You can only assign values with write access. No literals, constants, etc...
    - Can only be used as input variable of function or function_block
    - Therefore, stg you could reach:
    LogEntry.Initialize (stVariable|rVariable|iVariable|xVariable);

    Just a quick example (it's funny to play with ANY):

    Be careful it was not tested. I'm sure can be done better, please feel free to comment

    FUNCTION_BLOCK FB_LogEntry
    VAR_INPUT
        MsgClass    :   UDINT;  // Like DEBUG, WARN, ERR...
        MsgCode     :   UDINT;  // Like Errors.ERR_FAILED
        MsgTS       :   DT;     // The timestamp
    END_VAR
    VAR
        _Data       :   STRING(80);         // Our data container...
        _Descr      :   __SYSTEM.AnyType;   // A standard descriptor for our data, containing TYPE_CLASS, address and size
    END_VAR
    

    METHOD SET_Value : BOOL
    VAR_INPUT
        anyValue    :   ANY;
    END_VAR
    VAR
        I       : DINT;
        diSize  : DINT;
        pStr    : POINTER TO STRING;
    END_VAR
    
    // Check what did we receive in anyValue. 
    diSize := anyValue.diSize;
    
    // We use constant __SYSTEM.TYPE_CLASS to identify the received data type 
    CASE anyValue.TypeClass OF
        // Maybe we don't want to store references, pointers... and who knows what else...
        __SYSTEM.TYPE_CLASS.TYPE_REFERENCE,
        __SYSTEM.TYPE_CLASS.TYPE_POINTER    :
            SET_Value := FALSE; 
    
        // For the planned types we will be just fine. 
        TYPE_CLASS.TYPE_BOOL,
        TYPE_CLASS.TYPE_INT,
        TYPE_CLASS.TYPE_REAL    :
            SET_Value := TRUE;
    
        // Optionally string can be handled separately, maybe we have received STRING(255), but practically it is shorter than 80 bytes... 
        TYPE_CLASS.TYPE_STRING  :
            pStr := anyValue.pValue;        
            diSize := MIN(anyValue.diSize, LEN(pStr^) + 1); // Get the actual size, and rewrite the received structure member
            diSize := MIN(SIZEOF(_Data), diSize);   // Can chop down the received string to our length...
            SET_Value := TRUE;
    
        // Maybe want to play a little bit more here, to narrow down or convert datatypes, etc...
    
        // Or just reject any other datatype
        ELSE            
            SET_Value := FALSE;
            RETURN;
    
    END_CASE
    
    // Fail, if the received value is still larger than our container...
    IF diSize > SIZEOF(_Data) THEN
        SET_Value := FALSE;
    END_IF
    
    //  Here we should be ok, just set up the  _DataType structure, and copy store the data
    IF SET_Value THEN
    
        THIS^._Descr.TypeClass := anyValue.TypeClass;   // The typeclass is already filtered
        THIS^._Descr.diSize := diSize;                  // Set the (adjusted) size
        THIS^._Descr.pValue := ADR(_Data);              // This will not change, just to be sure
    
        {IF defined (pou:SysMem.SysMemCpy)}
            SysMem.SysMemCpy(_DataType.pValue, anyValue.pValue, TO_UDINT(anyValue.diSize));
        {ELSE}
            // An ugly replacement MemCpy
            FOR I:=0 TO diSize - 1 DO
                _Descr.pValue[I] := anyValue.pValue[i];
            END_FOR
        {END_IF}
    
    // Otherwise, in case of failure maybe better set an empty value (overwrite the former data descriptor)
    ELSE  
        THIS^._Descr.TypeClass := TYPE_CLASS.TYPE_NONE;
        THIS^._Descr.pValue := ADR(_Data);                      
        THIS^._Descr.diSize := 0;                   
    END_IF
    

    METHOD GET_Value : BOOL
    VAR_INPUT
        anyValue    :   ANY;
    END_VAR
    VAR
        I   :   DINT;
    END_VAR
    
    // We just have to serve the data, using the __System.AnyType structure received
    
    // Roughly we can say: 
    IF anyValue.TypeClass = _Descr.TypeClass 
    AND anyValue.pValue <> 0 // This should not be possible, already taken care of by Codesys (?)
    THEN
        {IF defined (pou:SysMem.SysMemCpy)}
            SysMem.SysMemCpy(anyValue.pValue, _DataType.pValue, TO_UDINT(MIN(anyValue.diSize, _Descr.diSize)));
        {ELSE}
            // An ugly replacement MemCpy
            FOR I:=0 TO MIN(anyValue.diSize -1, _Descr.diSize - 1) DO
                anyValue.pValue[I] := _Descr.pValue[I];
            END_FOR
        {END_IF}
    
        // Just to make sure, that our string is terminated...
        IF anyValue.TypeClass = TYPE_CLASS.TYPE_STRING THEN
            anyValue.pValue[anyValue.diSize -1] := 0;
        END_IF
    
        GET_Value := TRUE;
        RETURN;
    END_IF
    
    // ... But can play more
    CASE anyValue.TypeClass OF
    
        TYPE_CLASS.TYPE_WSTRING : ; // Could do conversion
    
        TYPE_CLASS.TYPE_XSTRING : ; // Wow, I have to figure this out
    
        TYPE_CLASS.TYPE_PARAMS : ;  // BTW, what is this, how to use?       
    
        TYPE_CLASS.TYPE_ANYNUM : ;  // ...      
    
    END_CASE
    

    Be careful it was not tested. I'm sure can be done better, please feel free to comment

    3. If you really want to do entries with variable size

    In a standard environment, it would be similar to the previous, except you dont have the container variable _Data, just use a pointer, practically _Descr.pValue

    At Initialize (SET_Value), you have to allocate the memory, would be easy with SysMem.SysMemAlloc - nowadays with SysMem.SysMemAllocData -, and you make sure to release it after use with SysMem.SysMemFreeData...

    SysMemAlloc was already hidden. The problem with this, that sooner or later your application will totally fragment the dynamic memory, and fail...

    So should look for some form of dynMaybe MemUtils.MemoryManager (I am not sure what is the status and the future of it).

    4. You will end up by a LogEntry Factory

    ...

    5. You could still have a look at this

    IEC Snippets

    BTW, Standard Codesys Logger is not a bad choice either. If you are really interested, I share some more code / library.

     
  • Hismoon - 2025-03-14

    Using a UNION instead of a STRUCT alone should give you that flexibility and only occupy memory once.

    And if you want to be really elegant, you could use an FB, which only holds PUBLIC VAR.
    Because a POU can use "EXTENDS"/inheritance. In CODESYS V3, an FB has a lot in common with C++ classes.
    Using ANY or ALIAS is rarely a good idea. It neither won't be pretty, nor have good readability
    and requires you to create a lot of error-prone things around it.

     

    Last edit: Hismoon 2025-03-14
  • Hismoon - 2025-03-14

    --- deleted. Was a bug.

     

    Last edit: Hismoon 2025-03-14
  • Hismoon - 2025-03-14

    --- deleted. Was a bug.

     

    Last edit: Hismoon 2025-03-14
  • Hismoon - 2025-03-14

    --- deleted. Was a bug.

     

    Last edit: Hismoon 2025-03-14
  • Hismoon - 2025-03-14

    --- deleted. Was a bug.

     

    Last edit: Hismoon 2025-03-14
  • Hismoon - 2025-03-14

    --- deleted. Was a bug.

     

    Last edit: Hismoon 2025-03-14

Log in to post a comment.