va_arg / alternative/erweitern



  • Hallo zusammen,

    ich hoffe Ihr könnt mir nochmal weiterhelfen, Dr. Google kann es nicht.

    ich möchte einen IMU über eine serielle Schnittstelle ansprechen und ihm Anfragen zusenden.

    Der Aufbau sieht etwa so aus

    Sync1|Sync2|Desc|Len|FieldLen|FieldDesc|FieldData

    bisher habe ich das mit va_arg lösen können, da es nun flexibler werden soll komme ich an meine Grenzen. Bisher konnte ich immer ein Argument(Desc) übergeben gefolgt von der Anzahl der zu übergebenden Daten und letztendlich die Daten selber.

    int createCommand(char cDescriptor,int iCount, ...);
    

    Wie kann ich es denn realisieren wenn ich nun mehrere Descriptoren mit einmal senden muss/will. Also quasi so...

    Sync1|Sync2|Desc|Len|FieldLen1|FieldDesc1|FieldData1|FieldLen2|FieldDesc2|FieldData2

    Aus der Dokumentation von va_arg lese ich nicht herraus das dies damit machbar ist, und meine suche stößt mich doch jedesmal wieder darauf.

    Vielen Dank für eure Zeit...



  • meine Idee ist halt die Methode zu überladen aber ob das der saubere weg ist ....



  • Ist das ein Binär- oder ASCII Protokoll?



  • Hallo DocShoe,

    ich denke Binär, bin mir aber nicht sicher. Die Daten werden Hexadezimal Byteweise geschrieben.

    char c2[] = { 0x75,0x65,0x80,0x0E,0x0E,0x04,0x3E,0x7A,0x63,0xA0,0xBB,0x8E,0x3B,0x29,0x7F,0xE5,0xBF,0x7F,0x92,0xC0 };
    

    Wobei das 6.Byte der Decriptor1 ist und die folgenden, bis auf die letzten beiden, die Datenfelder sind...wenn dir das hilft



  • Du weisst nicht, ob du ein Binär- oder ASCII Protokoll hast 😮

    Naja, mein Vorschlag ist, das Telegramm Element für Element zusammenzubauen. Ungefähr so:

    #include <vector>
    
    class Telegram
    {
       mutable std::vector<unsigned char> Payload_;
    
    public:
       static const unsigned char Sync1 = 0x75;
       static const unsigned char Sync2 = 0x65;
    
       Telegram() :
          Telegram( 0 )
       {
       }
    
       Telegram( unsigned char Descriptor )
       {
          // ersten 2 Byte Telegramheader, 3. Byte Descriptor, Bytes 4+5 Telegrammlänge
          Payload_ = { Sync1, Sync2, Descriptor, 0x00, 0x00 };
       }
    
       void append( unsigned int FieldLen, const Type1& FieldDesc, const Type&2 FieldVal )
       {
          // to do: Daten byteweise an Payload anhängen
       }
    
       unsigned char const* payload() const
       {
          // Telegrammlänge kann aus der Anzahl der Elemente im Vektor berechnet werden
          Payload_[3] = (Payload_.size() >> 8) & 0xff
          Payload_[4] = Payload_.size()        & 0xff;
          return Payload_.data();
       }
    };
    
    int main()
    {
       Telegram t( Desc1 );
    
       t.append( MyLen1, MyDesc1, MyField1 );
       t.append( MyLen2, MyDesc2, MyField2 );
       t.append( MyLen3, MyDesc3, MyField3 );
       somedevice.send( t.payload() );
    }
    

    Die Feldgrößen und Indexe werden nicht passen, aber es geht ja um´s Prinzip.

    Zur eigentlichen Frage: Du kannst die Anzahl der Parameter einer Ellipse nicht bestimmen. In C++ gibt es allerdings bessere Möglichkeiten, wenn es wirklich eine Funktion mit beliebig vielen Parametern sein soll => variadic templates.



  • Ehrlich gesagt wusste ich nicht das es dies als Protokoll gibt, ich weiß zwar das Binär 1 und 0 sind und ASCII lesbar, also Zeichen sind. Ich dachte ASCII ist nur eine Art der Darstellung, kann ein ASCII Zeichen ja auch Binär darstellen also das kleine "a" als 01100001.....
    Dazu werde ich mich noch belesen müssen, kommt auf die lange Liste .

    Für dein Beispiel dank ich erst mal und gucke mir den Verweis mal an....



  • Hallo nochmal,"
    also ich habe mich jetzt bei den variadic templates etwas eingelesen. Beim Punkt Expansion loci Function argument lists, das sieht fast so aus als wenn ich das nutzen könnte.

    #include "IMU_Lord3DM.h"
    IMU_Lord3DM::IMU_Lord3DM()
    {
    }
    char m_cSync1 = 0x75; //definiert im Header
    char m_cSync2 = 0x65; // definiert im Header
    
    char* IMU_Lord3DM::setParam(char cDescriptor, int iCount, ...)
    {
    	va_list p_list;
    	char p_cQuelle;
    	char ZielCommand[100];
    	int j = 2;
    
    	ZielCommand[0] = iCount + 2;
    	ZielCommand[1] = cDescriptor;
    
    	va_start(p_list, iCount);
    	for (int i = 1; i <= iCount; i++)
    	{
    		p_cQuelle = va_arg(p_list, char);
    		ZielCommand[j] = p_cQuelle;
    		j++;
    	}
    	va_end(p_list);
    
    	return ZielCommand;
    }
    
    char* IMU_Lord3DM::createCommand(char cDescriptor, int iCount, ...)
    {
    	va_list p_list;
    	char p_cQuelle;
    	char ZielCommand[100];
    	char p_cSync1 = 0x75;
    	char p_cSync2 = 0x65;
    	int j = 2;
    
    	ZielCommand[0, 1] = p_cSync1, p_cSync2;
    
    	va_start(p_list, iCount);
    	for (int i = 1; i <= iCount; i++)
    	{
    		p_cQuelle = va_arg(p_list, char);
    		ZielCommand[j] = p_cQuelle;
    	}
    	va_end(p_list);
    
    	return 0;
    }
    
    int main()
    {
    char* test = B->createCommand(0x80, 2, (B->setParam(0x01, 2, 0x10, 0x11), B->setParam(0x02, 3, 0x12, 0x13, 0x14)));
    }
    

    So wie hier in etwa, oder interpretiere ich das vollkommen falsch ?

    f(h(args...) + args...); // expands to 
    // f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
    

    EDIT:
    Also die Methode "setParam" funktioniert soweit. Was ich nicht verstehe ist die Methode "createCommand", schon das 2. SyncByte schreibt sie nicht und das erste schreibt sie an die 2. stelle , an der ersten stelle steht dann 0xcc .

    Beim Debuggen fällt mir auf das die Daten zwar richtig übergeben wurden aber falsch abgespeichert werden also aus 0x04 wird 0x87 und es steht an 3. Stelle....ich hab keinen blassen Schimmer woran das liegen kann

    @DocShoe, danke für deinen Hinweis natürlich nutze ich das Binärprotokoll 😉



  • Äh.... ne.
    Du verwechselst Ellipsen mit variadic templates, in deinem Quelltext benutzt du weiterhin Ellipsen und keine variadic templates. Ich denke auch nicht, dass variadic templates für diesen Fall sinnvoll sind, schließlich erwartest du ja immer die gleichen Parametertypen. Musst du das Command denn unbedingt mit einem Funktionsaufruf erzeugen? Was spricht gegen einen Zusammenbau Feld für Feld?
    Dein Programm erzeugt übrigens undefiniertes Verhalten, du gibst in Zeile 29 einen Zeiger auf ein Array zurück, das beim Verlassen der Funktion zerstört wird und nicht mehr existiert. Daher vermutlich dein Fehler.
    Deine Elementzuweisung in Zeile 41 ist falsch, du kannst keine zwei Elemente gleichzeitig zuweisen. Der Kommaoperator funktioniert nicht so, wie du denkst.

    Am besten löst du dich von der Ellipse. Sie ist unsicher, erzeugt zur Compile Time keine Fehlermeldung und kann ein Grund für schwer zu findende Laufzeitfehler sein.



  • Prinzipiell spricht nichts dagegen, doch bin ich zu unerfahren und das hier ist die einzige Lösung die mir eingefallen ist.

    Da die zu übergebenden Parameter in der Anzahl Variiabel sind denke ich das das einzelne zusammenbauen nicht funktionieren würde, oder ?

    Ich meine, wenn der Aufbau immer gleich wäre, in Anzahl der der Felder und jedes Feld eine feste Anzahl an Parametern hat würde ich es ja so machen. Doch Kann es bei einem Befehl 1 oder auch mehrere Felder geben mit jeweils einem oder mehreren Parametern(pro Feld).
    Vielleicht fehlt mir da auch einfach noch die Vorstellungskraft...



  • Du versuchst alle Sonderfälle mit einer einzigen Funktion zu erschlagen, das ist keine gute Idee, versuche das modular aufzubauen. Du weißt, dass es sich um ein binäres Protokoll mit variabler Länge handelt, da springt einen std::vector<char> oder std::vector<unsigned char> doch förmlich an. Die Protokollspezifikation beschreibt das Format des Telegramms, die ersten beiden Bytes sind doch immer 0x75 und 0x65 . Darauf folgen ein Deskriptor und die Telegrammlänge, die Felder sind für jedes Telegramm gleich. Danach folgen beliebig viele Felder, die ebenfalls ein festes Format haben. Ich würde das dann so lösen, dass zuerst ein Telegramm erzeugt wird, das nur aus dem Telegrammkopf besteht. Anschließend werden die benötigten Felder an das Telegramm angehängt. Und dann sind wir bei dem Code, den ich oben bereits gepostet habe, wobei die append_field angepasst werden könnte:

    void Telegram::append_field( unsigned char Descriptor, void* const FieldData, unsigned int FieldLength )
    {
       Payload_.push_back( Descriptor );
    
       // Feldlänge als 16bit Wert, zuerst MSB, dann LSB
       Payload_.push_back( (FieldLength & 0xff00) >> 8 );
       Payload_.push_back( (FieldLength & 0x00ff) );
    
       // ggf. Felddaten
       if( FieldData )
       {
          unsigned char const* ptr = reinterpret_cast<void* const>( FieldData );
          Payload_.insert( Payload_.end(), ptr, ptr + FieldLength );
       }
       // Länge des Telegramms anpassen
       Payload_[3] = (Payload_.size() & 0xff00) >> 8;
       Payload_[4] = (Payload_.size() & 0xff);
    }
    
    std::size_t Telegram::size() const
    {
       return Payload_.size();
    }
    
    int main()
    {
       Telegram t( 100 );
    
       double Value = 1.0;
       t.append_field( 200, &Value, siezof( Value ) );
    
       // Telegramm verschicken  
       some_device.send( Telegram.payload(), Telegram.size() );
    }
    

    Mit der append_field Methode kann man einige Komfortfunktionen bauen, wenn du zB besonders häufig 32bit Integer Felder hast ginge so etwas:

    void Telegram::append_field( unsigned char Descriptor, unsigned int Value )
    {
       append_field( Descriptor, &Value, sizeof( Value ) );
    }
    

    Edit:
    Wenn die einzelnen Felder variabel aufgebaut sind dann implementierste das Ganze halt auf Feldbasis, anstatt auf Telegrammbasis. Wie viele verschiedenen Kommandos gibt es eigentlich? Sind da eher 10 oder eher 100?



  • 114 gibt es insgesamt, ich habe das jetzt so gemacht wie du sagtest. Also modular. Funktioniert auch, bis auf die ChekSumme....aber das bekomme ich noch hin

    IMU_Lord3DM::IMU_Lord3DM()
    {
    	m_vTelegram = { m_cSyncByte0 ,m_cSyncByte1 };
    }
    
    //setzte Descriptor und Paketgroesse
    bool IMU_Lord3DM::setPacketData(char cPaketDesc)
    {
    	m_vTelegram.push_back(cPaketDesc);
    	m_vTelegram.push_back(0xFF);
    
    	if (4 == m_vTelegram.size())
    	{
    		return true;
    	}
    	m_CDebugOut.debug(0, "ERROR setPacketData");
    	return false;
    }
    
    //setzt Fielddescriptor und Parameter
    bool IMU_Lord3DM::setField(char cFieldDesc, int iAnzahlParam, ...)
    {
    	//groesse des Feldes wird in Vector gepackt
    	int iCount = iAnzahlParam;
    	iCount += 2;
    	char cAnzParam = iCount;
    	m_cPaketPayload = iCount;
    	m_vTelegram.push_back(cAnzParam);
    
    	//Field Descriptor wird in Vector gepackt
    	m_vTelegram.push_back(cFieldDesc);
    
    	va_list p_listParam;
    	char p_cQuelle;
    
    	va_start(p_listParam, iAnzahlParam);
    	for (int i = 0; i < iAnzahlParam; i++)
    	{
    		p_cQuelle = va_arg(p_listParam, char);
    		m_vTelegram.push_back(p_cQuelle);
    	}
    	va_end(p_listParam);
    
    	return true;
    }
    

    Auf jedenfall nochmal vielen Dank für deine Hilfe...



  • Naja, wenn du unbedingt bei den Ellipsen bleiben willst... dann frag ich mich, wieso ich mir hier die Finger wundtippe. Hoffentlich fliegt´s dir nicht um die Ohren.



  • Hier: http://www.learncpp.com/cpp-tutorial/714-ellipsis-and-why-to-avoid-them/ mal ein paar Beispiele, warum man auf die Ellipse verzichten sollte. Da ist Ärger vorprogrammiert.


Anmelden zum Antworten