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 boundary

  • The 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