0

In the TwinCAT and CodeSys IEC-61131 programming environments, it's possible to declare POU VAR_INPUTs using an INTERFACE as a type specification. I believe the support for interfaces in TwinCAT and CoDeSys is an extension to the standard IEC-61131 language definition.

Question 1: When the POU is invoked, do interface VAR_INPUTs have pass-by-value (i.e. the input FB's state is copied on each execution of the called FB) or pass-by-reference semantics?

Question 2: Where is this behaviour specified or documented?

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41

2 Answers2

2

The interface type itself is a value, but it doesn't carry the function block it refers to. It's implemented as a pointer-to-instance's-vtable-pointer. It's used as-if it was a reference to a function block that implemented the interface, but the address returned is NOT that of the function block (that's the critical difference). That's because of the implementation:

                                    FB Instance
                                         |
interface (PVOID) ------+        * PVOID vtable 1       +----> VTABLE 3
                        +------> * PVOID vtable 2  -----+        |
                                 *     ...                   * method 1
                                 * PVOID vtable n            *   ...
                                 * data fields               * method m

So, if you read the contents of an interface, you'll get an address somewhere within the function block instance, and that address is the address of the vtable pointer within the instance. The particular vtable is the one that implements the methods of the interface (i.e. is compatible with the interface).

We can check that this is so for some type FB_MyFB:

INTERFACE I_Derived EXTENDS __SYSTEM.QueryInterface
END_INTERFACE

FUNCTION_BLOCK FB_MyFB IMPLEMENTS I_Derived
...
END_FUNCTION_BLOCK

FUNCTION F_CheckInterfaceRange(fb : REFERENCE TO FB_MyFB) : BOOL
VAR
  ifc : I_Derived := fb;
  ifcval : POINTER TO PVOID := ADR(ifc);
END_VAR
ifcval := ifcval^;
F_CheckInterfaceRange := 
  ifcval >= ADR(fb) 
  AND_THEN ifcval <= (ADR(fb) + SIZEOF(FB_MyFB) - SIZEOF(PVOID)); 
END_FUNCTION

It is seemingly impossible to get the address of the instance directly. Most likely it's an arbitrary limitation: all the vtable pointers must be valid and probably belong in a certain memory area, so you could imagine starting at whatever interface points to and walking backwards from it until you stop getting valid pointers. Those are the bounds. The instance starts with a vtable pointer, so one of those pointers you found will be it. Then examine how do the pointers look in instances of various library FB types, and then look at how the pointed-to vtables look, and I'm sure some valid heuristic would pop up that might not even be as expensive as a __QUERYINTERFACE call. CoDeSys 3 code generator is abysmal.

The supported way, instead, is for FB to implement an interface extending SYSTEM.__QueryInterface. Then, __QUERYPOINTER is used to access that interface to get the value of THIS of the FB.

You could imagine that __QUERYPOINTER looks a bit like:

FUNCTION __QUERYPOINTER
VAR_INPUT
  ifc : __SYSTEM.QueryInterface;
  ptr : REFERENCE TO PVOID;
END_VAR
ptr := ifc.__QUERYTHIS();
END_FUNCTION

The __SYSTEM.QueryInterface interface implements a method that casts between interfaces implemented by a FB, as long as both interfaces derive from __SYSTEM.QueryInterface, as well as a method (imagine it's called __QUERYTHIS) that returns THIS.

The method is generated by the compiler.

Imagine that the rest of the implementation is a bit like:

INTERFACE __SYSTEM.QueryInterface
PROPERTY _This_ : POINTER TO BYTE
METHOD _This__GET : POINTER TO BYTE   // that's how CoDeSys 3 implements getters/setters
END_PROPERTY
...
END_INTERFACE

FUNCTION BLOCK FB_Queryable IMPLEMENTS I_Queryable
PROPERTY _This_ : POINTER TO BYTE
METHOD _This__GET : POINTER TO BYTE
_This_GET := THIS;
END_METHOD
END_FUNCTION_BLOCK

You could similarly implement F_QueryInterface (this won't be as easy because __QUERYINTERFACE gets help from the compiler):

FUNCTION F_QueryInterface2 : BOOL
VAR_INPUT
  from : I_Queryable;
  to : REFERENCE TO I_Interface2;
END_VAR
IF from <> 0 THEN
  // the compiler would translate __QUERYINTERFACE(from, to) to something like:
  F_QueryInterface2 := from._QueryInterface_(2, ADR(to));
END_IF
END_FUNCTION

INTERFACE I_Queryable   // cont'd
...
METHOD _QueryInterface_ : BOOL
VAR_INPUT
  typeid : INT;
  to : POINTER TO PVOID; // pointer to interface
END_VAR
END_INTERFACE

INTERFACE I_Interface2 EXTENDS I_Queryable
...
END_INTERFACE

FUNCTION_BLOCK FB_MoreQueryable IMPLEMENTS I_Interface1, I_Interface2
METHOD _QueryInterface_ : BOOL
VAR_INPUT
  typeid : INT;
  to : POINTER TO U_Interfaces; // pointer to interface
END_VAR
to^.PVOID := 0;
CASE typeId OF
  1: to^.Interface1 := THIS^;
  2: to^.Interface2 := THIS^;
END_CASE
_QueryInterface_ := to^.PVOID <> 0;
END_FUNCTION_BLOCK

TYPE U_Interfaces :
UNION
   PVOID : PVOID;
   Interface1 : I_Interface1;
   Interface2 : I_Interface2;
END_UNION
END_TYPE
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
1

Interface variables are always treated as references in CoDeSys and TwinCAT. This should include VAR_INPUT variables.

TwinCAT reference: screen capture of linked documentation excerpt

CoDeSys reference: screen capture of excerpt from linked documentation

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41