Interface Pointer

Für TwinCAT Vision werden dynamische Speicherbereiche benötigt, um z. B. Bilder und Container zu speichern. Diese Bilder und Container werden durch Variablen vom Typ ITcVnImage und ITcVnContainer repräsentiert. Da es sich dabei um Interfaces handelt und diese auf einen dynamischen Speicherbereich zeigen, werden diese Variablen als Interface Pointer bezeichnet.

Bei den TwinCAT Vision API-Funktionen wird die zugehörige Speicherverwaltung innerhalb der Funktionen automatisch geregelt, hier ist nichts zu beachten.

Beispielhaft wird ein neues Bild angelegt (800x600 Pixel vom Typ TCVN_ET_USINT mit einem Kanal):

VAR
    hr        : HRESULT;
    ipImage   : ITcVnImage;
END_VAR

hr := F_VN_CreateImage(ipImage, 800, 600, TCVN_ET_USINT, 1, hr);

Dieses Bild soll nun in ein RGB-Bild mit 3 Kanälen umgewandelt werden. Daher wird auch 3-mal so viel Speicher benötigt. Dafür genügt der folgende Funktionsaufruf:

hr := F_VN_ConvertColorSpace(ipImage, ipImage, TCVN_CST_GRAY_TO_RGB, hr);

Intern gibt er den bisherigen Speicherbereich frei und allokiert neuen Speicher in passender Größe. Interface Pointer sind also eine einfache Möglichkeit, Objekte mit dynamisch benötigtem Speicher in der SPS zu verwalten.

WARNUNG

Konsequenzen von Fehlgebrauch

Wenn Sie folgende Hinweise zum Gebrauch von Interface Pointern nicht beachten, kann dies zu vollständigen Systemabstürzen und zu Speicher-Lecks führen!

Referenzzähler

Sobald die Variablen nicht mehr ausschließlich als Funktionsparameter für die TwinCAT Vision API-Funktionen verwendet werden, ist es jedoch notwendig, sich genauer mit dem Konzept von Interface Pointern vertraut zu machen.

Es ist wichtig zu wissen, dass die Variable vom Typ Interface Pointer nur ein Zeiger auf ein Objekt im Speicher ist. Weist man also eine Variable einer anderen zu, so wird lediglich der Pointer auf diesen Speicherbereich kopiert, nicht jedoch die Daten selbst.

Das bedeutet, wenn die Daten in einer der beiden Variablen verändert werden, sind sie auch automatisch in der anderen geändert.
Wenn der Speicher in einer der Variablen freigegeben wird (z. B. im obigen Beispiel F_VN_ConvertColorSpace) und die zweite Variable immer noch ihren Pointer auf diesen Speicherbereich hat, so führt der Zugriff auf diesen bereits freigegebenen Speicher oftmals zu einem kompletten Systemabsturz.

Aus diesem Grund haben die Objekte im Speicher alle einen Referenz-Zähler. Sobald eine Variable einer anderen zugewiesen wird, muss dieser Referenz-Zähler erhöht werden. Wird einer Variablen ein neuer Pointer zugewiesen, muss der Referenz-Zähler auch wieder verringert werden.

Sobald der Referenz-Zähler dabei den Wert 0 erreicht, wird der Speicher freigegeben. Die entsprechenden Daten sind somit vollständig gelöscht.

Um anzuzeigen, ob eine Pointer-Variable auf einen gültigen Speicherbereich zeigt, muss dieser Pointer immer auf 0 gesetzt werden, wenn er nicht mehr verwendet wird (und daher der Referenz-Zähler verringert wurde). Standardmäßig werden alle angelegten Variablen (und damit auch die Pointer) in der PLC initial beim Programmstart automatisch auf 0 gesetzt.

Interface Pointer 1:

Verwaltung von Interface Pointern

In den TwinCAT Vision API-Funktionen (gekennzeichnet durch F_VN_...) wird der Referenzzähler von Interface Pointern automatisch verwaltet. Bei ihrer Verwendung ist also auf nichts Spezielles zu achten. Wenn Interface Pointer jedoch manuell kopiert werden oder z. B. als Funktionsparameter verwendet werden, ist eine manuelle Verwaltung nötig (siehe unten).

Validität von Interface Pointern prüfen

Da der Wert 0 bei einem Interface Pointer prinzipiell auch nur einem ungültigen Speicherbereich entspricht - und ein Zugriff darauf ebenfalls zu einem kritischen Systemfehler führt - muss vor jeder Verwendung geprüft werden, ob der Wert ungleich 0 ist.

In den TwinCAT Vision API-Funktionen (F_VN_...) geschieht diese Prüfung und entsprechende Behandlung intern automatisch. Bei Interface-Methoden, wie z. B. TcRelease() im folgenden Beispiel, ist dies jedoch nicht möglich, da bereits deren Aufruf zu einem Fehler führt, wenn die Variable 0 ist. Daher muss die Variable dort vorher explizit geprüft werden.

