TwinCat Load Symbol Data @camper



  • Hallo camper.

    Weiß jetzt nicht wie ich dich anders ereichen kann. Und dachte ich mal mal nen neuen Thread auf.

    Ich hätte noch eine Frage zu dem Code den du im Thread "Speicherverbrauch und shared_ptr" gepostet hast. Der Thread dort ist etwas zu lang geworden und der Titel trifft auch nicht mehr ganz den Inhalt.

    Du hast da folgendes gepostet:

    std::vector<adsData_impl::adsDT> parse_dt_data(const std::vector<char>& dt_data) {
            std::vector<adsData_impl::adsDT> result;
            auto len = dt_data.size();
            if (len == 0)
                return result;
            std::size_t dt_count = 0;
            for (auto p = &dt_data[0]; len != 0; ) {
                if (len < sizeof(AdsDatatypeEntry) + 3)
                    throw std::runtime_error("datatypes corrupted (1)");
                auto cur_len = adsData_impl::adsDT::this_len(p);
                if (cur_len < sizeof(AdsDatatypeEntry) + 3 || cur_len > len)
                    throw std::runtime_error("datatypes corrupted (2)");
                ++dt_count;
                p += cur_len;
                len -= cur_len;
            }
            result.reserve(dt_count);
            for (auto p = &dt_data[0]; dt_count--; ) {
                result.emplace_back(p);
                p += adsData_impl::adsDT::this_len(p);
            }
            std::sort(result.begin(), result.end(), [](auto& lhs, auto& rhs) { return adsData_impl::icmp_less(lhs.name(), rhs.name()); });
            if (std::adjacent_find(result.begin(), result.end(),
                [](auto& lhs, auto& rhs) { return !(adsData_impl::icmp_less(lhs.name(), rhs.name())); }) != result.end())
                throw std::runtime_error("datatypes not unique");
            return result;
        }
    

    Ich habe mir nun eine PLC Projekt angelegt mit nur einer Variable.
    Dein Code wirft immer eine exception mit der Meldung "datatypes corrupted (2)"
    Leider verstehe ich deinen Code noch nicht ganz. Vieleicht könntest du mir ein paar dinge im Code erklären.



  • In der ersten Schleife wird nur gezählt wieviele Beschreibungen von Datentypen vorliegen.
    Die Länge eines einzelnen Eintrags ist dabei variabel - das theoretische Minimum eines Eintrags ist sizeof(AdsDatatypeEntry) + 3 (3 wegen der Terminatoren von 3 Strings), die tatsächliche Größe findet sich in AdsDatatypeEntry::entryLength - adsData_impl::adsDT::this_len ist ein bequemer Weg ohne Casten um darauf zuzugreifen.
    Wenn Zeile 12 also wirft, liegt das daran, dass der gefundene Längeneintrag nicht passt: entweder ist er kleiner als theoretisch möglich, oder er weist über das Ende des Datenblocks (len = die verbleibende Länge) hinaus. Welcher Fall zutrifft, kannst du leicht im Debugger feststellen.



  • Hi camper. Danke für deine Antwort.

    Tu mir immer noch schwer den Code zu verstehen 😕
    Bin schwer dabei zu debuggen. Und anzupassen, da ich noch nicht ganz das raus bekomme was ich brauche.

    Die exception wird doch an einer anderen Stelle geworfen.
    Und zwar kommt er aus check_item_size(const char* data)

    static void check_item_size(const char* data) 
    {
        if (this_len(data) < sizeof(AdsSymbolEntry) + 3)
            throw std::runtime_error("Symbol corrupted (1)");
        if (this_len(data) != checked_sum({ sizeof(AdsSymbolEntry) + 3,
                name_len(data), type_len(data), comment_len(data) }))
             throw std::runtime_error("Symbol corrupted (2)");
    }
    

    da ist das "Symbol corrupted (2)" nochmals drin.



  • Das ist allerdings ein anderere Text 😉
    Auch das hier ist nur ein einfacher Test, um sicherzustellen, dass die gelesenen Daten Sinn ergeben.
    Die Länge eines Symboleintrags ist exakt sizeof(AdsSymbolEntry) + die Längen der Name-, Typ-, Commentstrings + 3 Terminatoren.
    Wenn das nicht übereinstimmt, sind die Daten kaputt - damit weiterarbeiten zu wollen, dürfte früher oder später (eher früher) in einem ungültigen Zugriff auf die falschen Daten führen.
    checked_sum ist eine Bequemlichkeitsfunktion, die einfach alles aufsummiert und wirft, falls zwischendurch Überläufe beobachtet werden (was bei intakten Daten nicht der Fall sein kann).

    P.S. wie sieht denn das PLC Projekt aus?



  • Das ist allerdings ein anderere Text 😉

    *schähm".. ich sag am besten nichts mehr dazu.

    Wie sieht denn das PLC Projekt aus?

    😃

    Ich habe eine leeres PLC Projekt erstellt und nur eine Variable angelegt:

    PROGRAM MAIN
    VAR
        myInt:INT:=10;
    END_VAR
    

    Das ist alles. Nichts desto troz gibt es bereits vom System erstellte Variablen. Die wohl zum Fehler führen.



  • Ich versuche mit ein bisschen TwinCAT3 zu spielen.
    Die Visual Studio Integration ist ja ganz schön, ich habe aber keine Ahnung wie ich hier ein Programm hochlade - PLC/Einloggen liefert stets "Device not in a ready state" ?!



  • Was hast du angelegt? Ein TwinCAT XAE Project (XML format)?



  • booster schrieb:

    Was hast du angelegt? Ein TwinCAT XAE Project (XML format)?

    yep. Dann unter SPS ein Standard PLC Projekt angelegt, das neu erstellt, und jetzt weiss ich nicht weiter.



  • - Gegebenenfalls erst mal unter "TwinCAT\Activate Configuration".

    - 7 Tage Lizenz generieren: SolutionExplorer im Projekt unter SYSTEM\License Doppelklick -> Button "7 Days TrialLicense" dann captcha eingeben.

    - TwinCat\Restart TwinCAT System

    - Wenn TwinCAT System läuft (Logo in Statuszeile muss grün sein und Zahnrad muss sich bewegen) PLC\Login MessageBoxen bestättigen.

    - PLC\Start

    Hinweis: VMWare darf keine laufen.



  • jetzt kommen diese Fehler beim Startversuch:

    ERR | 16.03.2018 14:23:44 730 ms | 'TCOM Server' (10): Gerät 1 (EtherCAT) (Adapter): Failed to connect to network adapater!
    WRN | 16.03.2018 14:23:44 730 ms | 'TCOM Server' (10): PREOP to SAFEOP of 'Gerät 1 (EtherCAT) (Adapter)' (0x03010011) failed - 'request is aborted' 0x9811071F
    ERR | 16.03.2018 14:23:44 733 ms | 'TwinCAT System' (10000): Fehler beim senden des AMS-Kommandos >> Init12\IO: Set State TComObj SAFEOP: Set Objects (4) to SAFEOP >> AdsWarning: 1823 (0x71f, ADS ERROR: device aborted the action) << !
    

    (VirtualBox VM mit Windows 7 x86)

    Sehe gerade, dass C:\TwinCAT\3.1\sdk\Includes eine andere Version von TcAdsDef.h hat, in der AdsSymbolUploadInfo2 fehlt, dafür gibt es dort Ads.h mit einer Menge neuerer Definition, insbesondere sind dort AdsSymbolEntry und AdsDatatypeEntry erweitert, was wahrscheinlich erklärt, wieso mein Code, der mit TwinCAT2 geschrieben und getestet wurde, fehlschlägt.
    Wenn im Test != durch < ersetzt wird, funktioniert es möglicherweise.



  • Vermutlich liegt es an der VM Ware. Oder was mir gerade der Kollege sagt: Man braucht mindestens eine phyisikalische Netzwerkkarte. Das würde auch den Fehlertext etwas erklären.

    Wenn du TwinCat noch gar nicht zum laufen bekommen hast,... dann hast du dein Code noch gar nie getestet. Alles so runter programmiert?

    Woran ich auch noch hänge: AdsVarData liefert mir nur die Informationen der Variable selber. Nicht die der SubVars.

    Wenn ich z.B- eine struct Variable in der PLC habe benötige ich von dieser auch die Information der unterliegenden Variablen.



  • booster schrieb:

    Vermutlich liegt es an der VM Ware. Oder was mir gerade der Kollege sagt: Man braucht mindestens eine phyisikalische Netzwerkkarte. Das würde auch den Fehlertext etwas erklären.

    Ah nein, Ich musste noch den zu verwendenden Adapter unter E/A / Gerät 1/Adapter konfigurieren, paravirtualisiert ist dabei kein Problem.

    booster schrieb:

    Wenn du TwinCat noch gar nicht zum laufen bekommen hast,... dann hast du dein Code noch gar nie getestet. Alles so runter programmiert?

    Wie ich schon mehrfach sagte, ich habe mit TwinCAT2 geschrieben und getestet - das ist nicht zu bescheuert, nach einem Netzwerkaddapter zu fragen, wenn nur einer im System zu finden ist :p

    P.S. Sehe den throw Fehler jetzt auch, hier werden zu den Symbolen offenbar noch zusätzlich GUIDs geliefert.



  • Ah nein, Ich musste noch den zu verwendenden Adapter unter E/A / Gerät 1/Adapter konfigurieren, paravirtualisiert ist dabei kein Problem.

    Keine Ahnung mußte ich bisher noch nicht machen. Bei mir lief das nach Installation direkt.

    Wie ich schon mehrfach sagte, ich habe mit TwinCAT2 geschrieben
    

    Ach ja stimmt. 😃



  • Neue Version mit Iterator-Interface. Ein Framework um zusätzliche Informationen wie Attribute, Enums, Funktionsdaten etc. auszulesen, existiert auch schon; der Aufwand, diee Informationen zugänglich zu machen, ist relativ gering. Ausserdem werden jetzt alle erkannten Daten in eigene Datenstrukturen kopiert, was beim Debuggen hilfreich sein dürfte.

    adsData.h

    #pragma once
    
    #include <cstdint>
    #include <iterator>
    #include <memory>
    #include <string>
    #include <string_view>
    #include <type_traits>
    #include <variant>
    #include <vector>
    
    #define NOMINMAX
    // %TwinCAT3DIR%\sdk\Include
    #include <Ads.h>
    namespace tcAdsAPI {
    // <TcAdsDef.h> defines many of the same types as <Ads.h> so we move these into another namespace,
    // functions in <TcAdsAPI.h> have external "C" linkange so linking is not affected
    #include <TcAdsDef.h>
    #include <TcAdsAPI.h>
    }
    
    enum class adsDatatypeId : std::uint16_t
    {
        void_ = VT_EMPTY,
        int8_ = VT_I1,
        uint8_ = VT_UI1,
        int16_ = VT_I2,
        uint16_ = VT_UI2,
        int32_ = VT_I4,
        uint32_ = VT_UI4,
        int64_ = VT_I8,
        uint64_ = VT_UI8,
        float_ = VT_R4,
        double_ = VT_R8,
        cstring_ = VT_LPSTR,
        wcstring_ = VT_LPWSTR,
        ldouble_ = VT_LPWSTR + 1,
        bool_ = 33,
        bit_ = VT_LPWSTR + 2,
        big_ = VT_BLOB,
    };
    
    class adsVarData;
    
    class adsData {
    public:
        using sizeType = decltype(AdsSymbolEntry::entryLength);
        class symbolInfo;
        class subSymbolInfo;
        class datatypeInfo;
        class iterator;
        using const_iterator = iterator;
        explicit adsData(decltype(AmsAddr::port) port = AMSPORT_R0_PLC_RTS1);
    
        adsData(const adsData&) = delete;
        adsData& operator=(const adsData&) = delete;
    
        adsData(adsData&&) noexcept;
        adsData& operator=(adsData&&) noexcept;
    
        ~adsData() noexcept;
    
        adsVarData operator[](std::string_view name) const;
    
        iterator begin() const noexcept;
        iterator end() const noexcept;
        iterator cbegin() const noexcept;
        iterator cend() const noexcept;
    private:
        void initialize(const std::vector<char>& symData, const std::vector<char>& dtData);
        std::vector<symbolInfo> symbols_;
        std::vector<datatypeInfo> types_;
    };
    
    class adsVarData {
    public:
        using sizeType = adsData::sizeType;
        class iterator;
        using const_iterator = iterator;
    
        std::string        name(std::string prefix) const;
        std::string        shortName() const;
        const std::string& type() const noexcept;
        const std::string& comment() const noexcept;
        adsDatatypeId      typeId() const noexcept;
        sizeType           group() const noexcept;
        sizeType           offset() const noexcept;
        sizeType           size() const noexcept;
        bool               isStatic() const noexcept;
        const GUID*        typeGuid() const noexcept;
    
        bool is_scalar() const noexcept;
        bool is_array() const noexcept;
        bool is_struct() const noexcept;
        bool empty() const noexcept;
        sizeType subElements() const noexcept;
        iterator begin() const noexcept;
        iterator end() const noexcept;
        iterator cbegin() const noexcept;
        iterator cend() const noexcept;
    private:
        friend class adsData;
        friend class adsData::iterator;
        adsVarData(const adsData::subSymbolInfo* info, sizeType group, sizeType offset, sizeType index);
        const adsData::subSymbolInfo* info_;
        sizeType group_;
        sizeType offset_;
        sizeType index_;
    };
    
    class adsData::iterator {
    public:
        using value_type = adsVarData;
        using reference = adsVarData;
        using pointer = adsVarData;
        using difference_type = std::ptrdiff_t;
        using iterator_category = std::input_iterator_tag;
        reference operator*() const noexcept;
        pointer operator->() const noexcept;
        friend bool operator==(iterator lhs, iterator rhs) noexcept;
        friend bool operator!=(iterator lhs, iterator rhs) noexcept;
        iterator& operator++() noexcept;
        iterator operator++(int) noexcept;
    private:
        explicit iterator(std::vector<adsData::symbolInfo>::const_iterator it) noexcept;
        std::vector<adsData::symbolInfo>::const_iterator iter_;
        friend class adsData;
    };
    
    class adsVarData::iterator {
    public:
        using value_type = adsVarData;
        using reference = adsVarData;
        using pointer = adsVarData;
        using difference_type = std::ptrdiff_t;
        using iterator_category = std::input_iterator_tag;
        reference operator*() const noexcept;
        pointer operator->() const noexcept;
        friend bool operator==(const iterator& lhs, const iterator& rhs) noexcept;
        friend bool operator!=(const iterator& lhs, const iterator& rhs) noexcept;
        iterator& operator++() noexcept;
        iterator operator++(int) noexcept;
    private:
        explicit iterator(const adsData::subSymbolInfo* info, sizeType group, sizeType offset, sizeType index) noexcept;
        const adsData::subSymbolInfo* info_;
        sizeType group_;
        sizeType offset_;
        sizeType index_;
        friend class adsVarData;
    };
    


  • adsData.cpp Teil 1

    #include <algorithm>
    #include <cassert>
    #include <cctype>
    #include <cstring>
    #include <optional>
    #include <string>
    #include <string_view>
    #include <stdexcept>
    #include <type_traits>
    #include <vector>
    
    #include "adsData.h"
    
    using namespace std::literals::string_literals;
    using namespace std::literals::string_view_literals;
    
    adsVarData::adsVarData(const adsData::subSymbolInfo* info, sizeType group, sizeType offset, sizeType index = -1)
        : info_{ info }, group_{ group }, offset_{ offset }, index_{ index }
    {}
    
    namespace {
        std::string operator+(std::string lhs, std::string_view rhs) {
            return std::move(lhs.append(rhs.data(), rhs.size()));
        }
        std::string operator+(std::string_view lhs, const std::string& rhs) {
            std::string result;
            result.reserve(lhs.size() + rhs.size());
            result.assign(lhs.data(), lhs.size()).append(rhs);
            return result;
        }
        using sizeType = adsData::sizeType;
    
        inline bool icmp_less(std::string_view lhs, std::string_view rhs) {
            return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
                [](auto x, auto y) { return
                x == '\0' ? y != '\0' : y == '\0' ? false
                : x == '.' ? y != '.' : y == '.' ? false
                : std::tolower(x) < std::tolower(y); });
        }
        inline bool icmp_equal(std::string_view lhs, std::string_view rhs) {
            return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(),
                [](auto x, auto y) { return std::tolower(x) == std::tolower(y); });
        }
    
        template <typename T, std::enable_if_t<std::is_trivial_v<T>, int> = 0>
        T readRaw(const char* p) noexcept {
            T res;
            std::memcpy(&res, p, sizeof res);
            return res;
        }
    
        namespace detail {
    #define ASSERT_THROW_(c) \
    { \
        assert(c); \
        if (!(c)) \
            throw std::runtime_error(#c); \
    }
    #define ASSERT_THROW(c) ASSERT_THROW_(c)
    
            template <typename T> struct properties_of;
    
            enum class ref_prop {
                null,
                typeSize,
                entryLen,
                nameLen,
                typeLen,
                commentLen,
                nameData,
                typeData,
                commentData,
                name,
                type,
                comment,
                nameTerminator,
                typeTerminator,
                commentTerminator,
                group,
                offset,
                size,
                flags,
                hasGuid,
                guid,
                hasAttributes,
                numAttributes,
                attributeData,
                typeId,
                elements,
                lBound,
                valueLen,
                valueData,
                value,
                valueTerminator,
                reserved,
                expEntryLen,
                version,
                arrayDim,
                arrayInfo,
                subItems,
                subItemData,
                hasCopyMask,
                copyMaskLen,
                copyMask,
                hasMethods,
                methods,
                methodData,
                hasEnumInfos,
                enumInfos,
                enums,
                alignSize,
                lengthIsPara,
                vTableIndex,
                paras,
                paraData,
                isStatic
            };
    
            template <ref_prop Name, typename Accessor>
            struct ref_property {
                static constexpr ref_prop name = Name;
                using accessor = Accessor;
            };
    
            // simple property accessors
            template <typename T>
            struct fixed_size {
                static constexpr std::size_t get(const char*, const char*) { return sizeof(T); }
                static void check(const char*, const char*, std::size_t) noexcept {}
            };
            template <typename T>
            struct is_fixed_size : std::false_type {};
            template <typename T>
            struct is_fixed_size<fixed_size<T>> : std::true_type {};
            template <typename T>
            constexpr bool is_fixed_size_v = is_fixed_size<T>::value;
    
            template <typename T, std::size_t disp, typename U = T>
            struct element {
                static U get(const char* p, const char*) noexcept { return static_cast<U>(readRaw<T>(p + disp)); };
                static constexpr std::size_t offset(const char*, const char*) noexcept { return disp; }
                static constexpr std::size_t size(const char*, const char*) noexcept { return sizeof(T); }
                static constexpr std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(offset(p, q) < entryLimit && entryLimit - offset(p, q) >= size(p, q));
                }
            };
            template <typename T, std::size_t disp, T expected>
            struct element_ : element<T, disp> {
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    element<T, disp>::check(p, q, entryLimit);
                    ASSERT_THROW(get(p, q) == expected);
                }
            };
    #define ELEMENT(CLASS, MEMBER) \
        element<std::decay_t<decltype(std::declval<CLASS&>().*&CLASS::MEMBER)>, offsetof(CLASS, MEMBER)>
    #define ELEMENT_TYPE(CLASS, MEMBER, TYPE) \
        element<std::decay_t<decltype(std::declval<CLASS&>().*&CLASS::MEMBER)>, offsetof(CLASS, MEMBER), TYPE>
    #define ELEMENT_EXP(CLASS, MEMBER, EXPECTED) \
        element_<std::decay_t<decltype(std::declval<CLASS&>().*&CLASS::MEMBER)>, offsetof(CLASS, MEMBER), EXPECTED>
    
            // compound property accessors
            template <typename T, ref_prop before, ref_prop expected = ref_prop::null>
            struct next_data {
                static decltype(auto) get(const char* p, const char* q) noexcept { return properties_of<T>::template next_offset<before>(p, q); };
                static void check(const char* p, const char* q, std::size_t) {
                    static_cast<void>(p);
                    static_cast<void>(q);
                    if constexpr (expected != ref_prop::null)
                        ASSERT_THROW(get(p, q) == properties_of<T>::template get<expected>(p, q));
                }
            };
            template <typename T, ref_prop before, typename U>
            struct next_element {
                static U get(const char* p, const char* q) noexcept { return readRaw<U>(p + offset(p, q)); };
                static constexpr std::size_t offset(const char* p, const char* q) noexcept { return properties_of<T>::template next_offset<before>(p, q); }
                static constexpr std::size_t size(const char*, const char*) noexcept { return sizeof(U); }
                static constexpr std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(offset(p, q) < entryLimit && entryLimit - offset(p, q) >= size(p, q));
                }
            };
            template <typename T, ref_prop before, typename U, U expected>
            struct next_element_ : next_element<T, before, U> {
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    next_element<T, before, U>::check(p, q, entryLimit);
                    ASSERT_THROW(get(p, q) == expected);
                }
            };
    
            template <typename T, ref_prop data, ref_prop length>
            struct string {
                static constexpr std::string_view get(const char* p, const char* q) noexcept { return { p + offset(p, q), size(p, q) }; };
                static constexpr std::size_t offset(const char* p, const char* q) noexcept { return properties_of<T>::template get<data>(p, q); }
                static constexpr std::size_t size(const char* p, const char* q) noexcept { return properties_of<T>::template get<length>(p, q); }
                static constexpr std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(offset(p, q) < entryLimit && entryLimit - offset(p, q) >= size(p, q));
                    ASSERT_THROW(get(p, q).find('\0') == std::string_view::npos); // no embedded \0
                }
            };
    
            template <typename T, ref_prop ref, unsigned long long  mask>
            struct flag {
                static constexpr bool get(const char* p, const char* q) noexcept { return (properties_of<T>::template get<ref>(p, q) & mask) != 0; };
                static void check(const char*, const char*, std::size_t) noexcept {}
            };
    
            template <typename T, ref_prop flag, typename Access>
            struct optional {
                static constexpr auto get(const char* p, const char* q) noexcept {
                    using return_type = std::optional<decltype(value(p, q))>;
                    return exists(p, q) ? return_type{ value(p, q) } : return_type{};
                }
                static constexpr auto value(const char* p, const char* q) noexcept { return Access::get(p, q); }
                static constexpr bool exists(const char* p, const char* q) noexcept { return properties_of<T>::template get<flag>(p, q); };
                static constexpr std::size_t offset(const char* p, const char* q) noexcept { return Access::offset(p, q); }
                static constexpr std::size_t size(const char* p, const char* q) noexcept { return exists(p, q) ? Access::size(p, q) : 0; }
                static constexpr std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(exists(p, q) ? offset(p, q) < entryLimit : offset(p, q) <= entryLimit);
                    if (exists(p, q))
                        Access::check(p, q, entryLimit);
                }
            };
            template <typename T, ref_prop flag, typename Access, decltype(Access::get(std::declval<const char*>(), std::declval<const char*>())) default_>
            struct optional_ : optional<T, flag, Access> {
                static constexpr auto get(const char* p, const char* q) noexcept {
                    return exists(p, q) ? value(p, q) : default_;
                }
            };
    
            template <typename T, ref_prop before, ref_prop num, typename Element>
            struct array {
                static constexpr const char* get(const char* p, const char* q) noexcept { return p + offset(p, q); }
                static constexpr std::size_t numElements(const char* p, const char* q) noexcept { return properties_of<T>::template get<num>(p, q); }
                static constexpr std::size_t offset(const char* p, const char* q) noexcept { return properties_of<T>::template next_offset<before>(p, q); }
                static constexpr std::size_t size(const char* p, const char* q) noexcept {
                    if constexpr (is_fixed_size_v<typename properties_of<Element>::template accessor<ref_prop::entryLen>>) {
                        return numElements(p, q) * properties_of<Element>::template get<ref_prop::entryLen>(get(p, q), p);
                    } else {
                        auto first = get(p, q);
                        auto last = first;
                        for (auto n = numElements(p, q); n--; )
                            last += properties_of<Element>::template get<ref_prop::entryLen>(last, p);
                        return static_cast<std::size_t>(last - first);
                    }
                }
                static constexpr std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(offset(p, q) <= entryLimit);
                    q = p;
                    entryLimit -= offset(p, q);
                    p += offset(p, q);
                    for (auto n = numElements(q, q); n--; ) {
                        ASSERT_THROW(sizeof(Element) <= entryLimit);
                        properties_of<Element>::check(p, q, entryLimit);
                        entryLimit -= properties_of<Element>::template get<ref_prop::entryLen>(p, q);
                        p += properties_of<Element>::template get<ref_prop::entryLen>(p, q);
                    }
                }
            };
    
            template <typename T, ref_prop before, typename U, ref_prop size_>
            struct var_int {
                static auto get(const char* p, const char* q) noexcept; // TODO
                static std::size_t offset(const char* p, const char* q) noexcept { return properties_of<T>::template next_offset<before>(p, q); }
                static std::size_t size(const char*, const char* q) noexcept { return properties_of<U>::template get<size_>(q, q); }
                static std::size_t next_offset(const char* p, const char* q) noexcept { return offset(p, q) + size(p, q); }
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    ASSERT_THROW(offset(p, q) < entryLimit && entryLimit - offset(p, q) >= size(p, q));
                }
            };
    
            /////////////////////////////////////////
            struct unmatchedAccessor {
                friend constexpr auto operator^(unmatchedAccessor, unmatchedAccessor) noexcept { return unmatchedAccessor{}; }
                template <typename T>
                friend constexpr auto operator^(unmatchedAccessor, T) noexcept { return T{}; }
                template <typename T>
                friend constexpr auto operator^(T, unmatchedAccessor) noexcept { return T{}; }
            };
    
            template <typename... Properties>
            struct structured_type {
                using type = structured_type<Properties...>;
    
                template <ref_prop name>
                static auto get_accessor() noexcept {
                    return (... ^ std::conditional_t<Properties::name == name, typename Properties::accessor, unmatchedAccessor>{});
                };
                template <ref_prop name>
                using accessor = decltype(get_accessor<name>());
    
                template <ref_prop name> static constexpr decltype(auto) get(const char* p, const char* q)         noexcept { return accessor<name>::get(p, q); }
                template <ref_prop name> static constexpr decltype(auto) offset(const char* p, const char* q)      noexcept { return accessor<name>::offset(p, q); }
                template <ref_prop name> static constexpr decltype(auto) size(const char* p, const char* q)        noexcept { return accessor<name>::size(p, q); }
                template <ref_prop name> static constexpr decltype(auto) next_offset(const char* p, const char* q) noexcept { return accessor<name>::next_offset(p, q); }
                template <ref_prop name> static constexpr decltype(auto) value(const char* p, const char* q)       noexcept { return accessor<name>::value(p, q); }
                template <ref_prop name> static constexpr decltype(auto) exists(const char* p, const char* q)      noexcept { return accessor<name>::exists(p, q); }
                template <ref_prop name> static constexpr decltype(auto) numElements(const char* p, const char* q) noexcept { return accessor<name>::numElements(p, q); }
    
                static void check(const char* p, const char* q, std::size_t entryLimit) {
                    (..., Properties::accessor::check(p, q, entryLimit)); // check all properties in order
                }
            };
    
            template <>
            struct properties_of<ADS_UINT8>
                : structured_type<
                ref_property<ref_prop::entryLen, fixed_size<  ADS_UINT8>>>
            {};
    
            template <>
            struct properties_of<AdsDatatypeArrayInfo>
                : structured_type<
                ref_property<ref_prop::entryLen, fixed_size<  AdsDatatypeArrayInfo>>,
                ref_property<ref_prop::lBound, ELEMENT(AdsDatatypeArrayInfo, lBound)>,
                ref_property<ref_prop::elements, ELEMENT(AdsDatatypeArrayInfo, elements)>>
            {};
    
            template <>
            struct properties_of<AdsSymbolEntry>
                : structured_type<
                ref_property<ref_prop::entryLen, ELEMENT(AdsSymbolEntry, entryLength)>,
                ref_property<ref_prop::group, ELEMENT(AdsSymbolEntry, iGroup)>,
                ref_property<ref_prop::offset, ELEMENT(AdsSymbolEntry, iOffs)>,
                ref_property<ref_prop::size, ELEMENT(AdsSymbolEntry, size)>,
                ref_property<ref_prop::typeId, ELEMENT_TYPE(AdsSymbolEntry, dataType, adsDatatypeId)>,
                ref_property<ref_prop::flags, ELEMENT(AdsSymbolEntry, flags)>,
                ref_property<ref_prop::reserved, ELEMENT_EXP(AdsSymbolEntry, reserved, 0)>,
                ref_property<ref_prop::nameLen, ELEMENT(AdsSymbolEntry, nameLength)>,
                ref_property<ref_prop::typeLen, ELEMENT(AdsSymbolEntry, typeLength)>,
                ref_property<ref_prop::commentLen, ELEMENT(AdsSymbolEntry, commentLength)>,
                ref_property<ref_prop::nameData, next_data<AdsSymbolEntry, ref_prop::commentLen>>,
                ref_property<ref_prop::name, string<AdsSymbolEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsSymbolEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::typeData, next_data<AdsSymbolEntry, ref_prop::nameTerminator>>,
                ref_property<ref_prop::type, string<AdsSymbolEntry, ref_prop::typeData, ref_prop::typeLen>>,
                ref_property<ref_prop::typeTerminator, next_element_<AdsSymbolEntry, ref_prop::type, char, '\0'>>,
                ref_property<ref_prop::commentData, next_data<AdsSymbolEntry, ref_prop::typeTerminator>>,
                ref_property<ref_prop::comment, string<AdsSymbolEntry, ref_prop::commentData, ref_prop::commentLen>>,
                ref_property<ref_prop::commentTerminator, next_element_<AdsSymbolEntry, ref_prop::comment, char, '\0'>>,
                ref_property<ref_prop::hasGuid, flag<AdsSymbolEntry, ref_prop::flags, ADSSYMBOLFLAG_TYPEGUID>>,
                ref_property<ref_prop::guid, optional<AdsSymbolEntry, ref_prop::hasGuid, next_element<AdsSymbolEntry, ref_prop::commentTerminator, GUID>>>,
                ref_property<ref_prop::hasAttributes, flag<AdsSymbolEntry, ref_prop::flags, ADSSYMBOLFLAG_ATTRIBUTES>>,
                ref_property<ref_prop::numAttributes, optional_<AdsSymbolEntry, ref_prop::hasAttributes, next_element<AdsSymbolEntry, ref_prop::guid, ADS_UINT16>, 0>>,
                ref_property<ref_prop::attributeData, array<AdsSymbolEntry, ref_prop::numAttributes, ref_prop::numAttributes, AdsAttributeEntry>>,
    //            ref_property<ref_prop::expEntryLen, next_data<AdsSymbolEntry, ref_prop::attributeData, ref_prop::entryLen>>,
                ref_property<ref_prop::isStatic, flag<AdsSymbolEntry, ref_prop::flags, ADSSYMBOLFLAG_STATIC>>>
            {
                static constexpr ref_prop sort_prop = ref_prop::name;
                static constexpr auto cmp_less = icmp_less;
                static constexpr auto cmp_equal = icmp_equal;
            };
    
            template <>
            struct properties_of<AdsAttributeEntry>
                : structured_type<
                ref_property<ref_prop::nameLen, ELEMENT(AdsAttributeEntry, nameLength)>,
                ref_property<ref_prop::valueLen, ELEMENT(AdsAttributeEntry, valueLength)>,
                ref_property<ref_prop::nameData, next_data<AdsAttributeEntry, ref_prop::valueLen>>,
                ref_property<ref_prop::name, string<AdsAttributeEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsAttributeEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::valueData, next_data<AdsAttributeEntry, ref_prop::nameTerminator>>,
                ref_property<ref_prop::value, string<AdsAttributeEntry, ref_prop::valueData, ref_prop::valueLen>>,
                ref_property<ref_prop::valueTerminator, next_element_<AdsAttributeEntry, ref_prop::value, char, '\0'>>,
                ref_property<ref_prop::entryLen, next_data<AdsAttributeEntry, ref_prop::valueTerminator>>>
            {};
    
            template <>
            struct properties_of<AdsDatatypeEntry>
                : structured_type<
                ref_property<ref_prop::typeSize, fixed_size<  AdsDatatypeEntry>>,
                ref_property<ref_prop::entryLen, ELEMENT(AdsDatatypeEntry, entryLength)>,
                ref_property<ref_prop::version, ELEMENT_EXP(AdsDatatypeEntry, version, 1)>,
                ref_property<ref_prop::size, ELEMENT(AdsDatatypeEntry, size)>,
                ref_property<ref_prop::offset, ELEMENT(AdsDatatypeEntry, offs)>,
                ref_property<ref_prop::typeId, ELEMENT_TYPE(AdsDatatypeEntry, dataType, adsDatatypeId)>,
                ref_property<ref_prop::flags, ELEMENT(AdsDatatypeEntry, flags)>,
                ref_property<ref_prop::nameLen, ELEMENT(AdsDatatypeEntry, nameLength)>,
                ref_property<ref_prop::typeLen, ELEMENT(AdsDatatypeEntry, typeLength)>,
                ref_property<ref_prop::commentLen, ELEMENT(AdsDatatypeEntry, commentLength)>,
                ref_property<ref_prop::arrayDim, ELEMENT(AdsDatatypeEntry, arrayDim)>,
                ref_property<ref_prop::subItems, ELEMENT(AdsDatatypeEntry, subItems)>,
                ref_property<ref_prop::nameData, next_data<AdsDatatypeEntry, ref_prop::subItems>>,
                ref_property<ref_prop::name, string<AdsDatatypeEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsDatatypeEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::typeData, next_data<AdsDatatypeEntry, ref_prop::nameTerminator>>,
                ref_property<ref_prop::type, string<AdsDatatypeEntry, ref_prop::typeData, ref_prop::typeLen>>,
                ref_property<ref_prop::typeTerminator, next_element_<AdsDatatypeEntry, ref_prop::type, char, '\0'>>,
                ref_property<ref_prop::commentData, next_data<AdsDatatypeEntry, ref_prop::typeTerminator>>,
                ref_property<ref_prop::comment, string<AdsDatatypeEntry, ref_prop::commentData, ref_prop::commentLen>>,
                ref_property<ref_prop::commentTerminator, next_element_<AdsDatatypeEntry, ref_prop::comment, char, '\0'>>,
                ref_property<ref_prop::arrayInfo, array<AdsDatatypeEntry, ref_prop::commentTerminator, ref_prop::arrayDim, AdsDatatypeArrayInfo>>,
                ref_property<ref_prop::subItemData, array<AdsDatatypeEntry, ref_prop::arrayInfo, ref_prop::subItems, AdsDatatypeEntry>>,
                ref_property<ref_prop::hasGuid, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_TYPEGUID>>,
                ref_property<ref_prop::guid, optional<AdsDatatypeEntry, ref_prop::hasGuid, next_element<AdsDatatypeEntry, ref_prop::subItemData, GUID>>>,
                ref_property<ref_prop::hasCopyMask, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_COPYMASK>>,
                ref_property<ref_prop::copyMaskLen, optional_<AdsDatatypeEntry, ref_prop::hasCopyMask, ELEMENT(AdsDatatypeEntry, size), 0>>,
                ref_property<ref_prop::copyMask, array<AdsDatatypeEntry, ref_prop::guid, ref_prop::copyMaskLen, ADS_UINT8>>,
                ref_property<ref_prop::hasMethods, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_METHODINFOS>>,
                ref_property<ref_prop::methods, optional_<AdsDatatypeEntry, ref_prop::hasMethods, next_element<AdsDatatypeEntry, ref_prop::copyMask, ADS_UINT16>, 0>>,
                ref_property<ref_prop::methodData, array<AdsDatatypeEntry, ref_prop::methods, ref_prop::methods, AdsMethodEntry>>,
                ref_property<ref_prop::hasAttributes, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_ATTRIBUTES>>,
                ref_property<ref_prop::numAttributes, optional_<AdsDatatypeEntry, ref_prop::hasAttributes, next_element<AdsDatatypeEntry, ref_prop::methodData, ADS_UINT16>, 0>>,
                ref_property<ref_prop::attributeData, array<AdsDatatypeEntry, ref_prop::numAttributes, ref_prop::numAttributes, AdsAttributeEntry>>,
                ref_property<ref_prop::hasEnumInfos, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_ENUMINFOS>>,
                ref_property<ref_prop::enumInfos, optional_<AdsDatatypeEntry, ref_prop::hasEnumInfos, next_element<AdsDatatypeEntry, ref_prop::attributeData, ADS_UINT16>, 0>>,
                ref_property<ref_prop::enums, array<AdsDatatypeEntry, ref_prop::enumInfos, ref_prop::enumInfos, AdsEnumInfoEntry>>,
    //            ref_property<ref_prop::expEntryLen, next_data<AdsDatatypeEntry, ref_prop::enums, ref_prop::entryLen>>,
                ref_property<ref_prop::isStatic, flag<AdsDatatypeEntry, ref_prop::flags, ADSDATATYPEFLAG_STATIC>>>
            {
                static constexpr ref_prop sort_prop = ref_prop::name;
                static constexpr auto cmp_less = std::less<>{};
                static constexpr auto cmp_equal = std::equal_to<>{};
            };
    
            template <>
            struct properties_of<AdsMethodParaEntry>
                : structured_type<
                ref_property<ref_prop::entryLen, ELEMENT(AdsMethodParaEntry, entryLength)>,
                ref_property<ref_prop::size, ELEMENT(AdsMethodParaEntry, size)>,
                ref_property<ref_prop::alignSize, ELEMENT(AdsMethodParaEntry, alignSize)>,
                ref_property<ref_prop::typeId, ELEMENT_TYPE(AdsMethodParaEntry, dataType, adsDatatypeId)>,
                ref_property<ref_prop::flags, ELEMENT(AdsMethodParaEntry, flags)>,
                ref_property<ref_prop::reserved, ELEMENT_EXP(AdsMethodParaEntry, reserved, 0)>,
                ref_property<ref_prop::guid, ELEMENT(AdsMethodParaEntry, typeGuid)>,
                ref_property<ref_prop::lengthIsPara, ELEMENT(AdsMethodParaEntry, lengthIsPara)>,
                ref_property<ref_prop::nameLen, ELEMENT(AdsMethodParaEntry, nameLength)>,
                ref_property<ref_prop::typeLen, ELEMENT(AdsMethodParaEntry, typeLength)>,
                ref_property<ref_prop::commentLen, ELEMENT(AdsMethodParaEntry, commentLength)>,
                ref_property<ref_prop::nameData, next_data<AdsMethodParaEntry, ref_prop::commentLen>>,
                ref_property<ref_prop::name, string<AdsMethodParaEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsMethodParaEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::typeData, next_data<AdsMethodParaEntry, ref_prop::nameTerminator>>,
                ref_property<ref_prop::type, string<AdsMethodParaEntry, ref_prop::typeData, ref_prop::typeLen>>,
                ref_property<ref_prop::typeTerminator, next_element_<AdsMethodParaEntry, ref_prop::type, char, '\0'>>,
                ref_property<ref_prop::commentData, next_data<AdsMethodParaEntry, ref_prop::typeTerminator>>,
                ref_property<ref_prop::comment, string<AdsMethodParaEntry, ref_prop::commentData, ref_prop::commentLen>>,
                ref_property<ref_prop::commentTerminator, next_element_<AdsMethodParaEntry, ref_prop::comment, char, '\0'>>,
                ref_property<ref_prop::expEntryLen, next_data<AdsMethodParaEntry, ref_prop::commentTerminator, ref_prop::entryLen>>>
            {};
    
            template <>
            struct properties_of<AdsMethodEntry>
                : structured_type<
                ref_property<ref_prop::entryLen, ELEMENT(AdsMethodEntry, entryLength)>,
                ref_property<ref_prop::version, ELEMENT_EXP(AdsMethodEntry, version, 1)>,
                ref_property<ref_prop::vTableIndex, ELEMENT(AdsMethodEntry, vTableIndex)>,
                ref_property<ref_prop::size, ELEMENT(AdsMethodEntry, returnSize)>,
                ref_property<ref_prop::alignSize, ELEMENT(AdsMethodEntry, returnAlignSize)>,
                ref_property<ref_prop::reserved, ELEMENT_EXP(AdsMethodEntry, reserved, 0)>,
                ref_property<ref_prop::guid, ELEMENT(AdsMethodEntry, returnTypeGuid)>,
                ref_property<ref_prop::typeId, ELEMENT_TYPE(AdsMethodEntry, returnDataType, adsDatatypeId)>,
                ref_property<ref_prop::flags, ELEMENT(AdsMethodEntry, flags)>,
                ref_property<ref_prop::nameLen, ELEMENT(AdsMethodEntry, nameLength)>,
                ref_property<ref_prop::typeLen, ELEMENT(AdsMethodEntry, returnTypeLength)>,
                ref_property<ref_prop::commentLen, ELEMENT(AdsMethodEntry, commentLength)>,
                ref_property<ref_prop::paras, ELEMENT(AdsMethodEntry, paras)>,
                ref_property<ref_prop::nameData, next_data<AdsMethodEntry, ref_prop::paras>>,
                ref_property<ref_prop::name, string<AdsMethodEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsMethodEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::typeData, next_data<AdsMethodEntry, ref_prop::nameTerminator>>,
                ref_property<ref_prop::type, string<AdsMethodEntry, ref_prop::typeData, ref_prop::typeLen>>,
                ref_property<ref_prop::typeTerminator, next_element_<AdsMethodEntry, ref_prop::type, char, '\0'>>,
                ref_property<ref_prop::commentData, next_data<AdsMethodEntry, ref_prop::typeTerminator>>,
                ref_property<ref_prop::comment, string<AdsMethodEntry, ref_prop::commentData, ref_prop::commentLen>>,
                ref_property<ref_prop::commentTerminator, next_element_<AdsMethodEntry, ref_prop::comment, char, '\0'>>,
                ref_property<ref_prop::paraData, array<AdsMethodEntry, ref_prop::commentTerminator, ref_prop::paras, AdsMethodParaEntry>>,
                ref_property<ref_prop::expEntryLen, next_data<AdsMethodEntry, ref_prop::paraData, ref_prop::entryLen>>>
            {};
    
            template <>
            struct properties_of<AdsEnumInfoEntry>
                : structured_type<
                ref_property<ref_prop::nameLen, ELEMENT(AdsEnumInfoEntry, nameLength)>,
                ref_property<ref_prop::nameData, next_data<AdsEnumInfoEntry, ref_prop::nameLen>>,
                ref_property<ref_prop::name, string<AdsEnumInfoEntry, ref_prop::nameData, ref_prop::nameLen>>,
                ref_property<ref_prop::nameTerminator, next_element_<AdsEnumInfoEntry, ref_prop::name, char, '\0'>>,
                ref_property<ref_prop::value, var_int<AdsEnumInfoEntry, ref_prop::nameTerminator, AdsDatatypeEntry, ref_prop::size>>,
                ref_property<ref_prop::entryLen, next_data<AdsEnumInfoEntry, ref_prop::value>>>
            {};
    
            template <typename T>
            class ref {
            public:
                constexpr explicit ref(const char* p) noexcept : data(p) {}
                ref(const char* p, std::size_t entryLimit) : data(p) {
                    properties_of<T>::check(p, nullptr, entryLimit);
                }
                template <ref_prop prop>
                auto get() const noexcept {
                    return get<prop>(data);
                }
                template <ref_prop prop>
                static decltype(auto) get(const char*p) noexcept {
                    return properties_of<T>::template get<prop>(p, nullptr);
                }
            private:
                const char* data;
    
                friend bool operator<(const ref& lhs, const ref& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_less(lhs.get<prop::sort_prop>(), rhs.get<prop::sort_prop>());
                }
                template <typename U>
                friend bool operator<(const U& lhs, const ref& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_less(lhs, rhs.get<prop::sort_prop>());
                }
                template <typename U>
                friend bool operator<(const ref& lhs, const U& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_less(lhs.get<prop::sort_prop>(), rhs);
                }
                friend bool operator==(const ref& lhs, const ref& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_equal(lhs.get<prop::sort_prop>(), rhs.get<prop::sort_prop>());
                }
                template <typename U>
                friend bool operator==(const U& lhs, const ref& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_equal(lhs, rhs.get<prop::sort_prop>());
                }
                template <typename U>
                friend bool operator==(const ref& lhs, const U& rhs) noexcept {
                    using prop = properties_of<T>;
                    return prop::cmp_equal(lhs.get<prop::sort_prop>(), rhs);
                }
            };
        } // namespace detail
        using detail::ref;
        using detail::ref_prop;
    
        inline std::size_t checkedSum(std::initializer_list<std::size_t> args) {
            std::size_t result = 0;
            for (auto arg : args) {
                auto sum = result + arg;
                if (sum < result)
                    throw std::overflow_error("data corrupted: length");
                result = sum;
            }
            return result;
        }
    
        using std::rbegin;
        using std::rend;
    
        template <typename T>
        struct reverse_view { T& iterable; };
    
        template <typename T>
        auto begin(reverse_view<T> w) { return rbegin(w.iterable); }
    
        template <typename T>
        auto end(reverse_view<T> w) { return rend(w.iterable); }
    
        template <typename T>
        reverse_view<T> reverse(T&& iterable) { return { iterable }; }
    }
    
    class adsData::subSymbolInfo {
    public:
        subSymbolInfo(std::string_view name, std::string_view comment,
            sizeType offset, bool isStatic,
            adsData::datatypeInfo* typeData = nullptr)
            : name{ name }, comment{ comment }, offset{ offset },
              isStatic { isStatic }, typeData{ typeData }
        {}
        std::string name;
        std::string comment;
        sizeType offset;
        bool isStatic;
        adsData::datatypeInfo* typeData;
        friend bool operator==(const subSymbolInfo& lhs, const subSymbolInfo& rhs) {
            return icmp_equal(lhs.name, rhs.name);
        }
        friend bool operator==(const subSymbolInfo& lhs, std::string_view rhs) {
            return icmp_equal(lhs.name, rhs);
        }
        friend bool operator==(std::string_view lhs, const subSymbolInfo& rhs) {
            return icmp_equal(lhs, rhs.name);
        }
    };
    
    namespace {
    
        struct adsArrayInfo : adsData::subSymbolInfo
        {
            adsArrayInfo(std::string_view comment, sizeType arraySize, sizeType arrayDims, const char* arrayInfoData)
                : adsData::subSymbolInfo{ ""sv, comment, 0, false }, arrayInfo{}, numElements_{ 1 }
            {
                assert(arrayDims != 0);
                arrayInfo.reserve(arrayDims);
                for (; arrayDims--; ) {
                    ref<AdsDatatypeArrayInfo> ref(arrayInfoData);
                    auto& info = arrayInfo.emplace_back(AdsDatatypeArrayInfo{ ref.get<ref_prop::lBound>(), ref.get<ref_prop::elements>() });
                    sizeType rBound = info.lBound + info.elements - 1;
                    if (info.elements == 0 || rBound < info.lBound || arraySize % info.elements != 0)
                        throw std::runtime_error("array info corrupted");
                    arraySize /= info.elements;
                    numElements_ *= info.elements;
                    arrayInfoData += ref.get<ref_prop::entryLen>();
                }
            }
            sizeType index(std::string_view i) const {
                auto workIndex = i;
                sizeType realIndex = 0;
                for (auto& info : arrayInfo) {
                    auto pos = workIndex.find(',');
                    if ((&info == &arrayInfo.back()) == (pos == workIndex.npos))
                        throw std::out_of_range("index with wrong number of dimensions: "s + i);
                    auto curIndex = workIndex;
                    if (pos != workIndex.npos) {
                        curIndex.remove_suffix(curIndex.size() - pos);
                        workIndex.remove_prefix(pos + 1);
                    }
                    auto n = svtoi(curIndex);
                    if (n < info.lBound || n - info.lBound >= info.elements)
                        throw std::out_of_range("index out of range: "s + i);
                    // we don't need to check for overflow here since the constructor already ensures that 
                    // indizes stay within proper bounds
                    realIndex = realIndex * info.elements + (n - info.lBound);
                }
                return realIndex;
            }
            std::string toString(sizeType i) const {
                if (i >= numElements_)
                    throw std::out_of_range("index out of range");
                std::string result = "]";
                for (auto& info : reverse(arrayInfo)) {
                    auto curIndex = i % info.elements;
                    i /= info.elements;
                    do {
                        result.push_back(static_cast<char>('0' + curIndex % 10));
                    } while (curIndex /= 10);
                    if (&info != &arrayInfo.front())
                        result.push_back(',');
                }
                result.push_back('[');
                std::reverse(result.begin(), result.end());
                return result;
            }
            sizeType elemSize() const noexcept;
            sizeType numElements() const noexcept { return numElements_; }
            static sizeType svtoi(std::string_view s) {
                sizeType result = 0;
                if (s.empty())
                    throw std::out_of_range("index corrupted");
                for (; !s.empty(); s.remove_prefix(1)) {
                    if (s[0] < '0' || s[0] > '9' || (std::numeric_limits<sizeType>::max() - (s[0] - '0')) / 10 < result)
                        throw std::out_of_range("index corrupted");
                    result = result * 10 + (s[0] - '0');
                }
                return result;
            }
            std::vector<AdsDatatypeArrayInfo> arrayInfo;
            sizeType numElements_;
        };
    }
    
    class adsData::symbolInfo {
    public:
        symbolInfo(std::string_view name, std::string_view comment,
            sizeType offset, sizeType group, bool isStatic, datatypeInfo* typeData )
            : baseInfo{ name, comment, offset, isStatic, typeData }, group{ group }
        {}
        subSymbolInfo baseInfo;
        sizeType group;
        friend bool operator<(const symbolInfo& lhs, const symbolInfo& rhs) {
            return icmp_less(lhs.baseInfo.name, rhs.baseInfo.name);
        }
        friend bool operator<(const symbolInfo& lhs, std::string_view rhs) {
            return icmp_less(lhs.baseInfo.name, rhs);
        }
        friend bool operator<(std::string_view lhs, const symbolInfo& rhs) {
            return icmp_less(lhs, rhs.baseInfo.name);
        }
    };
    class adsData::datatypeInfo {
    public:
        datatypeInfo(std::string_view name, sizeType size, adsDatatypeId id, std::optional<GUID> guid)
            : name{ name }, size{ size }, guid{ guid }, id { id }, visited(false),
              typeSpecs{}
        {}
        datatypeInfo(std::string_view name, sizeType size, adsDatatypeId id, std::optional<GUID> guid,
            datatypeInfo* info)
            : name{ name }, size{ size }, guid{ guid }, id{ id }, visited(false),
              typeSpecs{ info }
        {}
        datatypeInfo(std::string_view name, sizeType size, adsDatatypeId id, std::optional<GUID> guid,
            adsArrayInfo&& arrayInfo)
            : name{ name }, size{ size }, guid{ guid }, id{ id }, visited(false),
              typeSpecs{ std::move(arrayInfo) }
        {}
        datatypeInfo(std::string_view name, sizeType size, adsDatatypeId id, std::optional<GUID> guid,
            sizeType numSymbols)
            : name{ name }, size{ size }, guid{ guid }, id{ id }, visited(false),
              typeSpecs{ std::vector<subSymbolInfo>{} }
        {
            std::get<std::vector<subSymbolInfo>>(typeSpecs).reserve(numSymbols);
        }
        std::string name;
        sizeType size;
        std::optional<GUID> guid;
        adsDatatypeId id;
        bool visited; // used for cycle detection
        enum { baseSpec, compoundSpec, arraySpec, classSpec };
        std::variant<std::monostate,                         // base type
                     adsData::datatypeInfo*,                 // compound type
                     adsArrayInfo,                           // array
                     std::vector<subSymbolInfo>> typeSpecs;  // class
    
        friend bool operator<(const adsData::datatypeInfo& lhs, const adsData::datatypeInfo& rhs) {
            return lhs.name < rhs.name;
        }
        friend bool operator<(const adsData::datatypeInfo& lhs, std::string_view rhs) {
            return lhs.name < rhs;
        }
        friend bool operator<(std::string_view lhs, const adsData::datatypeInfo& rhs) {
            return lhs < rhs.name;
        }
    };
    namespace {
        sizeType adsArrayInfo::elemSize() const noexcept { return typeData->size; }
    
        using adsDT = ref<AdsDatatypeEntry>;
        using adsSym = ref<AdsSymbolEntry>;
    
        template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
        std::string to_string_hex(T v) {
            auto val = static_cast<std::uint64_t>(v);
            char buf[2 * sizeof(val) + 1];
            std::snprintf(buf, sizeof(buf), "%llx", val);
            return buf;
        }
    
        const adsDT* find(const std::vector<adsDT>& data, std::string_view name) noexcept {
            auto it = std::lower_bound(data.begin(), data.end(), name);
            return it != data.end() && it->get<ref_prop::name>() == name ? &*it : nullptr;
        }
        const adsSym* find(const std::vector<adsSym>& data, std::string_view name) noexcept {
            auto it = std::upper_bound(data.begin(), data.end(), name);
            // prev is a match if it is a proper prefix of name
            return it != data.begin() && (--it)->get<ref_prop::nameLen>() <= name.size()
                && icmp_equal(it->get<ref_prop::name>(), std::string_view{ 0, it->get<ref_prop::nameLen>() }) ? &*it : nullptr;
        }
    
        void addDatatypes(const std::vector<char>& dtData, std::vector<adsData::datatypeInfo>& types) {
            auto limit = dtData.size();
            std::size_t count = 0;
            for (auto p = &dtData[0]; limit != 0; ) {
                adsDT cur(p, limit);
                ++count;
                p += cur.get<ref_prop::entryLen>();
                limit -= cur.get<ref_prop::entryLen>();
            }
            types.reserve(count);
            for (auto p = &dtData[0]; count--; ) {
                adsDT dt(p);
                p += dt.get<ref_prop::entryLen>();
                if (dt.get<ref_prop::arrayDim>() != 0) {
                    types.emplace_back(dt.get<ref_prop::name>(), dt.get<ref_prop::size>(), dt.get<ref_prop::typeId>(), dt.get<ref_prop::guid>(),
                        adsArrayInfo{ dt.get<ref_prop::comment>(), dt.get<ref_prop::size>(), dt.get<ref_prop::arrayDim>(), dt.get<ref_prop::arrayInfo>() });
                } else if (dt.get<ref_prop::subItems>() != 0) {
                    auto& ref = types.emplace_back(dt.get<ref_prop::name>(), dt.get<ref_prop::size>(), dt.get<ref_prop::typeId>(),
                        dt.get<ref_prop::guid>(), dt.get<ref_prop::subItems>());
                    auto& subs = std::get<adsData::datatypeInfo::classSpec>(ref.typeSpecs);
                    auto q = dt.get<ref_prop::subItemData>();
                    for (auto n = dt.get<ref_prop::subItems>(); n--;) {
                        adsDT sub(q);
                        q += sub.get<ref_prop::entryLen>();
                        subs.emplace_back(sub.get<ref_prop::name>(), sub.get<ref_prop::comment>(), sub.get<ref_prop::offset>(),
                            sub.get<ref_prop::isStatic>());
                    }
                } else if (dt.get<ref_prop::type>().empty()) {
                    types.emplace_back(dt.get<ref_prop::name>(), dt.get<ref_prop::size>(), dt.get<ref_prop::typeId>(), dt.get<ref_prop::guid>());
                } else {
                    types.emplace_back(dt.get<ref_prop::name>(), dt.get<ref_prop::size>(), dt.get<ref_prop::typeId>(), dt.get<ref_prop::guid>(), nullptr );
                }
            }
            std::sort(types.begin(), types.end());
        }
        void linkDatatypes(const std::vector<char>& dtData, std::vector<adsData::datatypeInfo>& types) {
            auto p = &dtData[0];
            for (auto count = types.size(); count--; ) {
                adsDT dt(p);
                p += dt.get<ref_prop::entryLen>();
                auto type = std::lower_bound(types.begin(), types.end(), dt.get<ref_prop::name>());
                assert(type != types.end());
                switch (type->typeSpecs.index()) {
                    default:
                        assert(false);
                    case adsData::datatypeInfo::baseSpec:
                        break;
                    case adsData::datatypeInfo::compoundSpec: {
                        auto it = std::lower_bound(types.begin(), types.end(), dt.get<ref_prop::type>());
                        if (it == types.end())
                            throw std::runtime_error("Datytypes corrupted: missing type data");
                        std::get<adsData::datatypeInfo::compoundSpec>(type->typeSpecs) = &*it;
                        break;
                    }
                    case adsData::datatypeInfo::arraySpec: {
                        auto it = std::lower_bound(types.begin(), types.end(), dt.get<ref_prop::type>());
                        if (it == types.end())
                            throw std::runtime_error("Datytypes corrupted: missing type data");
                        auto& info = std::get<adsData::datatypeInfo::arraySpec>(type->typeSpecs);
                        if (dt.get<ref_prop::size>() / info.numElements() != it->size)
                            throw std::runtime_error("Datatypes corrupted: mismatched element size");
                        info.typeData = &*it;
                        break;
                    }
                    case adsData::datatypeInfo::classSpec: {
                        const char* q = dt.get<ref_prop::subItemData>();
                        for (auto& info : std::get<adsData::datatypeInfo::classSpec>(type->typeSpecs)) {
                            adsDT sub(q);
                            q += sub.get<ref_prop::entryLen>();
                            auto it = std::lower_bound(types.begin(), types.end(), sub.get<ref_prop::type>());
                            if (it == types.end())
                                throw std::runtime_error("Datytypes corrupted: missing type data");
                            if (sub.get<ref_prop::size>() != it->size)
                                throw std::runtime_error("Datatypes corrupted: alias mismatched size");
                            info.typeData = &*it;
                        }
                        break;
                    }
                }
            }
        }
        void addSymbols(const std::vector<char>& symData, std::vector<adsData::datatypeInfo>& types,
            std::vector<adsData::symbolInfo>& symbols) {
            auto limit = symData.size();
            std::size_t count = 0;
            for (auto p = &symData[0]; limit != 0; ) {
                adsSym cur(p, limit);
                ++count;
                p += cur.get<ref_prop::entryLen>();
                limit -= cur.get<ref_prop::entryLen>();
            }
            symbols.reserve(symData.size());
            for (auto p = &symData[0]; count--; ) {
                adsSym sym(p);
                p += sym.get<ref_prop::entryLen>();
                if (sym.get<ref_prop::type>().empty())
                    throw std::runtime_error("Symboldata corrupted: symbol has no type");
                auto it = std::lower_bound(types.begin(), types.end(), sym.get<ref_prop::type>());
                if (it == types.end())
                    throw std::runtime_error("Symboldata corrupted: missing type data");
                if (sym.get<ref_prop::size>() != it->size)
                    throw std::runtime_error("Symboldata corrupted: mismatched size");
                if (sym.get<ref_prop::guid>() != it->guid)
                    throw std::runtime_error("mismatched guid");
                symbols.emplace_back(sym.get<ref_prop::name>(), sym.get<ref_prop::comment>(), sym.get<ref_prop::offset>(),
                    sym.get<ref_prop::group>(), sym.get<ref_prop::isStatic>(), &*it);
            }
            std::sort(symbols.begin(), symbols.end());
        }
        void cycleCheck(adsData::datatypeInfo* p) {
            if (p->visited)
                throw std::runtime_error("Datatypes corrupted: cycle detected");
            switch (p->typeSpecs.index()) {
                default:
                    assert(false);
                case adsData::datatypeInfo::baseSpec:
                    return;
                case adsData::datatypeInfo::compoundSpec:
                    p->visited = true;
                    cycleCheck(std::get<adsData::datatypeInfo::compoundSpec>(p->typeSpecs));
                    p->visited = false;
                    return;
                case adsData::datatypeInfo::arraySpec: {
                    auto& info = std::get<adsData::datatypeInfo::arraySpec>(p->typeSpecs);
                    if (info.numElements() != 1)
                        return;
                    p->visited = true;
                    cycleCheck(info.typeData);
                    p->visited = false;
                    return;
                }
                case adsData::datatypeInfo::classSpec: {
                    auto& info = std::get<adsData::datatypeInfo::classSpec>(p->typeSpecs);
                    p->visited = true;
                    for (auto& sub : info)
                        if (sub.typeData->size == p->size)
                            cycleCheck(sub.typeData);
                    p->visited = false;
                    return;
                }
            }
        }
        void cycleCheck(std::vector<adsData::datatypeInfo>& types) {
            for (auto& info : types)
                cycleCheck(&info);
        }
    } // namespace
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    adsData::adsData(decltype(AmsAddr::port) port)
        : symbols_{}, types_{}
    {
        auto adsPort = tcAdsAPI::AdsPortOpenEx();
        if (adsPort == 0)
            throw std::runtime_error("cannot open ads port");
        std::unique_ptr<decltype(adsPort), void(*)(decltype(adsPort)*)> guard(&adsPort,
            [](decltype(adsPort)* p) { tcAdsAPI::AdsPortCloseEx(*p); });
        tcAdsAPI::AmsAddr ams_addr{};
        if (auto err = tcAdsAPI::AdsGetLocalAddressEx(adsPort, &ams_addr); err != ADSERR_NOERR)
            throw std::runtime_error("cannot get local ams address: 0x" + to_string_hex(err));
        ams_addr.port = port;
        AdsSymbolUploadInfo2 info{};
        unsigned long bytes_read = 0;
        if (auto err = tcAdsAPI::AdsSyncReadReqEx2(adsPort, &ams_addr, ADSIGRP_SYM_UPLOADINFO2, 0, sizeof(info),
            &info, &bytes_read); err != ADSERR_NOERR)
            throw std::runtime_error("cannot read symbol upload info: 0x" + to_string_hex(err));
        if (bytes_read != sizeof(info))
            throw std::runtime_error("error reading sym_uploadinfo2: " + std::to_string(bytes_read) + " bytes read");
        std::vector<char> symData(info.nSymSize);
        std::vector<char> dtData(info.nDatatypeSize);
        if (auto err = tcAdsAPI::AdsSyncReadReqEx2(adsPort, &ams_addr, ADSIGRP_SYM_UPLOAD, 0, info.nSymSize,
            &symData[0], &bytes_read); err != ADSERR_NOERR)
            throw std::runtime_error("cannot read symbol info: 0x" + to_string_hex(err));
        if (bytes_read != info.nSymSize)
            throw std::runtime_error("error reading symbols: " + std::to_string(bytes_read) + " bytes read, "
                + std::to_string(info.nSymSize) + " expected");
        if (auto err = tcAdsAPI::AdsSyncReadReqEx2(adsPort, &ams_addr, ADSIGRP_SYM_DT_UPLOAD, 0, info.nDatatypeSize,
            &dtData[0], &bytes_read); err != ADSERR_NOERR)
            throw std::runtime_error("cannot read datatype info: 0x" + to_string_hex(err));
        if (bytes_read != info.nDatatypeSize)
            throw std::runtime_error("error reading datatypes: " + std::to_string(bytes_read) + " bytes read, "
                + std::to_string(info.nDatatypeSize) + " expected");
        guard.release();
        initialize(symData, dtData);
    }
    
    adsData::adsData(adsData&&) noexcept = default;
    adsData& adsData::operator=(adsData&&) noexcept = default;
    
    adsData::~adsData() noexcept = default;
    
    void adsData::initialize(const std::vector<char>& symData, const std::vector<char>& dtData) {
        addDatatypes(dtData, types_);
        linkDatatypes(dtData, types_);
        addSymbols(symData, types_, symbols_);
        cycleCheck(types_);
    }
    
    adsVarData adsData::operator[](std::string_view name) const {
        auto curName = name;
        if (auto pos = curName.find('['); pos != curName.npos)
            curName.remove_suffix(curName.size() - pos);
        auto sym = std::lower_bound(symbols_.begin(), symbols_.end(), curName);
        if (sym == symbols_.end())
            throw std::out_of_range("var not found: " + name);
        auto group = sym->group;
        auto offset = sym->baseInfo.offset;
        if (name.size() == sym->baseInfo.name.size())
            return adsVarData{ &sym->baseInfo, group, offset };
        curName = std::string_view{ name.data() + sym->baseInfo.name.size(), name.size() - sym->baseInfo.name.size() };
        if (curName[0] != '.' && curName[0] != '[')
            throw std::out_of_range("var not found: " + name);
        auto dt = sym->baseInfo.typeData;
        for (;;) {
            if (curName[0] == '.') {
                if (dt->typeSpecs.index() != adsData::datatypeInfo::classSpec)
                    throw std::out_of_range("has no subobjects: "s
                        + std::string_view{ name.data(), static_cast<std::size_t>(curName.data() - name.data()) }
                        + " with "sv + curName);
                curName.remove_prefix(1); // '.'
                auto shortName = curName;
                auto pos = shortName.find_first_of(".["sv);
                if (pos != shortName.npos) {
                    shortName.remove_suffix(shortName.size() - pos);
                }
                auto& subs = std::get<adsData::datatypeInfo::classSpec>(dt->typeSpecs);
                auto sub = std::find_if(subs.begin(), subs.end(), [=](auto& v) { return icmp_equal(shortName, v.name); });
                if (sub == subs.end())
                    throw std::out_of_range("subobjects not found: "s + shortName + " of "sv
                        + std::string_view{ name.data(), static_cast<std::size_t>(curName.data() - name.data()) }
                        + " with "sv + name);
                if (sub->isStatic)
                    offset = sub->offset;
                else
                    offset += sub->offset;
                if (pos == shortName.npos)
                    return adsVarData{ &*sub, group, offset };
                curName.remove_prefix(pos);
                dt = sub->typeData;
            } else {
                // cur_name[0] == '['
                if (dt->typeSpecs.index() != adsData::datatypeInfo::arraySpec)
                    throw std::out_of_range("is no array: "s
                        + std::string_view{ name.data(), static_cast<std::size_t>(curName.data() - name.data()) }
                +" with "sv + curName);
                curName.remove_prefix(1); // '['
                auto pos = curName.find(']');
                if (pos == curName.npos)
                    throw std::out_of_range("missing ]");
                auto index = curName;
                index.remove_suffix(index.size() - pos);
                auto& info = std::get<adsData::datatypeInfo::arraySpec>(dt->typeSpecs);
                auto i = info.index(index);
                offset += info.elemSize() * i;
                curName.remove_prefix(pos + 1); // "index]"
                if (curName.empty())
                    return adsVarData{ &info, group, offset, i };
                if (curName[0] != '[' && curName[0] != '.')
                    throw std::out_of_range("missing . or [");
                dt = info.typeData;
            }
        }
    }
    
    adsData::iterator adsData::begin() const noexcept { return iterator{ symbols_.begin() }; }
    adsData::iterator adsData::end() const noexcept { return iterator{ symbols_.end() }; }
    adsData::iterator adsData::cbegin() const noexcept { return begin(); }
    adsData::iterator adsData::cend() const noexcept { return end(); }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    std::string adsVarData::name(std::string prefix) const {
        if (!info_->name.empty()) {
            prefix.reserve(prefix.size() + 1 + info_->name.size());
            prefix += '.';
            prefix += info_->name;
            return std::move(prefix);
        } else
            return std::move(prefix) + shortName();
    }
    std::string adsVarData::shortName() const {
        return info_->name.empty() ? static_cast<const adsArrayInfo*>(info_)->toString(index_) : info_->name;
    }
    const std::string& adsVarData::type() const noexcept { return info_->typeData->name; }
    const std::string& adsVarData::comment() const noexcept { return info_->comment; }
    adsDatatypeId adsVarData::typeId() const noexcept { return info_->typeData->id; }
    sizeType adsVarData::group() const noexcept { return group_;  }
    sizeType adsVarData::offset() const noexcept { return offset_; }
    sizeType adsVarData::size() const noexcept { return info_->typeData->size; }
    bool adsVarData::isStatic() const noexcept { return info_->isStatic; }
    const GUID* adsVarData::typeGuid() const noexcept {
        return info_->typeData->guid.has_value() ? &*info_->typeData->guid : nullptr;
    }
    
    bool adsVarData::is_scalar() const noexcept {
        return info_->typeData->typeSpecs.index() == adsData::datatypeInfo::baseSpec
            || info_->typeData->typeSpecs.index() == adsData::datatypeInfo::compoundSpec; }
    bool adsVarData::is_array()  const noexcept {
        return info_->typeData->typeSpecs.index() == adsData::datatypeInfo::arraySpec;
    }
    bool adsVarData::is_struct() const noexcept {
        return info_->typeData->typeSpecs.index() == adsData::datatypeInfo::classSpec; }
    bool adsVarData::empty() const noexcept {
        return is_scalar() || is_struct()
            && std::get<adsData::datatypeInfo::classSpec>(info_->typeData->typeSpecs).empty();
    }
    sizeType adsVarData::subElements() const noexcept {
        switch (info_->typeData->typeSpecs.index()) {
        default:
            assert(false);
        case adsData::datatypeInfo::baseSpec:
        case adsData::datatypeInfo::compoundSpec:
            return 0;
        case adsData::datatypeInfo::arraySpec:
            return std::get<adsData::datatypeInfo::arraySpec>(info_->typeData->typeSpecs).numElements();
        case adsData::datatypeInfo::classSpec:
            return std::get<adsData::datatypeInfo::classSpec>(info_->typeData->typeSpecs).size();
        }
    }
    adsVarData::iterator adsVarData::begin() const noexcept {
        switch (info_->typeData->typeSpecs.index()) {
            default:
                assert(false);
            case adsData::datatypeInfo::baseSpec:
            case adsData::datatypeInfo::compoundSpec:
                return iterator{ nullptr, 0, 0, 0 };
            case adsData::datatypeInfo::arraySpec: {
                auto& info = std::get<adsData::datatypeInfo::arraySpec>(info_->typeData->typeSpecs);
                return iterator{ &info, group_, offset_, 0 };
            }
            case adsData::datatypeInfo::classSpec: {
                auto& info = std::get<adsData::datatypeInfo::classSpec>(info_->typeData->typeSpecs);
                return iterator{ info.empty() ? nullptr : &info.front(), group_, offset_, 0 };
            }
        }
    }
    adsVarData::iterator adsVarData::end() const noexcept {
        switch (info_->typeData->typeSpecs.index()) {
            default:
                assert(false);
            case adsData::datatypeInfo::baseSpec:
            case adsData::datatypeInfo::compoundSpec:
                return iterator{ nullptr, 0, 0, 0 };
            case adsData::datatypeInfo::arraySpec: {
                auto& info = std::get<adsData::datatypeInfo::arraySpec>(info_->typeData->typeSpecs);
                return iterator{ &info, group_, offset_, info.numElements() };
            }
            case adsData::datatypeInfo::classSpec: {
                auto& info = std::get<adsData::datatypeInfo::classSpec>(info_->typeData->typeSpecs);
                return iterator{ info.empty() ? nullptr : &info.back() + 1, group_, offset_, 0 };
            }
        }
    }
    adsVarData::iterator adsVarData::cbegin() const noexcept { return begin(); }
    adsVarData::iterator adsVarData::cend() const noexcept { return end(); }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    adsData::iterator::reference adsData::iterator::operator*() const noexcept {
        return adsVarData{ &iter_->baseInfo, iter_->group, iter_->baseInfo.offset };
    }
    adsData::iterator::pointer adsData::iterator::operator->() const noexcept { return **this; }
    bool operator==(adsData::iterator lhs, adsData::iterator rhs) noexcept { return lhs.iter_ == rhs.iter_; }
    bool operator!=(adsData::iterator lhs, adsData::iterator rhs) noexcept { return lhs.iter_ != rhs.iter_; }
    adsData::iterator& adsData::iterator::operator++() noexcept { ++iter_; return *this; }
    adsData::iterator adsData::iterator::operator++(int) noexcept { auto tmp = *this; ++*this; return tmp; }
    adsData::iterator::iterator(std::vector<adsData::symbolInfo>::const_iterator it) noexcept : iter_{ it } {}
    


  • adsData.cpp Teil 2

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    adsVarData::iterator::reference adsVarData::iterator::operator*() const noexcept {
        if (info_->name.empty())
            return adsVarData{ info_, group_, offset_ + index_ * info_->typeData->size, index_ };
        else
            return adsVarData{ info_, group_, info_->isStatic ? offset_ + info_->offset : info_->offset };
    }
    adsVarData::iterator::pointer adsVarData::iterator::operator->() const noexcept { return **this; }
    bool operator==(const adsVarData::iterator& lhs, const adsVarData::iterator& rhs) noexcept {
        return lhs.info_ == rhs.info_ && lhs.index_ == rhs.index_
            && lhs.offset_ == rhs.offset_ && lhs.group_ == rhs.group_;
    }
    bool operator!=(const adsVarData::iterator& lhs, const adsVarData::iterator& rhs) noexcept { return !(lhs == rhs); }
    adsVarData::iterator& adsVarData::iterator::operator++() noexcept {
        if (info_->name.empty())
            ++index_;
        else
            ++info_;
        return *this;
    }
    adsVarData::iterator adsVarData::iterator::operator++(int) noexcept { auto tmp = *this; ++*this; return tmp; }
    adsVarData::iterator::iterator(const adsData::subSymbolInfo* info, sizeType group, sizeType offset, sizeType index) noexcept
        : info_{ info }, group_{ group }, offset_{ offset }, index_{ index }
    {}
    

    Demo

    #include <algorithm>
    #include <iomanip>
    #include <iostream>
    #include <stdexcept>
    #include <string>
    #include <string_view>
    #include <utility>
    #include "adsData.h"
    
    using namespace std::literals::string_view_literals;
    
    std::ostream& operator<<(std::ostream& s, adsDatatypeId val) {
        static constexpr std::pair<adsDatatypeId, std::string_view> typenames[] =
        {
            { adsDatatypeId::void_,		"void"sv },
            { adsDatatypeId::int16_,    "int16"sv },
            { adsDatatypeId::int32_,    "int32"sv },
            { adsDatatypeId::float_,    "float"sv },
            { adsDatatypeId::double_,   "double"sv },
            { adsDatatypeId::int8_,     "int8"sv },
            { adsDatatypeId::uint8_,    "uint8"sv },
            { adsDatatypeId::uint16_,   "uint16"sv },
            { adsDatatypeId::uint32_,   "uint32"sv },
            { adsDatatypeId::int64_,    "int64"sv },
            { adsDatatypeId::uint64_,   "uint64"sv },
            { adsDatatypeId::cstring_,  "cstring"sv },
            { adsDatatypeId::wcstring_, "wcstring"sv },
            { adsDatatypeId::bool_,     "bool"sv },
            { adsDatatypeId::big_,      "blob"sv },
        };
        auto it = std::lower_bound(std::begin(typenames), std::end(typenames), val,
            [](auto& x, auto& y) { return x.first < y; });
        if (it == std::end(typenames) || it->first != val)
            s << "unknown(" << static_cast<int>(val) << ')';
        else
            s << it->second << '(' << static_cast<int>(val) << ')';
        return s;
    }
    
    std::ostream& operator<<(std::ostream& s, const GUID& guid) {
        return s << std::uppercase << std::hex << std::setfill('0')
            << std::setw(8) << guid.Data1 << '-'
            << std::setw(4) << guid.Data2 << '-'
            << std::setw(4) << guid.Data3 << '-'
            << std::setw(2) << static_cast<short>(guid.Data4[0])
            << static_cast<short>(guid.Data4[1])
            << '-' << static_cast<short>(guid.Data4[2])
            << static_cast<short>(guid.Data4[3])
            << static_cast<short>(guid.Data4[4])
            << static_cast<short>(guid.Data4[5])
            << static_cast<short>(guid.Data4[6])
            << static_cast<short>(guid.Data4[7])
            << std::nouppercase << std::dec;
    }
    
    std::ostream& operator<<(std::ostream& s, const adsVarData& var) {
        s   << "short: " << var.shortName() << '\n'
            << "type: " << var.type() << '\n'
            << "comment: " << var.comment() << '\n'
            << "typeId: " << var.typeId() << '\n'
            << "group: " << var.group() << '\n'
            << "offset: " << var.offset() << '\n'
            << "size: " << var.size() << '\n'
            << "is static: " << std::boolalpha << var.isStatic() << '\n';
        if (var.typeGuid())
            s << "type GUID: " << *var.typeGuid() << '\n';
        return s;
    }
    
    void showSubs(const adsVarData& var, std::string prefix) {
        for (auto&& sub : var) {
            std::cout << "name: " << sub.name(prefix) << '\n' << sub << '\n';
            showSubs(sub, sub.name(prefix));
        }
    }
    
    int main() {
        std::string varNames[] = {
            "MAIN.myInt",
        };
        try {
            adsData plcData{};
            for (auto&& var : plcData) {
                std::cout << var << '\n';
                showSubs(var, var.shortName());
            }
            for (auto& name : varNames)
                try {
                std::cout << plcData[name] << '\n';
            }
            catch (std::out_of_range& e) {
                std::cout << e.what() << '\n';
            }
        } catch (std::runtime_error& e) {
            std::cout << e.what() << '\n';
            return 1;
        }
    }
    

    gibt einfach alles aus.



  • Ich glaube, es wäre sinnvoll, die längeren Schnipsel zu verlinken.



  • @camper

    Wahnsinn. Vielen Dank.

    Bevor ich da jetzt erst mal inhaltlich was dazu sagen kann muss ich mich erst mal eine Weile durchkämpfen.

    Frage: Wieso hast du jetzt keine _impl.h ->

    Für mich war zwar klar dass man dies macht um bestimmte Implemetierungsdetails zu verstecken. Aber das hast du ja auch mit dem namenlosen namespace gemacht. Und wann man nun was wo hin packt war nicht ganz so ersichtlich.



  • Den Code habe ich nun mal in mein Programm genommen.
    Kompiliert aber noch nicht folgender Fehler:

    Error	C1083	Cannot open include file: 'VersionHelpers.h': No such file or directory	c:\twincat\3.1\sdk\include\TcDef.h
    


  • booster schrieb:

    Den Code habe ich nun mal in mein Programm genommen.
    Kompiliert aber noch nicht folgender Fehler:

    Error	C1083	Cannot open include file: 'VersionHelpers.h': No such file or directory	c:\twincat\3.1\sdk\include\TcDef.h
    

    verweist bei mir auf
    c:\Program Files\Windows Kits\10\Include\10.0.16299.0\um\VersionHelpers.h
    Evtl. musst du das Windows SDK noch mit dem Visual C++ Installer nachinstallieren.