1

In codesys some functions support what in other languages is usually called 'params', i.e. a function that can take a varied amount of similarly typed variables. For example the ADD Operator (function in ladder).

My question is, if there's any way to do the same in user defined functions?

The only idea that I have so far is to take an ARRAY [*] OF SOMETHING and use LOWER_BOUND and UPPER_BOUND to do the computations. This does work, but requires the user to create an additional array variable every time they want to call my function. For example, we have the CONCAT function that concatenates 2 strings. Suppose I want a CONCAT_ALL function that takes n strings and concatenates them all:

    STRS: ARRAY [0..9] OF STRING := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10];
    // This works, but I want to avoid creating an array variable!
    CONALL1: STRING := CONCAT_ALL(STRINGS := STRS);
    // This doesn't work!
    CONALL2: STRING := CONCAT_ALL(STRINGS := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10]);

(EDIT: As I was asked, I am using Schneider Electric Machine Expert 1.2, or CODESYS compiler 3.5.12.80)

Guiorgy
  • 1,405
  • 9
  • 26

4 Answers4

4

There is hope in the future!

In Codesys V3.5 SP16 it seems to be finally possible to use FUNCTIONs and METHODs with optional arguments. Of course this will be in non-codesys products, like TwinCAT and Schneider, in later versions.

This means you can finally create a CONCAT with 100 arguments and call it with just for example 3! Awesome.

https://www.codesys.com/fileadmin/data/Images/Download/features-and-improvements-V35SP16-en.pdf

Screenshot from the document

Quirzo
  • 1,183
  • 8
  • 10
1

Short answer: There is no way to pass n arguments to a function.

Structured text is a strongly and statically typed language designed for hard real time requirements and it is not a scripting language like Python.

If you have a lot of string manipulations in your code that you don't want to do in python but in your real time loop (and you should assess if it's really necessary depending on your requirements) and still want to make it in a comfortable way, then you have to put some effort in it and build a string manipulation library yourself.

After that you could have a very comfortable function call like this:

sResult := F_Concat6(str1,str2,str3,str4,str5,str6);

I understand that it is tempting to adopt thought and programming patterns learned from other programming languages, but structured text and real time industrial control programming is really another kind of beast compared to common user land programming.

With that I mean, that there are specific reasons why the language is designed as it is and when those principles are correctly understood and applied, rock solid architectures derive from them.

To sum it up, my two cents of advice on this:

Think and write software as expected by your domain and do not port incompatible working methods from other domains.

Filippo Boido
  • 1,136
  • 7
  • 11
1

No you cannot pass n arguments to function.

But you can pass an array, with none fixed number of elements. Syntaxyx for Codesys 2.3.

FUNCTION CONCAT_ALL : STRING(250)
    VAR_INPUT
        asParts: POINTER TO ARRAY[0..10000] OF STRING(20); (* Array of strings *)
        iNum: INT; (* Number of elements *)
    END_VAR
    VAR
        iCount: INT; (* For cycle *)
    END_VAR

    FOR iCount  := 0 TO 10000 DO
        IF iCount > iNum THEN
            EXIT;
        END_IF;

        CONCAT_ALL := CONCAT(CONCAT_ALL, asParts^[iCount]);
    END_FOR;
END_FUNCTION


PROGRAM PLC_PRG
    VAR
        (* Array 1 to test *)
        asTest1: ARRAY[1..2] OF STRING(20) := 'String 1', 'String 2';
        (* Array 2 to test *)
        asTest2: ARRAY[1..3] OF STRING(20) := 'String 1', 'String 2', 'String 3';

        s1: STRING(250);
        s2: STRING(250);
    END_VAR

    s1 := CONCAT_ALL(ADR(asTest1), 2);
    s1 := CONCAT_ALL(ADR(asTest2), 3);
