在结构和功能块中使用泛型
Using generic type in Struct & Function Block
我想创建一个泛型 STRUCT
和一个成对的 Function Block
接受和 return 泛型变量(假定 ANY_NUM
)。
需要使用通用数字类型 probably belonging to the ANY_NUM
type 将许多现有的 STRUCT
和 FB
对压缩成一个通用对。
在 C++ 中,通用结构将通过 Template Class
完成,但我在结构化文本中找不到类似的结构。
我尝试了通用功能块 on Beckhoff's ANY/ANY_(TYPE) page,但它很快就失败了 convert type 'LREAL' to type '__SYSTEM.AnyType'
。
问题:
在结构化文本中我可以在多大程度上实现这个目标?
编辑:
我错误地认为 ANY
是唯一相关的 ST 泛型。
我被引导到 type T_Arg 作为一个潜在的可行候选人。
尝试格式示例:
结构:
TYPE Bounded_Value:
STRUCT
Value : ANY_NUM;
Min_ : ANY_NUM;
Max_ : ANY_NUM;
END_STRUCT
END_TYPE
功能块:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
END_VAR
(实施会将 value_struct.Value
绑定到 value_struct.min_
和 value_struct.max_
之间)
我最近在 TwinCAT 中研究了这个(任何类型)。您基本上需要做的是将 ANY 指针指向的每个字节转换为 LREAL(您知道根据 IEC61131-3 始终为 8 字节)。 ANY 类型包含有关它指向什么类型的信息,因此您可以通过分析 ANY 指针指向的数据结构来了解它何时是 LREAL。请阅读我博客上的完整调查:The wonders of ANY
(我从 Stefan Henneken's blog post on T_Arg 中找到了我的问题的解决方案。)
目标可以完成,但不是特别干净。有两种适用的通用类型(到目前为止我已经找到):ANY_NUM
和 T_Arg
.
(我使用 ANY_NUM
因为它与这个例子最相关。ANY
、ANY_REAL
或 ANY_INT
也是合理的选择)
这两个选项具有相似的结构和相似的功能。每个都是一个包含有关存储变量的信息的结构:它的类型、指向它的指针和它的大小。
但各有利弊。为了最准确地解决这个问题,我们将使用 T_Arg
.
区别如下:
任意/ANY_NUM/等
优点:变量到ANY_NUM
的转换在implicitly变量赋值时完成。输入变量在输入函数之前不需要预先转换,减少了代码量。
此外,它只接受属于它的域的变量,因此不会意外使用字符串。
缺点: ANY_NUM
不能在 VAR_INPUT
块之外声明,事实上,在尝试时会提供此错误消息:
Variables of type 'ANY_NUM' only allowed as input of functions.
因此 ANY_NUM
不能用作 STRUCT
变量,即使 STRUCT
被声明为函数的输入。这就是为什么它不能用来解决这个特定问题的原因。
T_Arg
优点: T_Arg
可以在任何地方声明和使用
缺点: T_Arg
需要任何预期变量类型的转换函数,例如:
F_INT()
、F_REAL()
、F_DINT()
等
因此需要在输入前后进行类型检查。
示例解决方案
不幸的是,无法直接操作存储在T_Arg
中的变量。需要将存储的变量移动到临时变量中才能使用。因此 Value
、Min_
和 Max_
都需要从类型 T_Arg
转换为类型 REAL
/INT
/等
因为我们试图只使用一个 STRUCT
,Value
需要在 Bind_Value
完成操作后再次转换为 T_Arg。
总共 Value
在实例化时会转换 3 次,之后每次调用会转换 2 次。
结构:
TYPE Bounded_Value:
STRUCT
Value : T_Arg;
Min_ : T_Arg;
Max_ : T_Arg;
END_STRUCT
END_TYPE
功能块:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
// Other variable type declarations
END_VAR
VAR
val_int : INT;
max_int : INT;
min_int : INT;
END_VAR
CASE (value_struct.Value.eType) OF
E_ArgType.ARGTYPE_INT: // If the struct's Value's type is INT
// Copy generic pointer information into typed pointer
MEMCPY(ADR(val_int), value_struct.Value.pData, value_struct.Value.cbLen);
MEMCPY(ADR(max_int), value_struct.Max_.pData, value_struct.Max_.cbLen);
MEMCPY(ADR(min_int), value_struct.Min_.pData, value_struct.Min_.cbLen);
IF val_int > max_int THEN
value_struct.Value.pData := value_struct.Max_.pData;
ELSIF val_int < min_int THEN
value_struct.Value.pData := value_struct.Min_.pData;
END_IF
// Other variable type handlings
END_CASE
主线:
PROGRAM MAIN
VAR
val : INT := -1; //Change this to test
minim : INT := 0;
maxim : INT := 5;
newVal : INT;
bv : Bounded_Value;
bind : Bind_Value;
END_VAR
// Convert INT variables to T_Arg in structure
bv.Value:= F_INT(val);
bv.Max_ := F_INT(maxim);
bv.Min_ := F_INT(minim);
// Bind_Value.value_struct := bv;
bind(value_struct := bv);
// Copy result to newVal
MEMCPY(ADR(newVal), bv.Value.pData, bv.Value.cbLen);
您还可以在功能块和联合的帮助下创建通用类型。
假设您使用所有 DUT 和 POU 定义联合:
TYPE GenericType :
UNION
generic : PVOID;
bBool : REFERENCE TO BOOL;
nInt : REFERENCE TO INT;
nUint : REFERENCE TO UINT;
nUdint : REFERENCE TO UDINT;
fReal : REFERENCE TO REAL;
fLreal : REFERENCE TO LREAL;
fbTest : REFERENCE TO FB_Test;
END_UNION
END_TYPE
然后你创建一个特殊的功能块:
FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
uGenericType : GenericType;
END_VAR
还有一个获取和设置 PVOID 的 属性:
PROPERTY PUBLIC generic : PVOID
getter:
generic := uGenericType.generic;
setter:
uGenericType.generic := generic;
您还需要属性以便稍后检索您的类型:
Image of the FB
getBool 的示例 setter 可以是:
IF uGenericType.generic = 0
THEN
RETURN;
ELSE
getBool := uGenericType.bBool;
END_IF
现在创建将使用通用类型的 FB:
FUNCTION_BLOCK FB_Container
VAR_INPUT
myGenericType : Generic;
nContainerOption : INT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
testInt : INT;
testBool : BOOL;
testFB : FB_Test;
END_VAR
CASE nContainerOption OF
1:
testInt := myGenericType.getInt;
2:
testFB := myGenericType.getFbTest;
3:
testBool := myGenericType.getBool;
END_CASE
调用可能是这样的:
fbContainer.myGenericType.generic := ADR(testInteger);
...
fbContainer(nContainerOption := 1);
另一种方法是使用通用 FB 扩展我们的 FB。
我们需要对 Generic FB 和 GenericType Union 做一些修改:
FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
uGenericType : GenericType;
bInit : BOOL;
END_VAR
IF NOT bInit THEN
uGenericType.generic := ADR(THIS^);
bInit := TRUE;
END_IF
TYPE GenericType :
UNION
generic : PVOID;
//Add the pointer of the FB you want to extend
pAxis : POINTER TO FB_Axis;
bBool : REFERENCE TO BOOL;
nInt : REFERENCE TO INT;
nUint : REFERENCE TO UINT;
nUdint : REFERENCE TO UDINT;
fReal : REFERENCE TO REAL;
fLreal : REFERENCE TO LREAL;
fbTest : REFERENCE TO FB_Test;
END_UNION
END_TYPE
扩展FB:
FUNCTION_BLOCK FB_Axis EXTENDS Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
fPosition : LREAL;
END_VAR
现在像以前一样调用我们的容器:
fbContainer.myGenericType := fbAxis;
在 FB_Container 中,您可以按如下方式调用轴:
IF myGenericType.getPointerFbAxis <> 0
THEN
position := myGenericType.getPointerFbAxis^.fPosition;
END_IF
我想创建一个泛型 STRUCT
和一个成对的 Function Block
接受和 return 泛型变量(假定 ANY_NUM
)。
需要使用通用数字类型 probably belonging to the ANY_NUM
type 将许多现有的 STRUCT
和 FB
对压缩成一个通用对。
在 C++ 中,通用结构将通过 Template Class
完成,但我在结构化文本中找不到类似的结构。
我尝试了通用功能块 on Beckhoff's ANY/ANY_(TYPE) page,但它很快就失败了 convert type 'LREAL' to type '__SYSTEM.AnyType'
。
问题:
在结构化文本中我可以在多大程度上实现这个目标?
编辑:
我错误地认为 ANY
是唯一相关的 ST 泛型。
我被引导到 type T_Arg 作为一个潜在的可行候选人。
尝试格式示例:
结构:
TYPE Bounded_Value:
STRUCT
Value : ANY_NUM;
Min_ : ANY_NUM;
Max_ : ANY_NUM;
END_STRUCT
END_TYPE
功能块:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
END_VAR
(实施会将 value_struct.Value
绑定到 value_struct.min_
和 value_struct.max_
之间)
我最近在 TwinCAT 中研究了这个(任何类型)。您基本上需要做的是将 ANY 指针指向的每个字节转换为 LREAL(您知道根据 IEC61131-3 始终为 8 字节)。 ANY 类型包含有关它指向什么类型的信息,因此您可以通过分析 ANY 指针指向的数据结构来了解它何时是 LREAL。请阅读我博客上的完整调查:The wonders of ANY
(我从 Stefan Henneken's blog post on T_Arg 中找到了我的问题的解决方案。)
目标可以完成,但不是特别干净。有两种适用的通用类型(到目前为止我已经找到):ANY_NUM
和 T_Arg
.
(我使用 ANY_NUM
因为它与这个例子最相关。ANY
、ANY_REAL
或 ANY_INT
也是合理的选择)
这两个选项具有相似的结构和相似的功能。每个都是一个包含有关存储变量的信息的结构:它的类型、指向它的指针和它的大小。
但各有利弊。为了最准确地解决这个问题,我们将使用 T_Arg
.
区别如下:
任意/ANY_NUM/等
优点:变量到ANY_NUM
的转换在implicitly变量赋值时完成。输入变量在输入函数之前不需要预先转换,减少了代码量。
此外,它只接受属于它的域的变量,因此不会意外使用字符串。
缺点: ANY_NUM
不能在 VAR_INPUT
块之外声明,事实上,在尝试时会提供此错误消息:
Variables of type 'ANY_NUM' only allowed as input of functions.
因此 ANY_NUM
不能用作 STRUCT
变量,即使 STRUCT
被声明为函数的输入。这就是为什么它不能用来解决这个特定问题的原因。
T_Arg
优点: T_Arg
可以在任何地方声明和使用
缺点: T_Arg
需要任何预期变量类型的转换函数,例如:
F_INT()
、F_REAL()
、F_DINT()
等
因此需要在输入前后进行类型检查。
示例解决方案
不幸的是,无法直接操作存储在T_Arg
中的变量。需要将存储的变量移动到临时变量中才能使用。因此 Value
、Min_
和 Max_
都需要从类型 T_Arg
转换为类型 REAL
/INT
/等
因为我们试图只使用一个 STRUCT
,Value
需要在 Bind_Value
完成操作后再次转换为 T_Arg。
总共 Value
在实例化时会转换 3 次,之后每次调用会转换 2 次。
结构:
TYPE Bounded_Value:
STRUCT
Value : T_Arg;
Min_ : T_Arg;
Max_ : T_Arg;
END_STRUCT
END_TYPE
功能块:
FUNCTION_BLOCK Bind_Value
VAR_IN_OUT
value_struct: Bounded_Value;
// Other variable type declarations
END_VAR
VAR
val_int : INT;
max_int : INT;
min_int : INT;
END_VAR
CASE (value_struct.Value.eType) OF
E_ArgType.ARGTYPE_INT: // If the struct's Value's type is INT
// Copy generic pointer information into typed pointer
MEMCPY(ADR(val_int), value_struct.Value.pData, value_struct.Value.cbLen);
MEMCPY(ADR(max_int), value_struct.Max_.pData, value_struct.Max_.cbLen);
MEMCPY(ADR(min_int), value_struct.Min_.pData, value_struct.Min_.cbLen);
IF val_int > max_int THEN
value_struct.Value.pData := value_struct.Max_.pData;
ELSIF val_int < min_int THEN
value_struct.Value.pData := value_struct.Min_.pData;
END_IF
// Other variable type handlings
END_CASE
主线:
PROGRAM MAIN
VAR
val : INT := -1; //Change this to test
minim : INT := 0;
maxim : INT := 5;
newVal : INT;
bv : Bounded_Value;
bind : Bind_Value;
END_VAR
// Convert INT variables to T_Arg in structure
bv.Value:= F_INT(val);
bv.Max_ := F_INT(maxim);
bv.Min_ := F_INT(minim);
// Bind_Value.value_struct := bv;
bind(value_struct := bv);
// Copy result to newVal
MEMCPY(ADR(newVal), bv.Value.pData, bv.Value.cbLen);
您还可以在功能块和联合的帮助下创建通用类型。 假设您使用所有 DUT 和 POU 定义联合:
TYPE GenericType :
UNION
generic : PVOID;
bBool : REFERENCE TO BOOL;
nInt : REFERENCE TO INT;
nUint : REFERENCE TO UINT;
nUdint : REFERENCE TO UDINT;
fReal : REFERENCE TO REAL;
fLreal : REFERENCE TO LREAL;
fbTest : REFERENCE TO FB_Test;
END_UNION
END_TYPE
然后你创建一个特殊的功能块:
FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
uGenericType : GenericType;
END_VAR
还有一个获取和设置 PVOID 的 属性:
PROPERTY PUBLIC generic : PVOID
getter:
generic := uGenericType.generic;
setter:
uGenericType.generic := generic;
您还需要属性以便稍后检索您的类型:
Image of the FB
getBool 的示例 setter 可以是:
IF uGenericType.generic = 0
THEN
RETURN;
ELSE
getBool := uGenericType.bBool;
END_IF
现在创建将使用通用类型的 FB:
FUNCTION_BLOCK FB_Container
VAR_INPUT
myGenericType : Generic;
nContainerOption : INT;
END_VAR
VAR_OUTPUT
END_VAR
VAR
testInt : INT;
testBool : BOOL;
testFB : FB_Test;
END_VAR
CASE nContainerOption OF
1:
testInt := myGenericType.getInt;
2:
testFB := myGenericType.getFbTest;
3:
testBool := myGenericType.getBool;
END_CASE
调用可能是这样的:
fbContainer.myGenericType.generic := ADR(testInteger);
...
fbContainer(nContainerOption := 1);
另一种方法是使用通用 FB 扩展我们的 FB。 我们需要对 Generic FB 和 GenericType Union 做一些修改:
FUNCTION_BLOCK Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
uGenericType : GenericType;
bInit : BOOL;
END_VAR
IF NOT bInit THEN
uGenericType.generic := ADR(THIS^);
bInit := TRUE;
END_IF
TYPE GenericType :
UNION
generic : PVOID;
//Add the pointer of the FB you want to extend
pAxis : POINTER TO FB_Axis;
bBool : REFERENCE TO BOOL;
nInt : REFERENCE TO INT;
nUint : REFERENCE TO UINT;
nUdint : REFERENCE TO UDINT;
fReal : REFERENCE TO REAL;
fLreal : REFERENCE TO LREAL;
fbTest : REFERENCE TO FB_Test;
END_UNION
END_TYPE
扩展FB:
FUNCTION_BLOCK FB_Axis EXTENDS Generic
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
fPosition : LREAL;
END_VAR
现在像以前一样调用我们的容器:
fbContainer.myGenericType := fbAxis;
在 FB_Container 中,您可以按如下方式调用轴:
IF myGenericType.getPointerFbAxis <> 0
THEN
position := myGenericType.getPointerFbAxis^.fPosition;
END_IF