WARNUNG

Methodenzugriff auf ungültigen Speicherbereich führt zum kompletten Systemabsturz

Bei Interface-Methoden ist eine interne Überprüfung nicht möglich, da die zugehörigen Objekte existieren müssen, um die Methode aufrufen zu können.

Stellen Sie selbst außerhalb des Methodenaufrufs sicher, dass das entsprechende Objekt vorhanden ist (Interface Pointer ungleich 0).

Zudem tritt ein Systemabsturz nicht zwangsweise immer auf. Falls der ungültige Speicherbereich zufällig ungenutzt bleibt, kann ein Systemabsturz ausbleiben.

Freigeben eines Interface Pointers

Das Freigeben einer Interface Pointer Variablen besteht aus der Prüfung, ob diese bereits 0 ist und ansonsten Verringern des Referenz-Zählers und setzen der Variable auf den Wert 0. Da dies ein häufiger Anwendungsfall ist, gibt es die Funktion FW_SafeRelease, die alle benötigten Schritte mit einem einzigen Aufruf durchführt:

hr := FW_SafeRelease(ADR(ipImage));

Die interne Funktionsweise der FW_SafeRelease Funktion wird durch folgenden Code-Ausschnitt verdeutlicht:

IF ipImage <> 0 THEN       // Check if interface pointer is already = 0
                           // If not, call .TcRelease() and set it to 0
    ipImage.TcRelease();   // decrement reference count
    ipImage := 0;          // set to 0
END_IF

Kopieren eines Interface Pointers

Beim Kopieren eines Interface Pointers muss dessen Referenzzähler manuell erhöht werden. Als Kopiervorgang zählt sowohl eine direkte Zuweisung mittels des := Operators, als auch ein Programm- oder Funktionsblock-Aufruf, bei dem ein Interface Pointer als Input- oder Output-Wert übergeben wird. Ebenfalls zählt eine Übergabe mittels I/O-Abbild. Zum Erhöhen des Referenzzählers steht folgende Methode zur Verfügung:

ipImage.TcAddRef(); // increment reference count

Zudem muss darauf geachtet werden, dass der zu beschreibende Interface Pointer vor dem Kopieren freigegeben ist. Anderenfalls entstehen Speicher-Lecks. Ein manueller Kopiervorgang eines Interface Pointers sieht folglich z. B. so aus:

IF ipSrcImage <> 0 THEN                // check if source pointer is valid
    FW_SafeRelease(ADR(ipDestImage));  // release destination pointer
    ipDestImage := ipSrcImage;         // assign source pointer to destination pointer
    ipSrcImage.TcAddRef();             // increment reference count
END_IF

Beachten Sie, dass dabei nur der Interface Pointer, jedoch nicht das dahinterliegende Bild kopiert wird.

Faustregeln

Halten Sie sich bei der Handhabung von Interface Pointern an folgende Faustregeln.

Dabei ist egal, ob es sich bei den Interface Pointern um (anzeigbare) Bilder, Container, Iteratoren, etc. handelt, sie sind in den Beispielen gegenseitig auswechselbar.

Auf einem Interface Pointer eine Methode aufrufen

Es muss sichergestellt sein, dass der Interface Pointer valide ist
(Prüfung auf <> 0):

IF ipImage <> 0 THEN
    ipImage.GetImageInfo(stImageInfo);
END_IF

Grundsätzlich wird empfohlen, statt einer Interface-Methode die alternative F_VN_-Funktion anzuwenden (falls diese existiert), denn dann wird die Prüfung automatisch intern ausgeführt:

hr := F_VN_GetImageInfo(ipImage, stImageInfo, hr);

Wenn Sie die Prüfung und den Aufruf der Interface-Methode im selben Ausdruck durchführen, müssen Sie AND_THEN statt AND verwenden. Nur in dem Fall wird der hintere Teil nur ausgeführt, wenn der vordere Teil TRUE liefert:

IF ipIterator <> 0 AND_THEN ipIterator.CheckIfEnd() <> S_OK THEN
    ipIterator.GetContainer(ADR(ipElement));
END_IF

Interface Pointer manuell kopieren

Stellen Sie zuerst sicher, dass der zu beschreibende Interface Pointer freigegeben ist. Erhöhen Sie nach dem Kopieren den Referenzzähler des Interface Pointers:

FW_SafeRelease(ADR(ipDestImage)); 
IF ipSrcImage <> 0 THEN
    ipDestImage := ipSrcImage;
    ipSrcImage.TcAddRef();
END_IF

Interface Pointer im VAR_INPUT deklarieren

