TwinCat Load Symbol Data @camper
-
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.cppAus 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() }); }
-
Nächstes Problem.
Definiere ich einen Alias auf ein Intarray
TYPE TypedefIntArray : ARRAY [0..99] OF INT; END_TYPE tdIntArray : TypedefIntArray;
Erhalte ich im Konstruktor von DatatypeInfo die nächste Exception
else if (dt.get<DtIds::numArrayDims>() != 0) { ADSDATACURSOR_VERIFY("%1%", name.compare(0, arrStr.size(), arrStr) == 0); typeSpecs.emplace<Category::array>(dt, name.substr(name.find(" OF "sv) + " OF "sv.size())); size = 0; id = DatatypeId::blob_; }
Beim TypedefIntArray wird ein Array erkannt aber im Namen kommt kein "ARRAY OF [" vor
Folgende Lösung:
else if (auto dims = dt.get<DtIds::numArrayDims>(); dims != 0) { //ADSDATACURSOR_VERIFY("%1%", name.compare(0, arrStr.size(), arrStr) == 0); if (name.find(" OF "sv) == -1) typeSpecs.emplace<Category::alias>(); else { auto type = name.substr(name.find(" OF "sv) + " OF "sv.size()); typeSpecs.emplace<Category::array>(dt, type); size = 0; id = DatatypeId::blob_; } }
-
Das folgende liefert nun auch eine Exception. Da ich den Typedef auf das IntArray auch als Alias angelegt habe.
// crosslink datatypes case Category::alias: { auto it = std::lower_bound(types_.begin(), types_.end(), dt.get<DtIds::type>()); std::get<Category::alias>(type->typeSpecs) = &*it; ADSDATACURSOR_VERIFY("%1%", dt.get<DtIds::size>() == it->size); break; }
it->size liefert (INT) => 2
dt.getDtIds::size() liefert (ARRAY OF INT 0-99) => 200Wobei ich noch nicht weiß wie lösen deshalb erst mal auskommentiert.
Ist ja "nur" eine Überprüfung.
-
Jetzt muss ich nochmals fragen. Auch wenn camper seit 2 Monaten nicht mehr online war. Vieleicht taucht er irgendwann man wieder auf.
Was ist bei dir ein Alias?
Im TwinCat System ist z.B. Type OTCID definiert der auf einen UDINT verweist.
TYPE OTCID : UDINT; END_TYPE
in cyclecheck bekomme ich beim rekursiven aufruf einen Fehler
SizeType cycleCheck(AdsData::DatatypeInfo* p) { ... switch (p->typeSpecs.index()) { ... case Category::alias: cycleCheck(std::get<Category::alias>(p->typeSpecs)); // bei "OTCID" eralte ich dann für AdsData::DatatypeInfo* p = null break; } ... }
-
@booster sagte in TwinCat Load Symbol Data @camper:
in cyclecheck bekomme ich beim rekursiven aufruf einen Fehler
Den bekomme ich weil ich, das war nur eine "Überprüfung" ist , auskommentiert habe.
Mist.
-
Hätte noch so viele Fragen zu deinem Code.
- Wieso ist das feld info_ private in AdsVarData. Wenn ich die Daten (AdsVarData) einer Variablen über den Index Operator von AdsData anfordere. Möchte ich ja genau diese Informationen haben.
- Wieso heißt der Typ von dem Feld SubSymbolInfo. Das sind doch nicht nur Informationen zu den Subsymbols.
- Am SpecType der wiederum im DataTypeInfo vorhanden ist kann ich nicht unterscheiden ob ich einen struct oder eine function block vorliegen habe. Was aber entscheidend ist. Der lesende und schreibende Zugriff darauf unterscheidet sich.
-
Noch ein Fehler:
auto pvoidIt = std::lower_bound(types_.begin(), types_.end(), "PVOID"sv); SizeType pointerSize = pvoidIt == types_.end() || pvoidIt->name != "PVOID"sv ? 4 : pvoidIt->size;
Funktionalität:
Hier wird die Größe von Pointern ermittelt in dem man in _types nach "PVOID" sucht und dessen Größe verwendet.
Wird der Datentype "PVOID" nicht gefunden wird die Größe 4 verwendet.Anmerkung:
Keine Ahnung wie std::lower_bound in einem vector von DatatypeInfo mit einem string "PVOID" sucht. Woher weiß die Funktion dass sie nach dem Feld "name" in DatatypeInfo suchen soll. Das funktioniert zwar aber ich verstehe nicht warum.Problem:
"PVOID" taucht in der Liste (types_) aber nur auf wenn auch eine Instanz von PVOID irgendwo im PLC Projekt angelegt wurde. Warum weiß ich nicht. Wurde der Typ nun nicht angelegt kann ich davon auch keine Größe ermitteln und der Defaultwert 4 wird genommen als Pointersize.Die Pointersize ist allerdings in TwinCat3.1 -> 8 und in TwinCat2 -> 4.
Das führt dann hier zu einer Exception:
ADSDATACURSOR_VERIFY( "%1%\nSymboldata corrupted: mismatched size\nit->size == %2%\nsym.get<SymIds::size>() == %3%", sym.get<SymIds::size>() == it->size, it->size, sym.get<SymIds::size>());
it->size ist wie gesagt 4
und sym.get<SymIds::size>() liefert 8und wie sym.get<SymIds::size>() auf seine 8 kommt habe ich nach ewigem studieren und debuggen der Funktion immer noch nicht verstanden.
template <IdType prop> constexpr auto get() const noexcept { return PropertyList<tag>::map::template get<prop>(data_.data_, data_.parent()); }
?????
-
@booster sagte in TwinCat Load Symbol Data @camper:
Anmerkung:
Keine Ahnung wie std::lower_bound in einem vector von DatatypeInfo mit einem string "PVOID" sucht. Woher weiß die Funktion dass sie nach dem Feld "name" in DatatypeInfo suchen soll. Das funktioniert zwar aber ich verstehe nicht warum.Zumindest die Anmerkung habe ich gelöst
In DatatypeInfo sind ja die verschiedenen operatoren < > und == implementiert
friend bool operator<(const AdsData::DatatypeInfo& lhs, std::string_view rhs) { return lhs.name < rhs; } ...
-
Wieso liefert AdsVarData::shortName() z.b. bei einer Struktur den vollen Namen
Also z.B. "Main.MyStruct.MyVariable" und nicht nur "MyVariable" was ja dem shortName entsprechen würde und nur in einem Array bekomme ich Beispielsweise "[1]" als shortName();Wie bekomme ich wiederum von einem Array denn vollen Name. Wenn ich hier name() aufrufe muss ich als parameter den prefix übergeben. Was ja nicht ganz logisch ist. Das sollte die Instanz ja selber wissen.
Aber bei einem Array wird der Name der wiederum in info_ liegt gar nicht gesetzt. Warum auch immer.
-
Weiß irgendjemand was vom @camper. Ist der hier ausgestiegen?
-
@booster Das ist duchaus normal, dass Mitglieder (auch Moderatoren) mal eine ganze Weile abwesend sind, das können Monate aber auch Jahre sein. Ich war auch schon 1-2 Jahre abwesend. Letztendlich ist es halt eine Freizeitaktivität, die schnell mal weichen muss...
-
Das ist klar dass das nur eine Freizeitbeschäftigung ist. Ich stelle ja auch keinen Anspruch auf seine Anwesenheit.
Er hat mir mit seinem Code ziemlich viel geholfen. Nichst desto trotz funktioniert er an einigen Stellen nicht. Jetzt kämpfe ich mich halt durch den Code durch. Ihr wisst ja selber wie schwer es ist wenn es nicht sein eigener Code ist.Da frag man sich dann an manchen Stellen ist das nun ein BUG oder hat sich der Coder einfach was anderes dabei gedacht. Das liegt auch daran dass der Code von Camper ziemlich komplex ist und mit den Kommentaren ziemlich sparsam umgegangen wurde.
Da wäre es einfach manchmal nicht schlecht wenn man noch ein paar Fragen zum Code los werden könnte. Ich kann zwar immer mal wieder Teile aus dem Code von Camper raus ziehen und hier im Forum fragen dazu stellen. Aber die Frage was hat camper sich dabei gedacht wird wahrscheinlich nicht immer so einfach zu beantworten sein.
-
Es scheint als wäre @camper wieder online. Würdest du mir nochmals helfen?