END_PROGRAM
Sergey Romanov
  • 2,949
  • 4
  • 23
  • 38
  • Unfortunate, but ended up with something similar. One thing I changed in particular, is that my input is `STRINGS: ARRAY [*] OF STRING`. Any reason why you chose to go with an array with constant length? – Guiorgy Jul 03 '20 at 11:40
  • Because he used the codesys 2.3 compiler in this example. – Filippo Boido Jul 03 '20 at 11:57
  • By the way this code is very dangerous and could lead to a phone call at midnight asking why the whole production line suddenly stopped in an unrecoverable way. – Filippo Boido Jul 03 '20 at 12:08
  • @FilippoBoido I thought you will appreciate this example :) I would like to have more explanation for your statement. How code like this may be dangerouse? – Sergey Romanov Jul 03 '20 at 13:58
  • @Guiorgy because you taged this question with Codesys tag but had not mention version. So I based my example on 2.3. – Sergey Romanov Jul 03 '20 at 14:00
  • @SergeyRomanov Because you can easily access memory area outside the scope of the passed array.In fact it already happens now.You count from 0 to iCount > iNum.For your asTest1 array which has a size of 2 you pass 2, this means you access 20 bytes of unknown memory that could potentially kill the runtime and disrupt production. – Filippo Boido Jul 03 '20 at 15:35
  • @SergeyRomanov Another problem is the fact that you pass String(20) -> 20 bytes of String but a normal String are 80 bytes. What if someone inadvertently passes an 80 byte string array or an 255 byte string array?You end up concatenating the strings in a wrong way – Filippo Boido Jul 03 '20 at 15:39
  • @SergeyRomanov Even if those two errors are corrected we have always the problem of likely human error when assigning an array and an index to concat_all.It can easily happen to put a wrong number or variable inside the iNum parameter leading again to access violation. – Filippo Boido Jul 03 '20 at 15:43
1

Here is an object oriented example of a string concatenator Function Block:

First we define an Interface with 2 methods:

INTERFACE I_MultipleConcat

METHOD concatString : I_MultipleConcat
VAR_INPUT
    sTarget : STRING;
END_VAR

METHOD getResult 
VAR_IN_OUT
   sRetrieveResult : STRING(1000);
END_VAR

Then the Function Block which implements the Interface:

FUNCTION_BLOCK FB_MultipleConcat IMPLEMENTS I_MultipleConcat

VAR_OUTPUT
    uiLen : UINT;
END_VAR
VAR
    sResult : STRING(1000);
END_VAR

//-------------------------------------------------------------------
METHOD concatString : I_MultipleConcat
VAR_INPUT
    sTarget : STRING;
END_VAR

//make sure that the length of sResult is not exceeded
IF uiLen + INT_TO_UINT(LEN(sTarget)) <= (SIZEOF(sResult)-1)
THEN   

    //add uiLen as offset to sResult memory access          
    memcpy(ADR(sResult) + uiLen,ADR(sTarget),LEN(sTarget));
    uiLen := uiLen + INT_TO_UINT(LEN(sTarget));

END_IF

//return the instance of this FuncBlock in order to concat new strings
//with concatString() or pass the result to another STRING with getResult()
concatString := THIS^;

//-------------------------------------------------------------------
METHOD getResult 
VAR_IN_OUT
    sRetrieveResult : STRING(1000);
END_VAR

sRetrieveResult := sResult;
sResult := '';
uiLen := 0;

You can call it like this:

IF NOT bInit
THEN
    bInit := TRUE;
    //s1 must be a STRING(1000) otherwise compile error
    fbMultipleConcat
        .concatString('Test1 ')
        .concatString('Test2 ')
        .concatString('Test3 ')
        .getResult(s1);     
END_IF
Filippo Boido
  • 1,136
  • 7
  • 11
  • so essentially a String Builder! Not exactly what I was hoping, but a very close and elegant solution. – Guiorgy Jul 05 '20 at 07:22
  • *Just curious, but is there a reason you've made an interface for this?* Nevermind, I tried doing it without an interface and got *"C0185: It is not possible to perform component access '.', index access '[]' or call '()' on result of function call. Assign result to help variable first."*. But WHY? – Guiorgy Jul 05 '20 at 14:16
  • 1
    As you said the interface is necessary to do component access on the result of the method call. We could have also used a pointer to FB_MultipleConcat as a return value of concatString, but this has downsides: 1st, it is counterintuitive because the subsequent calls after the first .concatString('Test1 ') have a different syntax ^.concatString('Test2 ').2nd, because the interface is a contract and we can increase code encapsulation with it.You can access only a set of methods or properties.With the pointer to the Function Block you have access to the complete object. – Filippo Boido Jul 05 '20 at 14:33