Если VAR_INPUT имеет тип INTERFACE, передается ли значение по ссылке или по значению?

В средах программирования TwinCAT и CodeSys IEC-61131 можно объявить POU VAR_INPUT, используя INTERFACE в качестве спецификации типа. Я считаю, что поддержка интерфейсов в TwinCAT и CoDeSys является расширением стандартного определения языка IEC-61131.

Вопрос 1: При вызове POU интерфейс VAR_INPUT имеет семантику передачи по значению (т. е. состояние входного FB копируется при каждом выполнении вызываемого FB) или семантику передачи по ссылке?

Вопрос 2: Где это поведение указано или задокументировано?


person Hydrargyrum    schedule 20.05.2019    source источник


Ответы (2)


Тип интерфейса сам по себе является значением, но он не содержит функционального блока, на который ссылается. Он реализован как указатель на vtable-указатель экземпляра. Он используется так, как если бы это была ссылка на функциональный блок, реализующий интерфейс, но возвращаемый адрес НЕ адрес функционального блока (в этом принципиальное отличие). Это из-за реализации:

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

Таким образом, если вы прочитаете содержимое интерфейса, вы получите адрес где-то внутри экземпляра функционального блока, и этот адрес является адресом указателя виртуальной таблицы внутри экземпляра. Конкретная виртуальная таблица реализует методы интерфейса (т.е. совместима с интерфейсом).

Мы можем проверить, что это так для некоторого типа 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, а затем посмотрите, как выглядят виртуальные таблицы, на которые указывают, и я уверен, что появится какая-то действительная эвристика, которая может быть даже не такой дорогой, как вызов __QUERYINTERFACE. Генератор кода CoDeSys 3 ужасен.

Вместо этого поддерживаемый способ заключается в том, чтобы FB реализовывал интерфейс, расширяющий SYSTEM.__QueryInterface. Затем __QUERYPOINTER используется для доступа к этому интерфейсу, чтобы получить значение THIS FB.

Вы можете себе представить, что __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), возвращающий 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
person Kuba hasn't forgotten Monica    schedule 23.10.2019

Переменные Interface всегда рассматриваются как ссылки в CoDeSys и TwinCAT. Это должно включать VAR_INPUT переменных.

справочник по TwinCAT: снимок экрана фрагмента связанной документации

справочник по CoDeSys: снимок экрана отрывка из  связанная документация

person Hydrargyrum    schedule 20.05.2019