Dynamic Generation of FB Instances¶
In CODESYS there are three possibilities of dynamically creating an FB instance:
- A.) By using the operators
__NEW
and__DELETE
and a MemoryPool which has to be parameterized for the respecting application via CODESYS. However this method has a significant disadvantage:
The MemoryPool is defined once for the application and all the referenced libraries within the application. But it is not possible to determine how much storage space each library is allowed to claim in the MemoryPool.
The coincidental utilization of
__NEW
and__DELETE
with different sized FB instances causes a fragmentation of the MemoryPool. Because of that it is not possible to securely install any system for a non-stop operation throughout the entire year. An occasional reboot of the facility would be necessary to re-establish the MemoryPool.That is why the MemoryPool and the operators
__New
and__Delete
can only be recommended for applications in very special cases. Especially when dealing with libraries this method should not be used!
- B.) By using the library
FBFactory
there is a second and more reliable possibility to meet the demands of dynamically creating an FB instance. With the help of the
FBFactory Library
a static pool (which can be extended into a dynamic pool if needed) for each factory is defined in advance. The management of the memory space is organized in way which makes sure that no fragmentation is possible. Therefore there is no obstacle to a non-stop operation of the system.This library dates back to a time when CODESYS did not yet provide enough options for users to create dynamic blocks in a simple way. Using this library requires a relatively large amount of additional code, memory and CPU time that is often not needed.
This library is no longer recommended for new developments.
The next section describes the current recommendation on how function blocks can be generated dynamically.
- C.) By creating a function using the
__VFINIT
operator. Such a function mimics the behaviour of the compiler when creating function blocks. It uses a predefined memory area of appropriate size. For this reason, it is important that the following memory requirements are met.
The address of the first byte of the instance memory should aligned to a
__XWORD
boundaryThe value of the memory size should be a multiple of
SIZEOF(__XWORD)
These requirements are met for memory provided by the Memory Block Manager functions. It is therefore recommended to use this library if dynamic generation of function blocks is required.
Example returning Instance Pointer
/// Returns a instance pointer to the function block /// placed in the memory area given by the parameters. /// /// axwMemory : ARRAY[0..(SIZEOF(_Packet) / SIZEOF(__XWORD))] OF __XWORD; /// pPacket : POINTER TO _Packet := CreatePacket(ADR(axwMemory), SIZEOF(axwMemory)); /// FUNCTION CreatePacket : POINTER TO _Packet VAR_INPUT /// | Pointer to the first byte of the memory. /// | This address should aligned to a ``__XWORD`` boundary! pbyMemory : POINTER TO BYTE; /// | Size of the allocated memory for the function block. /// | This value should be a multiple of ``SIZEOF(__XWORD)``! udiSize : UDINT; END_VAR VAR_OUTPUT eErrorID : ERROR; END_VAR VAR // Instance pointer pPacket : POINTER TO _Packet; END_VAR IF pbyMemory = 0 OR udiSize < SIZEOF(_Packet) THEN eErrorID := ERROR.WRONG_PARAMETER; RETURN; END_IF {implicit on} pPacket := pbyMemory; pPacket^.__VFINIT(); pPacket^.FB_INIT(TRUE, FALSE); {implicit off} CreatePacket := pPacket;
Example returning Interface Reference
Alternatively, such a function can return an interface reference. In this case, the instance pointer must be converted accordingly.
/// Returns an IPacket reference to the instance of the function block /// placed in the memory area specified by the parameters. /// /// axwMemory : ARRAY[0..(SIZEOF(_Packet) / SIZEOF(__XWORD))] OF __XWORD; /// itfPacket : IPacket := CreatePacket(ADR(axwMemory), SIZEOF(axwMemory)); /// FUNCTION CreatePacket : IPacket VAR_INPUT /// | Pointer to the first byte of the memory. /// | This address should aligned to a ``__XWORD`` boundary! pbyMemory : POINTER TO BYTE; /// | Size of the allocated memory for the function block. /// | This value should be a multiple of ``SIZEOF(__XWORD)``! udiSize : UDINT; END_VAR VAR_OUTPUT eErrorID : ERROR; END_VAR VAR // Instance pointer pPacket : POINTER TO _Packet; END_VAR IF pbyMemory = 0 OR udiSize < SIZEOF(_Packet) THEN eErrorID := ERROR.WRONG_PARAMETER; RETURN; END_IF {implicit on} pPacket := pbyMemory; pPacket^.__VFINIT(); pPacket^.FB_INIT(TRUE, FALSE); {implicit off} __QUERYINTERFACE(pPacket^, CreatePacket);
Example with initial values
It is also possible to pass additional (e.g. optional) parameters in order to provide the new instance with its starting values.
/// Returns an IPacket reference to the instance of the function block /// placed in the memory area specified by the parameters. /// /// axwMemory : ARRAY[0..(SIZEOF(_Packet) / SIZEOF(__XWORD))] OF __XWORD; /// itfPacket : IPacket := CreatePacket(ADR(axwMemory), SIZEOF(axwMemory, itfIPAddress, uiPort)); /// FUNCTION CreatePacket : IPacket VAR_INPUT /// | Pointer to the first byte of the memory. /// | This address should aligned to a ``__XWORD`` boundary! pbyMemory : POINTER TO BYTE; /// | Size of the allocated memory for the function block. /// | This value should be a multiple of ``SIZEOF(__XWORD)``! udiSize : UDINT; itfIPAddress : IIPAddress := 0; uiPort : UINT := 0; END_VAR VAR_OUTPUT udiPacketSize : UDINT; eErrorID : ERROR; END_VAR VAR // Instance pointer pPacket : POINTER TO _Packet; END_VAR IF pbyMemory = 0 OR udiMemSize < SIZEOF(_Packet) THEN eErrorID := ERROR.WRONG_PARAMETER; RETURN; END_IF {implicit on} pPacket := pbyMemory; pPacket^.__VFINIT(); pPacket^.FB_INIT(TRUE, FALSE); {implicit off} udiPacketSize := udiMemSize - SIZEOF(_Packet); IF udiPacketSize > 0 THEN eErrorID := pPacket^.SetInitialValue( pbyData := pbyMemory + SIZEOF(_Packet), udiSize := udiPacketSize, itfIPAddress := itfIPAddress, uiPort:=uiPort ); END_IF __QUERYINTERFACE(pPacket^, CreatePacket);
Example PacketPool
For a convenient application, it is always good to encapsulate all these details in function blocks and not have them called directly by the end user.
{attribute 'no_explicit_call' := 'An explicit call makes no sense'} {attribute 'no_assign'} {attribute 'call_after_init'} /// | Provides a pool of |IPacket| instances. /// | Use the ``GetPacket`` method to get a new instance. FUNCTION_BLOCK FINAL PacketPool IMPLEMENTS IPacketPool VAR_INPUT CONSTANT udiPacketSize : UDINT := 16#FFFFFFFF; udiInitialCapacity : UDINT; /// | The optional memory space for the packet pool. /// | ``pbyPool = 0`` => Memory is allocated from Heap /// | ``pbyPool <> 0`` => The related static Memory of size ``udiPoolSize`` is used. pbyPool : POINTER TO BYTE; /// The size of the memory area referenced by ``pbyPool`` udiPoolSize : UDINT; usiExtensionFactor : USINT; END_VAR VAR _hPool : MBM.HANDLE; _udiBlockSize : UDINT; _usiExtensionFactor : USINT; END_VAR
METHOD FINAL SetInitialValue : ERROR VAR_INPUT udiPacketSize : UDINT; udiInitialCapacity : UDINT; pbyPool : POINTER TO BYTE; udiPoolSize : UDINT; usiExtensionFactor : USINT; END_VAR VAR udiBlockSize : UDINT; udiMinimalPoolSize : UDINT; eError : MBM.ERROR; END_VAR IF _hPool = MBM.gc_hINVALID THEN (* execute only if the default 16#FFFFFFFF for udiSegmentSize is removed *) IF udiPacketSize <> 16#FFFFFFFF THEN THIS^.udiPacketSize := udiPacketSize; THIS^.udiInitialCapacity := udiInitialCapacity; THIS^.pbyPool := pbyPool; THIS^.udiPoolSize := udiPoolSize; THIS^.usiExtensionFactor := _usiExtensionFactor := usiExtensionFactor; udiBlockSize := SIZEOF(_Packet) + udiPacketSize; IF pbyPool <> 0 AND udiPoolSize > 0 THEN udiMinimalPoolSize := TO_UDINT(MBM.XChgGetSize(0, udiBlockSize, 0)); IF udiPoolSize >= udiMinimalPoolSize THEN _hPool := MBM.XChgCreateP( szBlockSize := udiBlockSize, ctNumPrios := 0, szMemSize := udiPoolSize, pMemory := pbyPool, peError := ADR(eError) ); ELSE SetInitialValue := ERROR.WRONG_PARAMETER; RETURN; END_IF ELSIF udiInitialCapacity > 0 AND udiBlockSize > 0 THEN _hPool := MBM.XChgCreateH( ctNumMsg := udiInitialCapacity, szBlockSize := udiBlockSize, ctNumPrios := 0, peError := ADR(eError) ); END_IF IF _hPool = MBM.gc_hINVALID THEN SetInitialValue := ERROR.WRONG_CONFIGURATION; RETURN; END_IF _udiBlockSize := TO_UDINT(MBM.RXChgGetMsgSize(_hPool, 0)); ELSE SetInitialValue := ERROR.WRONG_CONFIGURATION; RETURN; END_IF ELSE SetInitialValue := ERROR.ALREADY_INITIALIZED; END_IF
METHOD FINAL GetPacket : POINTER TO BYTE VAR_INPUT itfIPAddress : IIPAddress := 0; uiPort : UINT := 0; END_VAR VAR_OUTPUT itfPacket : IPacket; udiSize : UDINT; eErrorID : ERROR; END_VAR VAR pPacket : POINTER TO _Packet; eError : MBM.ERROR; udiNumPackets : UDINT; END_VAR IF _hPool = MBM.gc_hINVALID THEN eErrorID := ERROR.WRONG_CONFIGURATION; RETURN; END_IF pPacket := MBM.MsgReceive(_hPool, 0, ADR(eError)); IF pPacket <> 0 THEN itfPacket := CreatePacket( pPacket, _udiBlockSize, itfIPAddress, uiPort, udiPacketSize=>udiSize, eErrorID=>eErrorID ); GetPacket := pPacket^.pbyData; RETURN; ELSIF _usiExtensionFactor > 0 THEN udiNumPackets := TO_UDINT(MBM.RXChgGetCurCapacity(_hPool, 0)) * _usiExtensionFactor / 100; IF udiNumPackets < 1 THEN udiNumPackets := 1; END_IF eError := MBM.XChgExtendH(_hPool, udiNumPackets); IF eError = MBM.ERROR.NO_ERROR THEN GetPacket := THIS^.GetPacket( itfIPAddress, uiPort, itfPacket=>itfPacket, udiSize=>udiSize, eErrorID=>eErrorID ); RETURN; ELSE eErrorID := ERROR.NO_MEMORY; END_IF ELSE eErrorID := ERROR.NO_MEMORY; END_IF