Container

A typical task in image processing is to find objects in the image and to determine their shape, position and size. The objects found can be described, for example, by their contour points. Since the number of contour points depends on the specific object, it can vary greatly. For this reason, containers of dynamic size are used to store these contours.

However, the containers are not limited to storing contour points, they can also contain a variety of other data types. Different data types cannot be mixed within a container. Some basic concepts for handling containers are explained below.

Creating a container

A container can be created with a single function call. Only the type (see available types) and the initial number of elements have to be defined:

PROGRAM MAIN
VAR
    hr           : HRESULT;
    ipContainer  : ITcVnContainer;
END_VAR

hr := F_VN_CreateContainer(ipContainer, ContainerType_Vector_REAL, 10, hr);

In this sample a new container with 10 elements of type REAL was created, which is internally organized as C++ vector. Alternatively, the function F_VN_CopyContainer can be used to create a deep copy of an existing container. Not only the pointer to the data is copied - as with an assignment to another variable - but the data is copied to a newly allocated memory area.

All available types of containers are stored as global constants and named according to the following pattern: All names begin with "ContainerType_". This is followed by the type, which is composed of the description of the actual container type (based on C++ containers) and the element types (e.g. REAL, UDINT, etc.). Containers can in turn also contain further containers as elements. Each level of the container structure is represented in the name by the respective container type, e.g. "Vector_" or "String_". Therefore the resulting type name of a container with REAL elements is ContainerType_Vector_REAL.

Adding elements

Since the containers are able to dynamically expand the allocated memory, additional elements can be added to an existing container. The functions F_VN_AppendToContainer and F_VN_InsertIntoContainer can be used for this.

However, as it may be internally necessary to allocate a completely new memory area and to copy the existing data, it is recommended to reserve the maximum required memory in advance. Consequently, new memory is allocated only once and the program has an overall higher performance. A function exists for this, F_VN_ReserveContainerMemory, which merely reserves memory, but does not change the number of elements in the container.

// Reserve memory for 100 elements
hr := F_VN_ReserveContainerMemory(ipContainer, 100, hr);

// Append the value 1.23 to the end of the container
hr := F_VN_AppendToContainer_REAL(1.23, ipContainer, hr);

// Insert the value 4.56 into the container at index 2 (3rd element)
hr := F_VN_InsertIntoContainer_REAL(4.56, ipContainer, 2, hr);

Accessing elements

In the case that only a few individual elements of the container are to be accessed, it is recommended to use the F_VN_GetAt_... or F_VN_SetAt_... functions. Since no function name overload is possible in the PLC, there is a separate function name for each supported container type. The first element in the container has the index 0.

PROGRAM MAIN
VAR
    hr           : HRESULT;
    ipContainer  : ITcVnContainer;
    fElement     : REAL;
END_VAR

hr := F_VN_GetAt_REAL(ipContainer, fElement, 0, hr); // Get first element
hr := F_VN_SetAt_REAL(fElement, ipContainer, 2, hr); // Set third element

The advantage of using these ready-made functions is that only a single line of code is required and all checks (e.g. whether the container type is correct and the index is not too large) are performed internally. If you wish to access all elements in the container one after the other, it is recommended in particular with large containers to access the container elements via iterators and the associated access interface in order to achieve a better performance:

PROGRAM MAIN
VAR
    hr           :   HRESULT;
    ipContainer  :   ITcVnContainer;
    ipIterator   :   ITcVnForwardIterator;
    ipAccess     :   ITcVnAccess_REAL;
    fElement     :   REAL;
END_VAR

hr := F_VN_GetForwardIterator(ipContainer, ipIterator, hr);
IF SUCCEEDED(hr) AND ipIterator <> 0 THEN
    hr := ipIterator.TcQueryInterface(IID_ITcVnAccess_REAL, ADR(ipAccess));
    IF SUCCEEDED(hr) AND ipAccess <> 0 THEN
        WHILE SUCCEEDED(hr) AND ipIterator.CheckIfEnd() <> S_OK DO
            hr := ipAccess.Get(fElement);
            IF SUCCEEDED(hr) THEN
                fElement := fElement + 1;
                hr := ipAccess.Set(fElement);
            END_IF
            IF SUCCEEDED(hr) THEN
                hr := ipIterator.Increment();
            END_IF
        END_WHILE
    END_IF
    hr := FW_SafeRelease(ADR(ipAccess));
END_IF
hr := FW_SafeRelease(ADR(ipIterator));

If the elements in the container are in turn containers, use the following procedure instead.

Container of containers

Since, for example, the function F_VN_FindContours can find not only one but several contours, containers can contain further containers as elements (in which case all inner containers must contain elements of the same type). In principle, these can be used in exactly the same way as the simple containers described above. For this purpose there is, for example, the function F_VN_GetAt_ITcVnContainer for obtaining a deep copy of any inner container that is usable from the PLC (it is technically impossible to obtain an interface pointer to the original data). Due to the general ITcVnContainer data type at this level, it is not necessary to distinguish which base element types are contained in the inner containers. This simplifies access via iterators, since the special Access Interface is no longer necessary:

PROGRAM MAIN
VAR
    hr           : HRESULT;
    ipContainer  : ITcVnContainer;
    ipIterator   : ITcVnForwardIterator;
    ipElement    : ITcVnContainer;
END_VAR

hr := F_VN_GetForwardIterator(ipContainer, ipIterator, hr);
IF SUCCEEDED(hr) AND ipIterator <> 0 THEN
    WHILE SUCCEEDED(hr) AND ipIterator.CheckIfEnd() <> S_OK DO
        // ipElement gets a deep copy!
        hr := F_VN_GetContainer(ipIterator, ipElement, hr);
        IF SUCCEEDED(hr) AND ipElement <> 0 THEN
            // 1. extract information or manipulate ipElement
            // 2. write back changes if ipElement was manipulated
            hr := F_VN_SetContainer(ipIterator, ipElement, hr);
        END_IF
        hr := F_VN_IncrementIterator(ipIterator, hr);
    END_WHILE
    // more efficient to release outside loop, GetContainer handles it inside
    hr := FW_SafeRelease(ADR(ipElement));
END_IF
hr := FW_SafeRelease(ADR(ipIterator));

Displaying containers

At present the content of containers cannot be displayed in the Visual Studio Live Debugging without further action. Instead, the data can be exported to an array and viewed in this way:

VAR
    aArray      :   ARRAY [0..9] OF REAL;
    ipContainer :   ITcVnContainer;
    nBufferSize :   ULINT;
END_VAR

hr := F_VN_ExportContainerSize(ipContainer, nBufferSize, hr);
IF nBufferSize = SIZEOF(aArray) THEN
    hr := F_VN_ExportContainer(
        ipContainer :=ipContainer,
        pBuffer     :=ADR(aArray),
        nBufferSize :=nBufferSize,
        hr
    );
END_IF
Container 1:

Overwriting containers

Generally, containers with API functions can simply be overwritten. At present, however, overwriting is not possible if the existing container and the container to be written are of different types. This is signaled by the error code 70E (INCOMPATIBLE).