Bei Funktionen und Methoden muss nichts beachtet werden. Bei Programmen und Funktionsblöcken bleibt der Pointer in den Variablen auch nach dem Aufruf erhalten und sollte daher am Ende der POU auf 0 gesetzt werden:

VAR_INPUT
    ipSrcImage: ITcVnImage;
END_VAR
//… functional code
ipSrcImage := 0;

Damit wird sichergestellt, dass beim nächsten Aufruf nicht mehr auf den Speicherbereich zugegriffen werden kann, da dieser außerhalb bereits freigegeben worden sein könnte. Eine Erhöhung des Referenzzählers innerhalb der POU ist keine Lösung, da bei einer neuen Zuweisung der vorige Pointer überschrieben wird und daher nicht mehr freigegeben werden kann, wodurch ein Speicherleck entsteht. Falls der Interface Pointer behalten werden soll, sollte er in eine lokale Variable kopiert werden.

Interface Pointer im VAR deklarieren

Geben Sie den Interface Pointer am Ende von Funktionen und Methoden frei. Der Interface Pointer existiert nach Beendigung der POU nicht mehr, daher muss vorher der Referenzzähler verringert werden. Bei Programmen und Funktionsblöcken sollte der Interface Pointer freigegeben werden, sobald er nicht mehr benötigt wird:

VAR 
    ipImageWork: ITcVnImage;
END_VAR
//… functional code
hr := FW_SafeRelease(ADR(ipImageWork));

Nicht empfohlen - Interface Pointer im VAR_OUTPUT deklarieren

Die Deklaration eines Interface Pointers im VAR_OUTPUT ist nicht empfohlen, da die Verwendung von Outputs optional ist und so Speicherlecks entstehen können.
Bei Programmen und Funktionsblöcken bleibt der Pointer in den Variablen auch nach dem Aufruf erhalten, so dass bei Änderungen außerhalb, im Baustein auf einen freigegebenen Speicherbereich zugegriffen werden kann.
Damit das nicht passiert, müssen Sie immer sicherstellen, dass die Outputs verwendet werden und bei Programmen und Funktionsblöcken nach dem Kopieren der Output-Variable der Referenzzähler erhöht oder die Output-Variable auf 0 gesetzt wird.
Das ist sehr umständlich und fehlerträchtig, da man selbst darauf achten muss, an jeder Stelle im Code die Implementierung richtig umzusetzen. Daher wird stattdessen die Übergabe als Referenz im VAR_INPUT empfohlen.

Hinweis

Speicherlecks

Die Verwendung eines Interface Pointers im VAR_OUTPUT ist nicht empfohlen. Bei Nichtbeachtung der vorstehend genannten Punkte können Speicherlecks und Systemabstürze entstehen.

Interface Pointer als Referenz im VAR_INPUT deklarieren

Die Übergabe als Referenz sollte verwendet werden, wenn eine Änderung des Interface Pointers (z. B. neues Bild / Container zuweisen) innerhalb der POU nach außen gereicht werden soll, um dort weiterverwendet werden zu können.

VAR_INPUT 
    ipDestImage: REFERENCE TO ITcVnImage;
END_VAR

Das Beispiel Selbstgeschriebene Funktionen erläutert eine korrekte Verwendung von Interface Pointern in selbstgeschriebenen Funktionen und Methoden.

Konsequenzen von Fehlgebrauch

Wenn eine Interface Pointer Variable kopiert wird, dabei jedoch der Referenzzähler nicht mit TcAddRef() angepasst wird, gibt es zwei Pointer; dem Programm ist jedoch nur einer bekannt. Daher werden die Daten, auf die beide Interface Pointer Variablen zeigen, schon dann gelöscht, wenn nur eine der beiden Variablen freigegeben wird. Dabei kann das Freigeben sowohl durch ein manuell ausgeführtes FW_SafeRelease, als auch durch eine TwinCAT Vision API-Funktion, die die Variable neu beschreibt, geschehen. Wird daraufhin per Methodenaufruf auf die verbleibende Interface Pointer Variable zugegriffen, führt dies wie zuvor beschrieben zu einem vollständigen Systemabsturz.

Wenn im Gegensatz dazu Interface Pointer Variablen ohne vorherige Freigabe neu beschrieben werden (z. B. durch eine := Zuweisung), so führt dies zu Speicher-Lecks. Dies kann beispielsweise vorkommen, wenn Interface Pointer als Methodenvariablen genutzt und vor Beendigung der Methode nicht freigegeben werden. Ein Indiz für Speicher-Lecks ist im Informationsfenster des AMS Routers zu finden (siehe Router-Speicher). Wenn der dort als verfügbar markierte Speicher mit der Zeit abnimmt, lässt dies ein Speicher-Leck vermuten.