TwinCat Load Symbol Data @camper


  • Mod

    Neue VM aufgesetzt und geht wieder. Snapshot gemacht 😃


  • Mod

    Anleitung zum Dev-Maschine kaputtmachen:

    PROGRAM MAIN
    VAR
    	myInt : INT := 10;
    	aufzaehlung : (eins, zwei, drei);
    END_VAR
    

    Das ist absolut gültiger Code. Der Compiler ist damit glücklich und min Programm kann die Aufzählung super auslesen. Aber: sei nicht eingelogt, und wechsle zu tabular view: erfreue dich an einer sehr sehr langen Fehlermeldung in einer Messagebox und einem Absturz+Neustartaufforderung von Visual Studio.

    2. Variante: (die ich bisher nur durch zurückgehen auf einen früheren Snapshot lösen konnte, du bist gewarnt!)

    TYPE
    
    END_TYPE
    PROGRAM MAIN
    VAR
    	myInt : INT := 10;
    END_VAR
    

    Das ist nat. kein legaler Code, der Compiler stört sich daran aber nicht und erzeugt gerne ein Programm, in dem main fehlt (wie zuvor beschrieben). Die Situation bleibt so, selbst wenn der fehlerhafte type-Block entfernt wird. Windowsneustart, Anlegen eines neuen Projektes etc. helfen nicht.
    Ist das wirklich Software, die im Produktiveinsatz ist und mit Geld verdient wird? Wobei ich verstehen kann, dass der Support Geld einbringt...



  • Variante 1 ist kein legaler code. Keine Typdefinition in einem POU. Dafür gibt es die DUTs. Dass die Variante kompiliert wußte ich allerdings nicht. Da hätte ich auch einen Fehler erwartet.

    Aber Variante ist bis auf den genannten fehlerhaften Type-block eigentlich gültiger Code. Main darf leer sein.

    Ist das wirklich Software, die im Produktiveinsatz ist und mit Geld verdient wird?

    Ja 🙂

    TwinCat basiert auf codesys -> https://www.codesys.com/

    Was da Beckhoff noch alles dazu erfunden hat weiß ich nicht.
    Wobei der Support von Beckhoff kostenlos ist wenn man Kunde dort ist (Hardware)



  • Jetzt nicht den Überblick verlieren.

    Das geht nur auf einem Weg: indem die Implemention mit der Spezifikation abgleichst

    Ich hatte den Satz nicht richtig verstanden.
    Mann muss die seine Implementation mit der Spezifikation (von Beckhoff) abstimmen?
    Na klar. Ich programmiere ja nach der Spezifikation.
    Aber woher weißt du dass die Spezifikation im Ordner 3.1\sdk gilt.
    Wie gesagt laut Beckhoff gilt für jedes Modul eine andere Spezifikation.

    2. Es ist interessant welche Informationen du daraus rausziehen kannst. Auch wenn ich den Code nicht wirkliche verstehe. Aber ich benötige die Menge der Informationen auch gar nicht. Mir ist egal ob ein enum ein flag-Accessor zugeordnet ist oder nicht.
    Ich beobachte den Speicher auch nicht oder verändere ihn.
    Was ich benötige ist die Informationen der Variablen:

    Name
    IndexGroup
    IndexOffset
    Datatype
    Size

    mehr nicht

    damit ich mit AdsSyncReadWriteReq geziehlt die Variable beschreiben kann.

    Es ist toll und beeindruckend wie du dich in c++ auskennst aber aktuell komme ich damit meiner Lösung nicht näher.

    3. Ich habe deinen Code jetzt nochmals kopiert und in ein neues Visual Studio Projekt (VS 2017) eingefügt. Da ich es in meinem Projekt nicht zum laufen bekomme, da ich dort an verschiedenen Stellen die TcAdsDef.h und TcAdsAPI.h eingebunden habe.

    In dem neuen Projekt habe ich den Code erst mal komplett unverändert (bis auf #include "stdafx.h" -> Code um 1 Zeile verschoben) versucht zu kompilieren. Erhalte hier aber noch 4 Fehler:

    Error C3861 'exists': identifier not found adsdata.cpp 323
    Error C3861 'value': identifier not found adsdata.cpp 323
    Error C2664 'void std::vector<adsData::attributeInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 993
    Error C2664 'void std::vector<adsData::enumInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 999


  • Mod

    booster schrieb:

    Erhalte hier aber noch 4 Fehler:

    Error C3861 'exists': identifier not found adsdata.cpp 323
    Error C3861 'value': identifier not found adsdata.cpp 323
    Error C2664 'void std::vector<adsData::attributeInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 993
    Error C2664 'void std::vector<adsData::enumInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 999

    Stimmt. Komisch dass das bisher kompiliert hat.
    Die Zeile muss

    return optional<T, flag, Access>::exists(p, q) ? optional<T, flag, Access>::value(p, q) : default_value;
    

    lauten.



  • dann kommen die nächsten zwei Fehler

    Error	C3861	'get': identifier not found	AdsSymbolLoader	adsdata.cpp	259	
    Error	C2228	left of '.find' must have class/struct/union	adsdata.cpp	259
    

  • Mod

    Selbes Prinzip:

    ASSERT_THROW((var_data<T, data, length>::get(p, q).find('\0') == std::string_view::npos)); // no embedded \0
    

    ausserdem Zeile 196

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

    Richtiges 2-Phasen-Lookup ist bei Visual C++ leider seit Jahren immer noch WIP, da hängt es von der Mondphase ab, ob der Fehler gefunden wird.

    Ich habe noch ein paar andere Bugs gefixed, ausserdem adsData Konstruktoren spendiert wie besprochen.
    adsdata.h
    adsdata.cpp



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


Anmelden zum Antworten