In the TwinCAT and CodeSys IEC-61131 programming environments, it's possible to declare POU VAR_INPUT
s 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_INPUT
s 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?
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