Many common programming languages have constructors, and some also have destructors. TwinCAT 3 and CODESYS 3 have the methods that map something like constructors and destructors. These are not real constructors/destructors, but this detail is not important for now. This blog post covers the basics and pitfalls.
There are always 3 methods for each function block:
-
FB_init
: This is the constructor. As you can see from the name, this is not a real constructor. It is just a method to initialize an instance of a function block -
FB_exit
: This is the destructor. As you can see from the name, this is not a real destructor. -
FB_reinit
: This method is a PLC peculiarity, that is only used for online change.
Furthermore, this blog article discusses a few pragmas that can and must be used to influence the behavior when creating, destroying and reinitializing.
Creating
To create one of these methods, use only the drop-down menu.
Add method from the context menu: Right click on the class and select Add → Method from the context menu.
Add method from the drop-down menu: Use only the drop-down menu to create these special methods.
FB_init
The FB_init
method always exists and is always called, even if it is not explicitly created.
The FB_init
method should never be called explicitly! This can lead to undesirable effects since the parent classes and in part also variables are reinitialized.
The FB_init
method of a base class on inheritance is called implicitly a SUPER^.FB_init
must therefore not be used.
FB_init
method is used to initialize instances in the object. It comes closest to what a constructor is, it is always present and is also always called.
If the method is not explicitly created, then the brackets are omitted in the declaration. If the FB_init
method is created, then the brackets must be set during the declaration, even if the method is without parameters.
Example: For the class Foo no FB_init
method was created. For the class Bar a method without parameters was created. The declaration looks as follows:
foo:Foo;
bar :Bar();
The method already has two input parameters: bInitRetains
and bInCopyCode
. These parameters cannot be set because they are set implicitly.
They can be read and eventually used to distinguish whether it is an online change or a download. In short this means that if ((bInitRetains = TRUE) AND (bInCopyCode = FALSE))
then it is a download, if ((bInitRetains = FALSE) AND (bInCopyCode = TRUE))
then it is an online change. However, this is only half the truth. Because if above the declaration of an object the pragma {attribute 'call_after_online_change_slot' := '<slotNumber>'}
is placed, then it looks again as if it is a download, although a copy code takes place.
FB_exit
The FB_exit
method should never be called explicitly!
The FB_exit
method should not be extended by parameters, because these cannot be served with the implicit call.
The FB_exit
method of a base class on inheritance is called implicitly. A SUPER^.FB_exit
must therefore not be used.
The FB_exit
method is called, if available, when an object is to be destroyed, e.g. when the controller changes to stop mode, or an object is regenerated after an online change. The method already has one input parameter: bInCopyCode
. This parameter cannot be set because it is set implicitly. bInCopyCode
can be read if the parameter is True. Then the object is destroyed during an online change. The call of the FB_exit
method can also be suppressed for individual objects with the pragma {attribute 'no-exit'}
.
Example: FE_exit suppression
VAR
foo :Foo();
{attribute 'no-exit'}
zombieFoo :Foo();
END_VAR
For the object foo
, foo.FB_exit
is called and for the object zombieFoo
, zombieFoo.FB_exit
is not called.
FB_reinit
The FB_reinit
method should never be called explicitly!
The FB_reinit
method should not be extended by parameters, because these cannot be served with the implicit call.
The FB_reinit
method of a base class on inheritance is not called implicitly. A SUPER^.FB_reinit
must therefore be used, when it is needed.
The FB_reinit
method is a strange method for which there is no equivalent in other languages. The FB_reinit
method is only called in case of an online change and that as the last one. FB_reinit
also behaves differently with inheritance than FB_exit
and FB_init
, because FB_reinit
of the base class is not called implicitly. I have not thought of a use case for the FB_reinit
method yet, because I cannot use parameters. It can be that the FB_reinit
method is used to keep references to global data up to date, but I think this is a bad style.
Pragma {attribute ’no_copy‘}
The pragma {attribute 'no_copy'}
is an important pragma. It prevents a variable from getting the actual values from the object after an online change. For pointers, references or interfaces it is harmful, if the values are taken from the old object. Therefore, all references, pointers and interfaces which are initialized over FB_init
from outside should be provided with the pragma {attribute 'no_copy'}
.
Example:
FUNCTION_BLOCK FooBar
VAR
{attribute 'no_copy'}
foo :IFoo;
END_VAR
METHOD FB_init :BOOL
VAR_INPUT
(*if TRUE, the retain variables are initialized (warm start / cold start)*)
bInitRetains :BOOL;
(*if TRUE, the instance afterwards gets moved into the copy code (online change)*)
bInCopyCode :BOOL;
foo :IFoo;
END_VAR
THIS^.foo := foo;
END_METHOD
foo
is reinitialized during online change and the value is not overwritten again after initialization.
Pragma {attribute ‚call_after_online_change_slot‘ := ‚<slotNumber>‘}
With the pragma {attribute 'call_after_online_change_slot' := '<slotNumber>'}
a sequence can be defined in which the FB_init
methods of the objects are called after an online change.
If an object is provided with the pragma, it is rebuilt for each online change. The sequence is defined by the slot number. The slotNumber
must be an integer. The lower the slotNumber
the earlier the rebuild takes place. This pragma is important for references, pointers and interfaces. At the same time, it becomes confusing quickly.
Example:
VAR
{attribute 'call_after_online_change_slot' := '1'}
foo :Foo(fooNess := 12);
{attribute 'call_after_online_change_slot' := '2'}
fooBar :FooBar(foo := foo);
END_VAR
In the example, the object foo
is rebuilt first and then the object fooBar
is rebuilt, so the reference on foo
always remains valid if foo
uses the pragma {attribute 'no_copy'}
in fooBar
.
Pragma {attribute ‚call_after_init‘}
The pragma {attribute 'call_after_init'}
is used to define a method that is called after the FB_init
method is called.
This method must not have any parameters.
Classes that have a method with {attribute 'call_after_init'}
must also have the pragma above the class header.
In case of inheritance, if the base class uses the {attribute 'call_after_init'}
pragma, the specialized class must also have the {attribute 'call_after_init'}
pragma.
In case of inheritance, if the base class uses the {attribute 'call_after_init'}
pragma, then the method can be overridden with the {attribute 'call_after_init'}
pragma. The method of the base class must then be called via the SUPER
pointer if necessary
Example:
{attribute 'call_after_init'}
FUNCTION_BLOCK Baz
VAR
baz :INT;
END_VAR
METHOD FB_init
VAR_INPUT
(*if TRUE, the retain variables are initialized (warm start / cold start)*)
bInitRetains :BOOL;
(*if TRUE, the instance afterwards gets moved into the copy code (online change)*)
bInCopyCode :BOOL;
baz :INT;
END_VAR
THIS^.baz := baz;
END_METHOD
{attribute 'call_after_init'}
METHOD doubleBaz
THIS^.baz := (THIS^.baz * 2);
END_METHOD
In this example, you can see that both the class and the method need the {attribute 'call_after_init'} pragma.
{attribute 'call_after_init'}
FUNCTION_BLOCK ExtendedBaz EXTENDS Baz
VAR
isBazValid :BOOL;
END_VAR
METHOD FB_init
VAR_INPUT
(*if TRUE, the retain variables are initialized (warm start / cold start)*)
bInitRetains :BOOL;
(* if TRUE, the instance afterwards gets moved into the copy code (online change)*)
bInCopyCode :BOOL;
baz :INT;
END_VAR
THIS^.isBazValid := FALSE;
END_METHOD
In the example above, you can see that the ExtendedBaz
class also needs the {attribute 'call_after_init'}
pragma, since the base class also uses it.
Pragma {attribute ‚call_on_type_change‘:= ‚<ClassName1>, <ClassNameX>‘}
The pragma does not work with the FB_init
method.
This method must not have any parameters.
The pragma {attribute 'call_on_type_change':= '<ClassName1>, ... <ClassNameX>'}
can be used to indicate a method, that should be called when one of the classes '<ClassName1>, ... <ClassNameX>'
changes.
Personally, I think this mechanism is bad, because the class needs to know exactly about the types used, and maintaining a pragma is almost as hard as maintaining comments. Since I cannot think of an example of what to do there, other than re-reference global data, I will not show an example here.
Pragma {attribute ’no-exit‘}
With the pragma, {attribute 'no-exit'}
you can suppress the call of FB_exit
for an object instance. An example can be found in the part on FB_exit
.
Orders
Here it will be shown very briefly what is executed when and in which order.
First download
- Instances are initialized via
FB_init
- Explicit assignments from outside at declaration
foo :Foo(foo := 3) := (fooness := 7, memberOfFookind := 2);
- Methods that are provided with the
{attribute 'call_after_init'}
Re-download
- Instances are cleaned up via
FB_exit
- Instances are initialized via
FB_init
- Explicit assignments from outside at declaration
foo :Foo(foo := 3) := (fooness := 7, memberOfFookind := 2);
- Methods that are provided with the
{attribute 'call_after_init'}
Online change
- Instances are cleaned up via
FB_exit
- New instances are initialized via
FB_init
- Explicit assignments from outside at declaration
foo :Foo(foo := 3) := (fooness := 7, memberOfFookind := 2);
- Methods that are provided with the
{attribute 'call_after_init'}
FB_reinit
method is executed
Conclusion
The online change is difficult to implement when working with references, dynamic allocation, interfaces, etc. It is definitely possible, but it requires a lot of discipline. Often it is easier to work only with downloads.