如果一个VAR_INPUT是INTERFACE类型,这个值是引用传递还是值传递?
If a VAR_INPUT is of INTERFACE type, is the value pass-by-reference or pass-by-value?
在 TwinCAT 和 CodeSys IEC-61131 编程环境中,可以使用 INTERFACE
作为类型规范来声明 POU VAR_INPUT
s。我相信 TwinCAT 和 CoDeSys 中对接口的支持是对标准 IEC-61131 语言定义的扩展。
问题 1:调用 POU 时,接口 VAR_INPUT
是否具有按值传递(即在每次执行调用的 FB 时复制输入 FB 的状态)或按引用传递语义?
问题 2:在哪里指定或记录了此行为?
Interface
变量在 CoDeSys 和 TwinCAT 中始终被视为引用。这应该包括 VAR_INPUT
个变量。
接口类型本身是一个值,但不携带它所引用的功能块。它被实现为一个指向实例的虚表指针的指针。它被用作对实现接口的功能块的引用,但返回的地址是 NOT 功能块的地址(这是关键的区别)。那是因为实施:
FB Instance
|
interface (PVOID) ------+ * PVOID vtable 1 +----> VTABLE 3
+------> * PVOID vtable 2 -----+ |
* ... * method 1
* PVOID vtable n * ...
* data fields * method m
因此,如果您读取接口的内容,您将在功能块实例中的某处获得一个地址,该地址是实例中 vtable 指针的地址。特定的 vtable 是实现接口方法的 vtable(即与接口兼容)。
我们可以检查某些类型是否如此 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
直接获取实例的地址貌似是不可能的。这很可能是一个任意限制:所有 vtable 指针必须有效并且可能属于某个内存区域,因此您可以想象从任何接口指向开始并从它向后走,直到您停止获得有效指针。那些是界限。该实例以 vtable 指针开头,因此您找到的那些指针之一就是它。然后检查指针在各种库 FB 类型的实例中的外观,然后查看指向的 vtable 的外观,我确信会弹出一些有效的启发式方法,甚至可能不如 __QUERYINTERFACE
打电话。 CoDeSys 3 代码生成器很糟糕。
支持的方式是让 FB 实现扩展 SYSTEM.__QueryInterface
的接口。然后用__QUERYPOINTER
访问那个接口,得到FB的THIS
的值。
你可以想象 __QUERYPOINTER
看起来有点像:
FUNCTION __QUERYPOINTER
VAR_INPUT
ifc : __SYSTEM.QueryInterface;
ptr : REFERENCE TO PVOID;
END_VAR
ptr := ifc.__QUERYTHIS();
END_FUNCTION
__SYSTEM.QueryInterface
接口实现了一个在FB实现的接口之间进行转换的方法,只要两个接口都派生自__SYSTEM.QueryInterface
,还有一个方法(假设它叫做__QUERYTHIS
) 即 returns THIS
.
该方法由编译器生成。
想象一下,其余的实现有点像:
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
您可以类似地实现 F_QueryInterface
(这不会那么容易,因为 __QUERYINTERFACE
需要编译器的帮助):
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
在 TwinCAT 和 CodeSys IEC-61131 编程环境中,可以使用 INTERFACE
作为类型规范来声明 POU VAR_INPUT
s。我相信 TwinCAT 和 CoDeSys 中对接口的支持是对标准 IEC-61131 语言定义的扩展。
问题 1:调用 POU 时,接口 VAR_INPUT
是否具有按值传递(即在每次执行调用的 FB 时复制输入 FB 的状态)或按引用传递语义?
问题 2:在哪里指定或记录了此行为?
Interface
变量在 CoDeSys 和 TwinCAT 中始终被视为引用。这应该包括 VAR_INPUT
个变量。
接口类型本身是一个值,但不携带它所引用的功能块。它被实现为一个指向实例的虚表指针的指针。它被用作对实现接口的功能块的引用,但返回的地址是 NOT 功能块的地址(这是关键的区别)。那是因为实施:
FB Instance
|
interface (PVOID) ------+ * PVOID vtable 1 +----> VTABLE 3
+------> * PVOID vtable 2 -----+ |
* ... * method 1
* PVOID vtable n * ...
* data fields * method m
因此,如果您读取接口的内容,您将在功能块实例中的某处获得一个地址,该地址是实例中 vtable 指针的地址。特定的 vtable 是实现接口方法的 vtable(即与接口兼容)。
我们可以检查某些类型是否如此 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
直接获取实例的地址貌似是不可能的。这很可能是一个任意限制:所有 vtable 指针必须有效并且可能属于某个内存区域,因此您可以想象从任何接口指向开始并从它向后走,直到您停止获得有效指针。那些是界限。该实例以 vtable 指针开头,因此您找到的那些指针之一就是它。然后检查指针在各种库 FB 类型的实例中的外观,然后查看指向的 vtable 的外观,我确信会弹出一些有效的启发式方法,甚至可能不如 __QUERYINTERFACE
打电话。 CoDeSys 3 代码生成器很糟糕。
支持的方式是让 FB 实现扩展 SYSTEM.__QueryInterface
的接口。然后用__QUERYPOINTER
访问那个接口,得到FB的THIS
的值。
你可以想象 __QUERYPOINTER
看起来有点像:
FUNCTION __QUERYPOINTER
VAR_INPUT
ifc : __SYSTEM.QueryInterface;
ptr : REFERENCE TO PVOID;
END_VAR
ptr := ifc.__QUERYTHIS();
END_FUNCTION
__SYSTEM.QueryInterface
接口实现了一个在FB实现的接口之间进行转换的方法,只要两个接口都派生自__SYSTEM.QueryInterface
,还有一个方法(假设它叫做__QUERYTHIS
) 即 returns THIS
.
该方法由编译器生成。
想象一下,其余的实现有点像:
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
您可以类似地实现 F_QueryInterface
(这不会那么容易,因为 __QUERYINTERFACE
需要编译器的帮助):
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