4

Say I have an abstract function block AValve that I extend for various types of valve. I extend that AValve in order to implement it as a BasicValve. Also I have a function block that takes an array of AValve, which looks like this

FUNCTION_BLOCK ValveDispatch
VAR_IN_OUT
   valves : ARRAY[*] OF AVALVE;
END_VAR

If I try to pass an array of BasicValve into this function block, I'm met with:

Cannot convert type 'ARRAY [0..5] OF BasicValve' to type 'ARRAY [*] OF AVALVE' of VAR_IN_OUT 'valves'

Thinking that maybe codesys just couldn't handle both extended types AND variable length arrays at the same time, I've tried doing a set length array as an input, just for testing as I need the variable length. Doing so gives a slightly different error that seems to mean the same thing:

Type `ARRAY[0..5] of BasicValve' is not equal to type 'ARRAY [0..5] OF AVALVE' of VAR_IN_OUT 'valves'

Is there a way I can make this work? Passing a single extended object into an input expecting its base type works fine, but doing so with arrays seems to be unsupported.

Taeo
  • 55
  • 7
  • 4
    Interesting question! I couldn't manage to get it work. One solution could be that you create an interface "I_Valve", that has all the same methods/properties as the abstract FB (which would implement that interface). Then your ValveDispatch FB could take `valves : ARRAY[*] OF I_Valve;` as input. Problem is that you would need to create/populate the array of interfaces before calling, as it won't accept any other data type, even though it would make sense to accept.. – Quirzo Sep 25 '21 at 06:13
  • @Quirzo, make that into an Answer so it can be accepted – Guiorgy Sep 26 '21 at 20:56
  • @Quirzo unfortunately that won't suit my purposes for a couple reasons. Firstly, an array of interfaces suffers the same issue, in that I cannot use an array of extended interfaces in its place. If you wonder why I'd do that, its because those objects would also be used elsewhere. Also my implementation of these valves uses actions, which you can include in an abstract object but not an interface. Seems polymorphism and inheritance just aren't completely supported by codesys. Currently experimenting with implementing a custom list object instead, not ideal but could work – Taeo Sep 27 '21 at 15:18
  • @Taeo Do you want to pass the actual valve objects through or just pointer to the objects? I think the approach of a generic high level array object with an internal coercion to the appropriate pointer/data type could work but it's not going to be particularly clean. Depending on the level of implementation of this array I would say that it may actually be easier to implement a linked list which can be used similar to an array albeit with a lot more setup and backend processing. – Steve Sep 27 '21 at 19:37
  • @Taeo check out the [__QUERYINTERFACE](https://help.codesys.com/api-content/2/codesys/3.5.16.0/en/_cds_operator_queryinterface/), it allows you to cast a IBase interface into a IChild at runtime. EDIT: Also, can't you use Methods instead of Actions? You can make them private/protected if you don't want them exposed – Guiorgy Sep 28 '21 at 09:05
  • @Steve I don't believe there is a way to pass an actual function block and not a reference/pointer, even if I wanted to, haha. An Array[*] of pointer to AValve may work, much as I'd hate to use it. My goal is to write a library that people with less knowledge than me can use, so I try to avoid pointers at all costs – Taeo Sep 28 '21 at 17:34
  • @Guiorgy Actions allow you to pass VAR_IN_OUT variables properly in CFC, while methods don't really support that. We make pretty extensive use of CFC at my workplace, as well as SFC, for readability and debugging. EDIT: Queryinterface only gives you other interfaces afaik, and querypointer provides no way to check to the type of the thing you're casting, so they're not quite what I need – Taeo Sep 28 '21 at 17:35
  • Does your initial array consist of all Valves of the same type `ARRAY [0..5] OF VALVEA`? If this is the case then it _may_ be feasible to have a basic `fb_ValveDispatch` that is then extended to `fb_ValveADispatch` and `fb_ValveBDispatch` with changing input types for the array. It's probably not what you want but without moving to Pointers/Interfaces I dont think there is any other way to catch all the various types. As you said you wanted to make this into a library, there is no real reason to decrease its complexity, end users should never need to know whats inside, just that it works. – Steve Sep 28 '21 at 18:12
  • @Taeo, we don't use CFC and Actions, so sorry if I am missing something, but you can define VAR_IN_OUT and REFERENCE TO variables in Methods. As for the Querryinterface, yes it casts from one interface to another. You may define a base Interface and FB, and anytime you extend and add methods, you can also extend the interface and querry base to child in a method that recieves a base interface variable (or array of them). If somebody finds a better way, I'd also like to hear it. Worst case, you can try doing it manually through pointers (or maybe better ANY types), though not recommended – Guiorgy Sep 29 '21 at 08:27

3 Answers3

2

The issue you are seeing actually comes down to a pretty simple point, arrays are value datatypes.

That is, when you are trying to pass ARRAY [*] OF FB_Ext through the VAR_IN_OUT what you are actually doing is creating a whole new datatype ARRAY [*] OF FB_Ext which does not extend ARRAY [*] OF FB_Base.

IF you look at an array at the bit level in ST the issue becomes pretty clear, an array itself has a size of ElementSize * ElementCount, So trying to force a bigger datatype FB_Ext into the smaller dataspace of FB_Base is not going to work without messing everything up.

So you will have to do this handling with either pointers or interfaces.


TLDR: What you want to do is not possible in ST.

So this was originally going to be a comment, but it got a bit beyond the size limit. Posted as a wiki instead.

Steve
  • 963
  • 5
  • 16
  • passing FB's is always by reference, so it seems silly that this is the case, I assumed an array of Function blocks would really be an array of references. That's too bad – Taeo Oct 01 '21 at 16:05
1

The short answer: With Var_In_Out it is not possible. Var_in_out is a kind of call by reference. And a reference has always a strict typebinding in TwinCAT.

My first approach would be to solve this with a Interface-Pointer as simple var_input. And the interface contains size and type of the field you handover (as Array[*] also does). But I am not sure if derived interface pointers are allowed to handover. Maybe you have to handover the base interface and do a interface conversion with __TCQUERYINTERFACE somewhere manually.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Gauss3k
  • 141
  • 3
  • Arrays in codesys/twincat simply don't appear to respect inheritance, even normal arrays that aren't variable length. In our designs we don't typically use interfaces since they can only add methods and properties, while we make extensive use of CFC's and therefore want input/outputs, so abstract classes are more useful. Which means query shenanigans are pretty much out, unfortunately. – Taeo Sep 30 '21 at 18:45
  • These "query shenanigans" you are talking of are part of the backbone of many, maybe also most object orientated approaches. CFC is simply not made for oop, from my point of view. You can use parts of oo concepts, but you will never get the full benefit. Use structured text if you really want to have all advantages TwinCAT offers in this scope. It's already limited enough ;-) (e.g. multiple inheritance of function block) – Gauss3k Oct 01 '21 at 19:14
1

I'm going to add an answer to my own question here, but not accept it for a bit in case someone has a secret left to divulge, haha.

Essentially, as far as I can tell, the pattern of passing an Array of Derived Type to an Array of BasicType is just simply unsupported. Either through a VAR_IN_OUT or other input type. You can't even just assign one to the other. Best I can tell Codesys doesn't respect inheritence when comparing arrays to each other, but WILL if you are comparing a particular index of the array to an object. I've come up with two work arounds that I don't like, but should work.

If you're passing interfaces, you can build the array in the header of the calling function block like this:

VAR
aFB : ARRAY[0..2] of ExtendedFB;//implements IExtended, which extends IBasic
aInterface : ARRAY[0..2] OF IBasic := [aFB[0], aFB[1],aFB[2]];
END_VAR

When it comes to extended fb's instead of interfaces, this won't quite work. In that scenario I had to use pointers, like so:

VAR
aFB: ARRAY[0..2] of ExtendedFB;//extends AbstractFB
apFB : Array[0..2] of Pointer to AbstractFB := [ADR(afb[0]), ADR(afb[1]), ADR(afb[2])];
END_VAR

TL;DR instead of passing Array of ExtendedType into an input expecting Array of BasicType or Reference to Array of BasicType, you can manually build an Array of BasicType one index at a time with ExtendedType objects and pass that instead, without the compiler complaining. Unfortunately, you're stuck with pointers if not using interfaces, since you can't create an array of function blocks without instantiating them.

Taeo
  • 55
  • 7