Container

Eine typische Aufgabe in der Bildverarbeitung ist, Objekte im Bild zu finden und deren Form, Position und Größe zu ermitteln. Dabei können die gefundenen Objekte zum Beispiel durch ihre Konturpunkte beschrieben werden. Da die Anzahl der Konturpunkte abhängig von dem konkreten Objekt ist, kann diese stark variieren. Aus diesem Grund werden Container dynamischer Größe verwendet, um diese Konturen zu speichern.

Die Container sind jedoch nicht darauf beschränkt nur Konturpunkte zu speichern, sondern sie können auch eine Vielzahl anderer Datentypen beinhalten. Allerdings können verschiedene Datentypen nicht innerhalb eines Containers gemixt werden. Im Folgenden werden ein paar grundlegende Konzepte im Umgang mit Containern erläutert.

Container erstellen

Ein Container kann mit einem einzigen Funktionsaufruf erstellt werden. Lediglich der Typ (siehe verfügbare Typen) und die initiale Anzahl von Elementen müssen definiert werden:

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

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

In diesem Beispiel wurde ein neuer Container mit 10 Elementen vom Typ REAL erstellt, der intern als C++ Vector organisiert ist. Alternativ kann mit der Funktion F_VN_CopyContainer auch eine tiefe Kopie eines bestehenden Containers erstellt werden. Dabei wird nicht nur der Pointer auf die Daten kopiert - so wie bei einer Zuweisung an eine andere Variable - sondern die Daten werden in einen neu allokierten Speicherbereich kopiert.

Alle verfügbaren Typen von Containern sind als globale Konstanten hinterlegt und nach folgendem Muster benannt: Alle Namen beginnen mit „ContainerType_“. Darauf folgt der Typ, der sich zusammensetzt aus der Beschreibung des eigentlichen Container-Typs (angelehnt an C++ Container) und dem Element-Typen (z.B. REAL, UDINT, …). Container können auch selbst wieder Container als Elemente enthalten. Jede Ebene der Container-Struktur wird im Namen durch den jeweiligen Container-Typ, z. B. „Vector_“ oder "String_", repräsentiert. Daher ergibt sich der Typ-Name eines Containers mit REAL Elementen zu ContainerType_Vector_REAL.

Elemente hinzufügen

Da die Container in der Lage sind, den zugewiesenen Speicher dynamisch zu erweitern, können einem bestehenden Container auch weitere Elemente hinzugefügt werden. Dazu können die F_VN_AppendToContainer und F_VN_InsertIntoContainer Funktionen benutzt werden.

Da es dafür aber intern notwendig sein kann, einen komplett neuen Speicherbereich zu allokieren und die bestehenden Daten zu kopieren, empfiehlt es sich, vorher den maximal notwendigen Speicher zu reservieren. Folglich wird nur einmal neuer Speicher allokiert und das Programm ist insgesamt performanter. Dazu existiert die Funktion F_VN_ReserveContainerMemory, die lediglich Speicher reserviert, die Anzahl der Elemente im Container jedoch nicht verändert.

// 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);

Zugriff auf Elemente

Für den Fall, dass nur auf wenige einzelne Elemente des Containers zugegriffen werden soll, empfiehlt es sich dafür die F_VN_GetAt_... bzw. F_VN_SetAt_... Funktionen zu verwenden. Da in der SPS keine Funktionsnamenüberladung möglich ist, gibt es für jeden unterstützten Containertyp einen eigenen Funktionsnamen. Das erste Element im Container hat dabei den 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

Der Vorteil bei der Verwendung dieser vorgefertigten Funktionen ist, dass nur eine einzelne Zeile Code notwendig ist und intern sämtliche Überprüfungen (z.B. ob der Container-Typ korrekt und der Index nicht zu groß ist) erledigt werden. Möchte man jedoch nacheinander auf alle Elemente des Containers zugreifen, empfiehlt es sich insbesondere bei großen Containern über Iteratoren und das zugehörige Access-Interface auf die Elemente des Containers zuzugreifen, um eine bessere Performance zu erreichen:

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));

Falls die Elemente des Containers wiederum Container sind, nutzen Sie stattdessen die folgende Vorgehensweise.

Container von Containern

Da z. B. die Funktion F_VN_FindContours nicht nur eine, sondern gleich mehrere Konturen finden kann, können Container selbst auch wieder Container als Elemente beinhalten (wobei aber alle inneren Container Elemente des gleichen Typs beinhalten müssen). Vom Prinzip her lassen sich diese genauso verwenden, wie die oben beschriebenen einfachen Container. So gibt es z. B. die Funktion F_VN_GetAt_ITcVnContainer, um eine aus der SPS nutzbare tiefe Kopie eines beliebigen inneren Containers zu erhalten (einen Interface Pointer auf die originalen Daten zu bekommen ist technisch nicht möglich). Da man auf dieser Ebene aufgrund des allgemeinen ITcVnContainer-Datentyps nicht unterscheiden muss, welche Basiselementtypen in den inneren Containern enthalten sind, vereinfacht sich der Zugriff über Iteratoren, da das spezielle Access Interface nicht mehr notwendig ist:

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));

Anzeige von Containern

Zurzeit kann der Inhalt von Containern nicht ohne weiteres im Visual Studio Live Debugging angezeigt werden. Stattdessen können die Daten in ein Array exportiert und so betrachtet werden:

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:

Überschreiben von Containern

Grundsätzlich können Container mit API-Funktionen einfach überschrieben werden. Zurzeit ist ein Überschreiben allerdings nicht möglich, wenn der existierende Container und der zu schreibende Container abweichende Typen haben. Dies wird durch den Fehlercode 70E (INCOMPATIBLE) signalisiert.