Тип интерфейса сам по себе является значением, но он не содержит функционального блока, на который ссылается. Он реализован как указатель на 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