【问题标题】:If a VAR_INPUT is of INTERFACE type, is the value pass-by-reference or pass-by-value?如果 VAR_INPUT 是 INTERFACE 类型,那么值是按引用传递还是按值传递?
【发布时间】:2019-05-20 01:27:58
【问题描述】:

在 TwinCAT 和 CodeSys IEC-61131 编程环境中,可以使用 INTERFACE 作为类型规范来声明 POU VAR_INPUTs。我相信 TwinCAT 和 CoDeSys 中对接口的支持是对标准 IEC-61131 语言定义的扩展。

问题 1:当 POU 被调用时,接口VAR_INPUTs 是否具有 pass-by-value(即输入 FB 的状态在每次执行被调用的 FB 时复制)或 pass-by-reference 语义?

问题 2:在哪里指定或记录了这种行为?

【问题讨论】:

    标签: twincat codesys iec61131-3


    【解决方案1】:

    接口类型本身是一个值,但它不携带它所引用的功能块。它被实现为指向实例的 vtable 指针。它被用作对实现接口的功能块的引用,但返回的地址是 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,以及一个返回 THIS 的方法(假设它被称为 __QUERYTHIS)。

    方法由编译器生成。

    想象一下其余的实现有点像:

    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
    

    【讨论】:

      【解决方案2】:

      Interface 变量在 CoDeSys 和 TwinCAT 中始终被视为引用。这应该包括VAR_INPUT 变量。

      TwinCAT reference:

      CoDeSys reference:

      【讨论】:

        猜你喜欢
        • 2019-04-04
        • 2016-11-28
        • 2017-11-14
        相关资源
        最近更新 更多