TwinCat Load Symbol Data @camper



  • Ist nett das du dir so viel Zeit nimmst und auch die Fehler verbesserst.
    Aber wie gesagt dein Code - und mag er noch so viel können und so effizient wie möglich sein - bringt mir in meinem Projekt nicht sehr viel.

    1. Ich muss die Headerdatein verwenden die mir Beckhoff als öffentlich und verbindlich bereit stellt.

    2. Ich muss den Code warten können.

    3. Ich benötige nur die von mir angegebenen Informationen

    Name
    IndexGroup
    IndexOffset
    Size
    Datatype
    Datatype ID

    und die Informationen für die Subvariablen.

    Ich muss nicht prüfen ob das SPS Programm valide ist oder sonstiges Zeugs. Sorry.



  • Zeile 198

    ASSERT_THROW(get(p, q) <= properties_of<T>::template get<expected>(p, q));
    

    wurde in deinem neusten Code aber noch nicht gefixed

    Ändere ich das nun aber in das:

    ASSERT_THROW(next_data<T, before, align>::get(p, q) == properties_of<T>::template get<expected>(p, q));
    

    Kommt der Fehler:

    Error	C2760	syntax error: unexpected token ')', expected 'id-expression'adsdata.cpp	198
    


  • Hi camper. Ich komm da nicht weiter was will mir denn der Compilerfehler sagen?


  • Mod

    Leider hat sich mein Computer kurz vor Ostern in die ewigen Jagdgründe verabschiedet, brauchte deshalb ein bisschen Zeit.

    Aus meiner Sicht fertige Version:
    - statische Variablen in Funktionsblöcken werden richtig behandelt
    - einzelne Bits in Strukturen werden unterstützt
    - die Auswertung von Attributen und Enums habe ich entfernt, da ausdrücklich kein Bedarf besteht
    - der Bequemlichkeit wegen benutze ich Boost.Format und Boost.Preprozessor (Boost verwendest du ja sowieso schon)
    - Bezeichner einigermaßen konsistent an CamelCase angepasst
    - der Parsercode ist nicht mehr abhängig von irgenwelchen Ads- oder Windows-Headern
    - compiliert auch mit gcc7.3.0 und clang6
    - sollte jetzt auch auf BigEndian-Systemen funktionieren

    Den Code, der die serialisierten Daten liest, habe ich in einen extra Header verschoben:
    adsDataCursor.h
    wenn ADSDATACURSOR_DEBUG definiert ist, enthalten einzelne AdsDataCursor-Objekte (war zuvor ref<...>) eine Kopie der gescannten Daten, die im Debugger direkt angeschaut werden können - das ist allerdings nicht auf Effizienz getrimmt und daher sehr langsam und sollte im Allgemeinen ausgeschaltet bleiben.
    Die Fehlerbehandlung in ADSDATACURSOR_VERIFY kann durch Definition eigener Handler (ADSDATACURSOR_VERIFY_MSG_HANDLER bzw. ADSDATACURSOR_VERIFY_DEBUG_MSG_HANDLER) vor Einbinden des Headers beeinflusst werden (um z.B. eine MessageBox anzuzeigen oder eine TRACE-Meldung abzusetzen).

    Der Rest:
    adsData.h
    adsData.cpp

    Testprogramm geht alle Symbole durch, testet, ob der []-Operator funktioniert und vergleicht die Daten mit einem Aufruf per ADSIGRP_SYM_INFOBYNAMEEX
    main.cpp
    Die Implementation von ADSIGRP_SYM_INFOBYNAMEEX scheint dabei einen Bug zu haben: für Zeigervariablen, die Teil einer Struktur sind, wird die DatatypeId des Pointees geliefert, statt der des Zeigers.

    formatiert mit clang-format
    .clang-format

    PLC-Testprojekt mit verschiedenen Deklarationen
    test1.xml

    Der Compilerfehler, mit dem du zuvor gerungen hast, kann durch ein extra Paar Klammern beseitigt werden. Präprozessor-Argumente werden durch Komma getrennt, sofern sich sie nicht innerhalb (zusätzlicher) runder Klammern befinden. Für C genügt das in der Regel, in C++ treten Kommas oft auch zwischen spitzen <> und geschweiften {} Klammern auf, ohne zusätzliches rundes Klammerpaar führt das wie hier schnell dazu, dass ein einzelner Bezeicher in mehrere Makroargumente aufgeteilt wird.



  • Hi camper.

    Habe dich schon vermisst 😃
    Vielen Dank dass du dich nochmal reingehängt hast.
    Zum Code: Ich bin begesitert.

    und sogar die Klammern in einer eigenen Zeile. 👍 😃

    Die Frage soll jetzt nicht unverschämt sein. Nur aus Interesse: War dir "langweilig". Oder woran arbeitest du dass du Zeit um dich in solche Themen einzuarbeiten.


  • Mod

    booster schrieb:

    War dir "langweilig".

    Anfangs, ja. Und wenn ich es einmal anfange, sollte es auch zu einem gewissen Abschluss gebracht werden.

    Kleine Korrektur:
    AdsVarData benötigt noch eine Überladung des -> Operators

    AdsVarData* AdsVarData::operator->() noexcept { return this; }
    

    damit der -> Operator für die in adsData.h definierten Iteratoren funktionieren kann. Wird im Testcode zwar nicht gebraucht, aber der Standard verlangt es.



  • Anfangs, ja. Und wenn ich es einmal anfange, sollte es auch zu einem gewissen Abschluss gebracht werden.

    Kann ich verstehen.
    Auch wenn mich es noch interessiert hätte was du sonst so machts. Kann ich es auch verstehen wenn du das hier nicht sagen willst.

    Ich habe nun mal deinen Code auf unser PLC Projekt losgelassen.

    Und erhalte jetzt die folgende Meldung

    __thiscall AdsData::ArrayInfo::ArrayInfo(const class adsDataCursor::AdsDataCurso
    r<4,0,0> &)
    adsdata.cpp 143:
    assertion failed:
    info.elements != 0 && rBound >= info.lBound && arraySize % info.elements == 0
    array info corrupted with
    info.elements == 10
    info.lBound == 1
    rBound == 10
    arraySize == 8
    

    Jetzt habe ich noch nicht ganz verstanden was du da überprüfst und auch nicht welches Array aus meiner SPS nun "corrupted" ist. Dazu fehl mir der Name der Array Variablen. Bin noch auf der Suche wie ich mir den mit Ausgebe. Im Konstruktor ArrayInfo wo die Überprüfung statt findet steht der nicht zur Verfügung.



  • Ok habe nun mal den Fehler lokalisiert.

    Dieser Typ (der den Fehler verursacht) hat dir in deiner PLC noch gefehlt

    arraybool_p : POINTER TO ARRAY [1..10] OF BOOL;
    

    Jetzt ist nur noch unklar wieso dein Code hier das als "corrupted" deklariert. 😃



  • auto arraySize = dt.get<DtIds::size>();
    

    liefert in diesem Fall 8 zurück
    und info.elements sind 10.

    arraySize % info.elements == 0 ist dann natürlich false;
    Wenn ich nun dahinter steigen würde was dt.getDtIds::size() genau macht.
    Also dass es die größe des Arrays liefern soll ist klar.

    Aber die Programmierung dahinter ist für mich noch weit weg von verständlich.


  • Mod

    booster schrieb:

    Ok habe nun mal den Fehler lokalisiert.

    Dieser Typ (der den Fehler verursacht) hat dir in deiner PLC noch gefehlt

    arraybool_p : POINTER TO ARRAY [1..10] OF BOOL;
    

    Jetzt ist nur noch unklar wieso dein Code hier das als "corrupted" deklariert. 😃

    Weil der bei Beckhoff veröffentlichte Beispielcode diesen Fall auch nicht korrekt behandelt. Im Unterschied zu diesem sagt mein Code aber wenigstens, dass etwas nicht stimmt, und erfindet nicht Fantasiewerte 🙂
    Zeiger auf Arrays unterliegen offenbar einem Decay ähnlich wie in C, die Arrayinformation wird dabei Teil des Zeigertyps, was dazu führt, dass der Code den Zeiger wie ein Array zu behandeln versucht. Auch der Beispielcode hat ja nur geschaut, of ( pEntry->arrayDim != 0) erfüllt ist (in CAdsParseSymbols::SubSymbolInfo).

    Korrektur besteht darin, dass Zeiger und Referenzen gleich gefiltert werden.

    Ausserdem hat TwinCAT3 einen Bug (in TwinCAT2 funktioniert es so, wie man erwarten würde), indem bei verschachtelten Arays (ARRAY OF ARRAY)
    und Arrays aus Aufzählungen, die übermittelte Größe und der Datentyp des Arrays der des Elements sind.
    Lösung besteht hier darin, dass ich die Arraygröße erst mal ignoriere und erst im Nachhinein (nachdem der Elementtyp bekannt ist) restauriere und der Datentyp explizit auf blob gesetzt wird.

    Als kleine Erweiterung habe ich den Zeigerdecay rückgängig gemacht, und AdsVarData eine deref-Funktion spendiert, mit der die Variableninformationen, auf die ein Zeiger zeigt, ermittelt werden können (siehe Beispielprogramm).

    adsData.h
    adsData.cpp

    main.cpp



  • Weil der bei Beckhoff veröffentlichte Beispielcode diesen Fall auch nicht korrekt behandelt.

    Was heißt nicht korrekt behandelt?
    Es wird halt nur geprüft ob pEntry->arrayDim != 0. Wie du schon sagtest
    Es heißt es wird halt nicht geprüft. Aber es werden doch keine Fantasiewerte erfunden?

    ...die übermittelte Größe und der Datentyp des Arrays der des Elements sind.

    Datentyp stimmt. Arraygröße nicht.



  • Ausserdem hat TwinCAT3 einen Bug (in TwinCAT2 funktioniert es so, wie man erwarten würde), indem bei verschachtelten Arays (ARRAY OF ARRAY)
    und Arrays aus Aufzählungen, die übermittelte Größe und der Datentyp des Arrays der des Elements sind.

    folgende Ausdrücke liefern folgende Arraygrößen (getestet mit .Net Applikation)

    abool : ARRAY[1..10] OF BOOL; // TC2 = 10 | TC3 = 10
    aofabool : ARRAY [1..10] OF ARRAY [1..10] OF BOOL; // TC2 = 100 | TC3 = 100
    aabool : ARRAY [1..10, 1..10]  OF BOOL; // TC2 = 100 | TC3 = 100
    
    abool_p : POINTER TO ARRAY [1..10] OF BOOL; // TC2 = 4 | TC3 = 8
    

    Also so verhalten sich beide Systeme gleich. Bis auf die Zeigergröße 4 oder 8 Byte. Also die verschachtelten Arrays liefern doch das richtige (erwartete) Ergebnis.

    Nur eben nicht beim Pointer.


  • Mod

    booster schrieb:

    Weil der bei Beckhoff veröffentlichte Beispielcode diesen Fall auch nicht korrekt behandelt.

    Was heißt nicht korrekt behandelt?
    Es wird halt nur geprüft ob pEntry->arrayDim != 0. Wie du schon sagtest
    Es heißt es wird halt nicht geprüft. Aber es werden doch keine Fantasiewerte erfunden?

    Mit

    p : POINTER TO ARRAY [1..2] OF INT;
    

    erzeugt Beckhoffs Code die Symbole
    MAIN.p[1] // offset == offset des Zeigers (== dort wo sich p befindet, nicht der Ort, auf den p zeigt)
    MAIN.p[2] // offset == offset des Zeigers + 1
    was offenkundig Unfug ist.

    Für die Definitionen

    word_p : POINTER TO WORD;
    abool_p : POINTER TO ARRAY [1..10] OF BOOL;
    xyz : POINTER TO ARRAY [0..2] OF POINTER TO ARRAY [0..4] OF ARRAY [0..6] OF POINTER TO POINTER TO INT;
    

    bekomme ich folgende relevante Informationen geliefert (TwinCAT3):

    Typen:
    name                                                                                             DatatypeId            Größe   ArrayDim       Datentyp (string)
    ARRAY [0..6] OF POINTER TO POINTER TO INT                                                        blob_               >>1<<     [0..6]       >>INT<<
    POINTER TO ARRAY [0..2] OF POINTER TO ARRAY [0..4] OF ARRAY [0..6] OF POINTER TO POINTER TO INT  blob_                 4       [0..2]         POINTER TO ARRAY [0..4] OF ARRAY [0..6] OF POINTER TO POINTER TO INT
    POINTER TO ARRAY [0..4] OF ARRAY [0..6] OF POINTER TO POINTER TO INT                             blob_               >>2<<     [0..4]       >>ARRAY [0..6] OF INT<<
    
    POINTER TO ARRAY [1..10] OF BOOL                                                               >>bool_<<               4       [1..10]        BOOL
    POINTER TO WORD                                                                                  blob_                 4                      WORD
    
    Symbole:
    MAIN.abool_p                                                                                   >>bool_<<               4                      POINTER TO ARRAY [1..10] OF BOOL
    MAIN.word_p                                                                                      blob_                 4                      POINTER TO WORD
    MAIN.xyz                                                                                       >>int16_<<              4                      POINTER TO ARRAY [0..2] OF POINTER TO ARRAY [0..4] OF ARRAY [0..6] OF POINTER TO POINTER TO INT
    

    System erkannt? Markierte Werte sind offenbar fehlerhaft.
    TwinCAT2 macht es sich noch einfacher und sendet gleich gar keine Typinformationen zu diesen Symbolen, muss man also selber aus dem Typstring parsen.

    Zmindest der PLC-Editor von TwinCAT2 scheint mit diesen Definitionen im Onlinemodus umgehen zu können, ich habe daher nicht den Eindruck, dass derartig tief verschachtelte Deklarationen nicht unterstützt würden.

    Neue Version, die mit solchen Definitionen und sollte jetzt auch mit 64bit-Zeigern umgehen könenn (allerdings ist das TcAdsAPI-Protokoll auf 32bit Offsets beschränkt).
    adsData.cpp
    main.cpp

    Aus einer recht einfachen Parserfunktion ist jetzt allerdings ein 200-Zeilen Monster geworden um mit diesem Unfugprotokoll arbeuten zu können 😡, etwas Refactoring sollte allerdings möglich sein...

    Auch TwinCAT2 scheint eine abgeschwächte Version des DatatypeId-Bugs zu haben:
    Für das Symbol MAIN.ABOOL_P gibt er mir (per ADSIGRP_SYM_INFOBYNAMEEX) dort auch bool_ statt blob_ für DatatypeId - obwohl es doch ein Zeiger ist.



  • System erkannt?

    Nein 😃

    Ich leg mir die Datentypen auch mal an und lade mir die Informationen mit dem .Net Programm aus. Dann kann ich das an den Beckhoff Support weiterleiten.

    Muss das mit .Net machen da Beckhoff die c++ Schnittstelle nicht mehr wirklich supported.

    Mal sehen was Sie dazu sagen.



  • booster schrieb:

    System erkannt?

    Nein 😃

    Ich leg mir die Datentypen auch mal an und lade mir die Informationen mit dem .Net Programm aus. Dann kann ich das an den Beckhoff Support weiterleiten.

    Muss das mit .Net machen da Beckhoff die c++ Schnittstelle nicht mehr wirklich supported.

    Mal sehen was Sie dazu sagen.

    Würde micht nicht wundern wenn es mit der .Net schnittstelle geht und Beckhoff dann sagt verwendet doch die .Net Schnittstelle



  • Zunächst habe ich mir mal mit der Beispielapplikation in .Net von Beckhoff die Symbole auflisten lassen. - Die Beispielapplikation listet nur Symbole auf keine Datentypen, muss ich mal noch mein eigenes Testprogramm schreiben.

    Aber die drei Symbole die du in dem beispiel gezeigt hast werden auch gleich falsch in .Net ausgegeben. Wahrscheinlich weden die Datentypen aber auch falsch dargestellt. Das .Net bassiert auf dem c++ kern.

    Nochmals zu dem

    p : POINTER TO ARRAY [1..2] OF INT;
    

    erzeugt Beckhoffs Code die Symbole
    MAIN.p[1] // offset == offset des Zeigers (== dort wo sich p befindet, nicht der Ort, auf den p zeigt)
    MAIN.p[2] // offset == offset des Zeigers + 1
    was offenkundig Unfug ist.

    Wenn ich mir das im debugger von TwinCat anzeige erhalte ich für
    MAIN.p[1]
    MAIN.p[2]
    beides mal den Inhalt.

    Und auch in der .Net applikation greife ich mit den beiden ausdrücken korrekt auf den Inhalt zu.

    Oder hättest du da was anderes erwartet?

    p zeigt auf das Array.
    mit p[1] zeige ich auf das erste element im array.



  • Hi camper. Bist du noch da? 🙂

    Leider habe ich keine direkte Adresse um dich zu erreichen (ich weiß wird wohl mit Absicht sein). Hatte noch diverse andere Aufgaben und konnte mich erst jetzt wieder diesem Problem widmen.

    Frage: Du hast mir mit adsdata.cpp eine neue Version zur Verfügung gestellt die mit den falschen Pointertypen zu recht kommt.

    In dieser Version fehlt aber in der Klasse AdsVarData die comment Methode:

    const std::string& AdsVarData::comment() const noexcept { return info_->comment; }
    

    In der verify methode wird diese aber 3 mal verwendet.

    Du hast die "comment" Information allgemein überall rausgenommen. Absicht?



  • Noch ein Kontaktversuch.

    Hallo Camper.

    Damit ich mal weiter machen kann habe ich mal die Verwendung von "Comment" rausgenommen.

    Habe deinen Parser nun mal auf unsere in Produktiv befindliches System losgelassen.

    Und erhalte nun eine Exception in verifySubs bei aufruf des []Operators aif AdsData.

    ->
    throw std::out_of_range("symbol "sv + name + " not found"sv);

    Leider kann ich noch nicht sagen wie es zu diesem Fehler kommt.
    Eigentlich suche ich ja nicht nach einem bestimmten Symbol sondern man iteriert ja nur alle Symbole durch.



  • Hi.

    Ich dokumentiere hier mal was ich noch herausgefunden habe. Vieleicht meldet sich camper nochmals 🙂

    In der Methode:

    AdsVarData AdsData::maybe(std::string_view name) const
    

    suchst du mit upper_bound nach der nächsten Variable in der übergeordneten Liste.
    um anschliesend mit --sym zur eigentlichen übergeordneten Variable zurück zu gelangen.

    Da hier davon ausgegangen wird dass die Namen der untergeordneten Variablen aus den übergeordneten zusammengesetzt sind und ich den übergeordneten Namen dadurch eindeutig identifizieren kann.

    Beipiel:

    MAIN.Auto
    MAIN.Bus
    MAIN.Panel
       MAIN.Panel.RealUnit
       MAIN.Panel.Point
    MAIN.System
    

    suche ich nach der Variablen "MAIN.Panel.RealUnit" mit upper_bound in der übergeordneten Liste erhalte ich
    MAIN.System. Gehe ich nun wiederum 1 element zurück mit -- bin ich bei meiner übergeordneten Variable Main.Panel.

    Folgendes Problem.

    In TwinCat kann ich Variablen als Ein oder Ausgänge definieren. Mit "VarName AT %I* oder "VarName AT %Q*;
    diese Variablen werden dann zusätlich zu ihrer eigentlichen Position in der sub in der obersten Hierarchie angezeigt.

    Definiere ich nun in der Struktur der Instanz Panel die Variable Point als Input

    Point AT %I* : INT;
    

    taucht diese Variable 2 mal auf.

    MAIN.Auto
    MAIN.Bus
    MAIN.Panel
       MAIN.Panel.RealUnit
       MAIN.Panel.Point
    MAIN.Panel.Point
    MAIN.System
    

    Such ich nun nach "MAIN.Panel.RealUnit" mit upper_bound erhalte ich nicht "MAIN.Panel.Point" als nächste Variable sondern "MAIN.System"

    "MAIN.Panel.RealUnit" ist größer als "MAIN.Panel.Point".

    mit --sym lande ich dann auf "MAIN.Panel.Point" was natürlich nicht meiner "base" Variablen entspricht.

    das Problem geht natürlich weiter. Da ich auch Arrays als Eingabe definieren kann und mit

    if (auto pos = curName.find('['); pos != curName.npos)
       curName.remove_suffix(curName.size() - pos);
    

    dem enfernen des suffixes, nicht zwingend meine eindeutige baseInfos finde.



  • Die Methode maybe habe ich nun mal umgeschrieben.
    Leider nicht mehr ganz so effektiv wie upper_bound aber funktioniert.

    AdsVarData AdsData::maybe(std::string_view name) const
    {
    	auto curName = name;
    
    	decltype(symbols_.begin()) sym;
    
    	while ((sym = std::find(symbols_.begin(), symbols_.end(), curName)) == symbols_.end())
    	{
    		auto ptlast = curName.find_last_of('.') != curName.npos ? curName.find_last_of('.') : 0;
    		auto arlast = curName.find_last_of('[') != curName.npos ? curName.find_last_of('[') : 0;
    
    		const auto pos = std::max(ptlast, arlast);
    
    		if (pos != 0)
    			curName.remove_suffix(curName.size() - pos);
    		else
    			break;
    	}
    
    	// -> durch Deklaration einer Variablen mit %I oder %O in TwinCat
    	// wird dieselbe Variable nochmals in der obersten Liste abgebildet
    	//-> somit funktioniert der folgende Suchalgorithmus nicht.
    
    	//auto sym = std::upper_bound(symbols_.begin(), symbols_.end(), curName);
    	//if (sym == symbols_.begin()
    	//    || ((void)--sym, curName.compare(0, sym->baseInfo.name.size(), sym->baseInfo.name) != 0))
    	//    {
    	//        return {};
    	//    }
    
    	AdsVarData data{ &sym->baseInfo, sym->group, sym->baseInfo.offset };
    	if (name.size() == sym->baseInfo.name.size())
    		return data;
    	else
    		return data.maybe(
    			std::string_view{ name.data() + sym->baseInfo.name.size(), name.size() - sym->baseInfo.name.size() });
    }
    

Anmelden zum Antworten