www.mikrocontroller.net

Forum: PC-Programmierung Programmstruktur zur Verarbeitung verschiedener Netzwerknachrichten


Autor: Klaus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!


Ich überlege gerade die grundsätzliche Programmstruktur (Klassenstruktur 
etc. ) für mein Programm.

Ws geht darum, dass ich verschiedene Netzwerknachrichten (als Bytestrom 
) bekomme und diese gerne komfortabel in meinem Programm 
weiterverarbeiten möchte. Ich suche dafür eine elegante C++ Lösung.

In C würde ich ein Datentyp definieren der als Union für jeden 
Nachrichtentyp ein Struct enhält. So dass ich bequem auf die Nutzdaten 
jedes Nachrichtentyps zugreifen kann.

Wie würde man das elegant in C++ lösen? Oder würde man das genau so 
machen?

Autor: tobi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zur Not auch mit Unions.

Autor: Klaus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja zur Not. Aber normalerweise hält C++ ja für fast alle C-Konzepte eine 
alternative mit gewissen Vorteilen bereit ;)

Und da ich in C++ Programmiere würde ich ungerne in C-Syle zurück 
fallen, wenn es eine elegantere alternative gibt.

Autor: Ahem (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn man das mal von einer etwas abstrakteren Ebene aus betrachtet, so 
sind structs die Beschreibung von Datenstrukturen im Speicher und unions 
das Mittel um mehrere solcher Beschreibungen sozusagen simultan auf 
einen einzigen Speicherbereich zu legen.

In C++ gibt es genauso structs und unions wie in C. Hingegen gibt es 
kein anderes Sprachmittel, das genau dieses leistet und es wäre auch 
redundant ein weiteres vorzuhalten (wenn es nicht weitere Möglichkeiten 
bieten würde).

Würde sich jedoch aus irgendwelchen Gründen der Zwang ergeben auf 
structs und unions zu verzichten, bliebe noch, nach genauem Studium des 
Layouts von instanziierten Individuen einer Klasse, der Weg, mehrere 
Klassen mit unterschiedlichen Mitgliedsvariablen zu erzeugen und Zeiger 
auf diese zu verwenden, die alle auf den selben Speicherbereich zeigen.
Weiter, bliebe der, mehr traditionelle, Weg, den Datenstrom beim Eingang 
zu parsen und die Felder entsprechend zu verarbeiten.

Es bliebe zu berücksichtigen, das sich die Mitglieder von structs mit 
einem pragma packen liessen, was nicht eigentlich C sondern eine 
spezielle Compilereigenschaft wäre (und bei den mir bekannten 
tatsächlich ist). Ob es eine analoge Compileranweisung für Mitglieder 
von C++-Klassen gibt ist mir nicht bekannt.

Im allgemeinen ist die einzige "natürliche" Oberklasse des Inhalts eines 
ansonsten sehr heterogenen Datenstrom eben der Datenstrom. Aber sogar 
eine sehr grosse Ähnlichkeit der Datensätze würde zwar die Bildung von 
mehreren miteinander verwandten Klassen nahelegen; dennoch für eine 
Implementierung nicht zwangsweise bedeuten, das hier Speicherbereiche 
beschrieben werden.
Wahrscheinlich würde man vielmehr (abgesehen von identischen linken 
Seiten) unterschiedliche Parser für den verbleibenden Datenstrom 
implementieren.
Eine andere Alternative wäre die Definition von Containern die viele 
kleine Klassen für jeweils ein Element des Datenstroms enthalten, die 
jeweils ihren eigenen Teilabschnitt des Datenstroms parsen.

Mein Fazit ist, das es keine "elegantere" Möglichkeit gibt wenn die 
Grundidee beibehalten werden soll. Wobei zu fragen bliebe, was den 
"Eleganz" in Deinen Augen ist. (Womit ich meine, das es eine objektive 
Beschreibung dieses Kriteriums geben muss, damit man ein entsprechendes 
Konstrukt wählen kann).

Der Vorteil von struct/unions ist, das man nur jeweils einmal die 
Struktur beschreibt und bei Änderungen der Reihenfolge etc. nur die 
Struktur ändert, jedoch an der Verarbeitung nichts ändern muss. Werden 
aber Parser implementiert muss man, da der Ablauf die Grammatik 
beschreibt, sehr aufpassen und bei Änderungen noch viel mehr.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das hängt natürlich auch irgendwie davon ab, wie man die
verschiedenen Nachrichten unterscheiden kann.

Wenn es noch nicht anderweitig festgelegt ist und man noch Einfluß
darauf hat, kann man das ja nutzen.

Ich würde vermutlich eine Nachricht so aufbauen, daß am Anfang zur
Sicherheit eine bestimmte Kennung kommt (um absoluten Unfug
blocken zu können), dann bei Bedarf eine Längenangabe für den Rest
der Nachricht (dadurch kann der Empfänger ggf.  seinen
Empfangspuffer anpassen und den Rest in einem Rutsch lesen).

Anschließend soll dann eine Kennung kommen, die den Typ der
Nachricht exakt definiert.

Für jede Art von Nachricht baut man sich eine entsprechende
Klasse, die sinnvollerweise von einer passenden Basisklasse
abgeleitet (z.B. ABC_Message) ist. Wahrscheinlich wird man von
dieser Basisklasse nie ein Objekt schaffen, also virtuell machen
(ABC = abstract base class).

Eine Methode dieser Klasse (factory method, virtueller
Konstruktor, z.B. create()) wird von den Ableitungen
jeweils überschrieben, um ein Objekt des richtigen Typs zu
erzeugen.

Eine weitere statische Methode der Basisklasse dient dazu, anhand
einer eindeutigen ID jeder abgeleiteten Klasse (Klasse, nicht
Objekt!) ein zugehöriges Muster der abgeleiteten Klasse speichern
zu können. Ich nenne sie mal register_prototyp(), weil sie einen
Prototypen registriert.

Für jede abgeleitete Klasse muß ein solches Muster (ein
Prototyp) bei der Basisklasse angemeldet werden.
Das kann durch ein statisches Element bei Programmstart passieren;
dann muß man aber aufpassen, dass die Reihenfolge aller statischen
Elemente stimmt.
Es ist auch denkbar, alles was zu einer solchen abgeleiteten
Klasse gehört, in eine DLL auszulagern (die in ihrer
Initialisierungsfunktion dann das Anmelden übernimmt). Dann kann
ein bereits kompiliertes (!) Programm beim Kunden ohne neues
Übersetzen und Linken um neue Nachrichtentypen erweitert werden,
indem man passende DLLs ergänzt (falls die über Namen, z.B. aus
Konfigurationsdateien geladen werden können).

Ab dann kann eine weitere statische Methode der Basisklasse anhand
einer Klassen-ID über das zugehörige Musterobjekt die
create()-Methode der richtigen Klasse aufrufen, die dann den
Bytestrom bekommt und daraus ein ordentliches Objekt baut.

Ich habe das mal zu einem möglichst kleinen vollständigen Beispiel
zusammengeschraubt...
Es geht dabei um die abstrakte Basisklasse ABC_Message und zwei
davon abgeleitete Nachrichtentypen (die sich nicht so richtig
unterscheiden, aber egal): MessageTyp1 und MessageTyp2.

Erst das Testprogramm, das ist noch am übersichtlichsten:
// Time-stamp: "03.06.09 12:33 testmessage.cpp klaus<bei>wachtler.de"
//
// kleines Testprogramm zur Nachrichtenerzeugung
//
// Linux/gcc:
// g++ -Wall -I. testmessage.cpp ABC_Message.cpp -o testmessage && ./testmessage

#include <iostream>


#include <ABC_Message.h>
#include <MessageTyp1.h>
#include <MessageTyp2.h>



#ifndef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__  __FUNCTION__
#endif /* ifndef __PRETTY_FUNCTION__ */



int main( int nargs, char **args )
{

  try
  {
    // Klassen amelden:
    ABC_Message::register_prototyp( "Typ1", new MessageTyp1 );
    ABC_Message::register_prototyp( "Typ2", new MessageTyp2 );

    // Objekte anhand eines Strings erzeugen:
    ABC_Message *pErsteNachricht
      = ABC_Message::createMessageObject( "Typ1\nHallo, ich bin der erste" );
    ABC_Message *pZweiteNachricht
      = ABC_Message::createMessageObject( "Typ2\nHallo, ich bin der zweite" );

    // Verwenden:
    if( pErsteNachricht )
    {
      pErsteNachricht->zeigdich();
    }
    if( pZweiteNachricht )
    {
      pZweiteNachricht->zeigdich();
    }
  }
  catch( std::exception &Fehler )
  {
    std::cerr << "Fehler: <"
              << Fehler.what()
              << "> in "
              << __PRETTY_FUNCTION__
              << "\n";
  }
  catch( ... )
  {
    std::cerr << "unbekannter Fehler in "
              << __PRETTY_FUNCTION__
              << "\n";
  }
}

Zuerst werden mit einer statischen Methode
ABC_Message::register_prototyp() zwei Klassen "angemeldet",
nämlich MessageTyp1 und MessageTyp2 mit je einem Namen. Dieser
Name ist der, der dann auch später im Datenstrom als Kennung
auftauchen soll, anhand dessen die Objekte erzeugt werden sollen.

Dann werden zwei solche Objekte erzeugt, indem ein passender
String an ABC_Message::createMessageObject() übergeben wird.  Ich
habe das jetzt so festgelegt, daß in dem String erst die Id kommen
muss, dann ein Zeilenvorschub und dann die restlichen Daten.  Mit
diesen restlichen Daten hat ABC_Message nichts zu tun, sondern
nimmt anhand der Id den Prototypen vom passenden Typ, der beim
Anmelden übergeben wurde, und ruft darüber den zugehörigen
virtuellen Konstruktor der passenden Klasse auf, dem werden dann
die Daten nach dem LF übergeben und er muß dann irgendwie daraus
das Objekt bauen.
Macht natürlich nur Sinn, wenn er dann wiederum mit den
Initialisierungsdaten nach dem LF etwas sinnvolles anfangen kann.
Daraus bekommt man je einen Zeiger auf ein neues Objekt, und kann
es irgendwie verwenden.
Als Beispiel habe ich einfach eine Methode zeigdich() eingebaut;
im echten Leben würde da vermutlich mehr kommen.

Die Basisklasse ABC_Message enthält eine statische Map, in der die
Zuordnung zwischen den IDs und den Prototypen abgelegt ist, sowie
die Methoden zum Registrieren der Klassen und Erzeugen der
Nachrichtenobjekte:
// Time-stamp: "03.06.09 12:30 ABC_Message.h klaus<bei>wachtler.de"
//
// virtuelle Basisklasse, von der die Nachrichtentypen abgeleitet sind

#ifndef __ABC_Message_h__
#define __ABC_Message_h__

#include <deriveexception.h>
#include <cstring>
#include <stdexcept>
#include <map>


DERIVEEXCEPTION( ErrMessage, std::runtime_error, "Fehler mit Messageerzeugung" );
DERIVEEXCEPTION( ErrMessageParseError, ErrMessage, "falsche Initialisierungsdaten" );
DERIVEEXCEPTION( ErrMessageUnknownID, ErrMessage, "unbekannte ID" );

class ABC_Message
{
public:

  // Konstruktor
  ABC_Message()
  {
  }

  // Destruktor
  virtual ~ABC_Message()
  {
  }

  // und alle Methoden, die von der Nachricht so verlangt werden und
  // folglich implementiert werden müssen...
  virtual void zeigdich() = 0;


  // szData muss die Daten zum Erzeugen des Objekts enthalten, und
  // zwar zuerst eine gültige ID, gefolgt von einem \n, dann die
  // restlichen Daten, aus denen das jeweilige create ein Objekt bauen
  // kann.
  static ABC_Message *createMessageObject( const char *szData )
    throw( ErrMessageUnknownID )
  {
    // ID extrahieren und Prototyp suchen:
    const char *pLF;

    if( ( pLF = std::strchr( szData, '\n' ) )
        &&
        pLF>szData
        )
    {
      std::string sId = std::string( szData, pLF-szData );
      std::map< std::string, const ABC_Message* >::const_iterator  itIdPrototyp;
      if( ( itIdPrototyp = map_Id_Prototyp.find( sId ) ) != map_Id_Prototyp.end() )
      {
        // ID gefunden, also darüber ein Objekt mit den übergebenen
        // Daten erzeugen:
        return itIdPrototyp->second->create( pLF+1 );
      }
      else
      {
        throw ErrMessageUnknownID( std::string( "<" ) + sId + ">" );
      }
    }
    else
    {
      // szData ungültig
      throw ErrMessageParseError( std::string( "<" ) + szData + ">" );
    }
  }

  static void register_prototyp( const std::string &id, const ABC_Message *pProtoyp )
  {
    map_Id_Prototyp.insert( std::make_pair( id, pProtoyp ) );
  }

protected:

  // erzeugt ein neues Objekt anhand eines C-Strings; im Fehlerfall
  // wird NULL geliefert:
  virtual ABC_Message *create( const char *szContent ) const throw() = 0;


private:

  // bildet Klassen-IDs auf Prototypen der Klasse ab
  static std::map< std::string, const ABC_Message* >    map_Id_Prototyp;


}; // class ABC_Message...


#endif /* ifndef __ABC_Message_h__ */

// Local Variables:
// mode:C++
// End:

Die beim Anmelden erzeugten Prototypen werden in dieser Form
übrigens erst bei Programmende gelöscht. Dazu muss der Destruktor
virtuell sein, weil in der Map ja eigentlich nur ABC_Message*
abgelegt sind, in Wirklichkeit aber davon abgeleitete Objekte
zerstört werden müssen!

Weiterhin funktioniert das so nur, wenn es keine Kollisionen durch
Threads gibt.
Falls das zu befürchten ist, muß man natürlich die Zugriffe auf
die Map mit Semaphoren irgendwie abdichten.

Die eigentlichen Nachrichten können natürlich auch indirekt von
ABC_Message abgeleitet sein, so daß ganze Hierarchien gebaut werden
können.

Weil die Klasse noch ein static Element enthält (die Map), muß
diese in einer CPP-Datei definiert werden:
// Time-stamp: "03.06.09 12:03 ABC_Message.cpp klaus<bei>wachtler.de"
//
// statische Elemente für ABC_Message

#include <ABC_Message.h>

std::map< std::string, const ABC_Message* >   ABC_Message::map_Id_Prototyp;



Hier die beiden abgeleiteten NAchritentypen:
// Time-stamp: "03.06.09 12:22 MessageTyp1.h klaus<bei>wachtler.de"
//
// Nachricht Typ 1

#ifndef __Message_Typ1_h__
#define __Message_Typ1_h__

#include <ABC_Message.h>
#include <string>
#include <iostream>


class MessageTyp1 : public ABC_Message
{
 public:

  MessageTyp1()
    : sWerbinich( "" )
  {
  }

  MessageTyp1( const char *szContent )
    : sWerbinich( std::string( szContent ) )
  {
  }

  virtual ~MessageTyp1()
  {
  }

 protected:

  // erzeugt Objekt mit Daten:
  virtual ABC_Message *create( const char *szContent ) const throw()
  {
    // TODO: Daten korrekt verwenden (parsen etc.), um Objekt sinnvoll
    // zu bauen!
    return new MessageTyp1( szContent );
  }

  void zeigdich()
  {
    std::cout << "ich bin ein MessageTyp1: " << sWerbinich << std::endl;
  }

 private:

  std::string  sWerbinich;

}; // class MessageTyp1

#endif /* ifndef __Message_Typ1_h__ */

// Local Variables:
// mode:C++
// End:

// Time-stamp: "03.06.09 12:30 MessageTyp2.h klaus<bei>wachtler.de"
//
// Nachricht Typ 2

#ifndef __Message_Typ2_h__
#define __Message_Typ2_h__

#include <ABC_Message.h>
#include <string>
#include <iostream>


class MessageTyp2 : public ABC_Message
{
 public:

  MessageTyp2()
    : sWerbinich( "" )
  {
  }

  MessageTyp2( const char *szContent )
    : sWerbinich( std::string( szContent ) )
  {
  }

  virtual ~MessageTyp2()
  {
  }

 protected:

  // erzeugt Objekt mit Daten:
  virtual ABC_Message *create( const char *szContent ) const throw()
  {
    // TODO: Daten korrekt verwenden (parsen etc.), um Objekt sinnvoll
    // zu bauen!
    return new MessageTyp2( szContent );
  }

  void zeigdich()
  {
    std::cout << "ich bin ein MessageTyp2: " << sWerbinich << std::endl;
  }

 private:

  std::string  sWerbinich;

}; // class MessageTyp2

#endif /* ifndef __Message_Typ2_h__ */

// Local Variables:
// mode:C++
// End:


Nur aus Faulheit habe ich eine zusätzliche Headerdatei verwendet,
die eigentlich hier nicht hingehört, aber zum Übersetzen der
obigen Beispiele nötig ist:
//////////////////////////////////////////////////////////////////////////
//
// Time-stamp: "03.06.09 12:22 deriveexception.h klaus<bei>wachtler.de"
//
//////////////////////////////////////////////////////////////////////////
//
// Mit dem Makro DERIVEEXCEPTION kann man eine exception von einer
// anderen (std::runtime_error, std::logic_error und alle davon
// abgeleiteten) ableiten und einen erklärenden Text hinzufügen.
// Durch mehrfache Verwendung von DERIVEEXCEPTION lassen sich ganze
// Hierarchien aufbauen.
//
// Ein Aufruf von DERIVEEXCEPTION erzeugt also eine neue class.
//
// Beispiel:
// DERIVEEXCEPTION( Feld_err, runtime_error, "Feldgrenzenüberschreitung" );
// DERIVEEXCEPTION( FeldOben_err, Feld_err, "(oben)" );
// DERIVEEXCEPTION( FeldUnten_err, Feld_err, "(unten)" );
// baut diese Klassenhierarchie unterhalb von runtime_error:
// runtime_error
//   |
//   +--Feld_err
//       |
//       +--FeldOben_err
//       |
//       +--FeldUnten_err
//
// Der in einer so gebildeten Ausnahme enthaltene Text (what())
// besteht aus allen Texten der Ableitungskette, jweils mit \n
// getrennt.
// FeldUnten_err::what() würde also
// "Feldgrenzenüberschreitung\n(unten)" liefern.
// Beim Erzeugen einer Ausnahme kann im ctor ein weiterer Text
// angegeben werden, der ebenfalls angehängt wird.
// Mit
//  throw FeldUnten_err( "schade!" );
// wirft man eine Ausnahme, die mit what den Text
// "Feldgrenzenüberschreitung\n(unten)\nschade!" liefert.
//
// DERIVEEXCEPTION kann auch innerhalb einer class verwendet werden,
// und erzeugt dementsprechend eine Unterklasse in der umgebenden.
// class MeinTollerContainer
// {
//    DERIVEEXCEPTION( Unsinn_err, runtime_error, "Unsinn in ..." );
// };
// Die erzeugte Klasse für Ausnahmen ist dann
// MeinTollerContainer::Unsinn_err.
//
//////////////////////////////////////////////////////////////////////////
//
// Geschichte:
//////////////
//
// 19.11.2004 kw          für YABA übernommen und kommentiert
//
//
// 24.01.2005 kw          Umbau auf anyware::string anstatt
//                        std::string
//
// 03.06.2009 kw          gekuerzt
//
// 
//
// 
//
// 
//
//////////////////////////////////////////////////////////////////////////
//
// Benoetigt:
/////////////
//
// -
//
//////////////////////////////////////////////////////////////////////////
//
// Umgebung:
////////////
//
// ANSI-C++
//
//////////////////////////////////////////////////////////////////////////


#ifndef _DERIVEEXCEPTION_H_
#define _DERIVEEXCEPTION_H_

#include <string>
typedef std::string   string;

#include <stdexcept>

#ifndef DERIVEEXCEPTION
#define DERIVEEXCEPTION( NAME, BASENAME, TEXT )                         \
  class NAME : public BASENAME                                          \
    {                                                                   \
    public:                                                             \
      NAME( const string &_what = string( #NAME ) )                     \
        : BASENAME( string( TEXT ) + string( "\n" ) + _what )           \
        {}                                                              \
      explicit NAME( const char *_what )                                \
        : BASENAME( string( TEXT ) + string( "\n" ) + _what )           \
        {}                                                              \
    }



#endif /* DERIVEEXCEPTION */

#endif /* _DERIVEEXCEPTION_H_ */

// Local Variables:
// mode:C++
// End:

Übersetzt wird das Ganze unter Linux (g++ 4.3.2) mit:
g++ -Wall -I. testmessage.cpp ABC_Message.cpp -o testmessage && ./testmessage

Programmausgabe:
ich bin ein MessageTyp1: Hallo, ich bin der erste
ich bin ein MessageTyp2: Hallo, ich bin der zweite


Mit dem MS-Compiler habe ich es nicht getestet. Im Prinzip sollte
es gehen, aber ich glaube, der kommt mit den throw-Deklarationen
nicht klar. Im Zweifelsfall einfach weglassen.

Sowohl Sender als auch Empfänger im Netzwerk können intern bei
Bedarf mit denselben Nachrichtentypen arbeiten, müssen es aber
nicht.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Noch als Anmerkung zur ursprünglichen Frage:

Das Ersatzkonzept für die union ist in C++ hier die Ableitung von
einer Basisklasse unter Einsatz virtueller Methoden.

Das funktioniert so, daß man einen Zeiger haben kann, der formal
auf ein Objekt der Basisklasse zeigt, aber auch auf ein Objekt
einer beliebigen davon abgeleiteten Klasse zeigen darf.
Ruft man über einen solchen Zeiger eine Methode auf, dann gibt
es zwei Möglichkeiten:
- die Methode ist nicht als virtual deklariert, dann wird für
  das abgeleitete Objekt leider die Methode der Basisklasse aufgerufen
- die Methode ist virtual, dann wird auch tatsächlich die Methode
  aus der abgeleiteten Klasse verwendet, die zu dem Objekt gehört.
Mit virtual braucht der Aufrufer also gar nicht die abgeleitete
Klasse kennen, kann aber trotzdem über einen Zeiger eine Methode
daraus aufrufen.
Indem man also Zeiger auf verschiedene Datentypen hat, wird erst
zur Laufzeit die Methode ausgesucht, die wirklich zu dem
betreffenden Objekt gehört.
Das ist deutlich eleganter als eine union.

(Mit Referenzen statt Zeigern analog...)

Autor: Klaus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
auf die Knie fall

wow, danke dir!  Das bringt mich doch steil vorran :)

Das muss ich nun erstmal im Detail durcharbeiten ;)

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> ...
> Für jede Art von Nachricht baut man sich eine entsprechende
> Klasse, die sinnvollerweise von einer passenden Basisklasse
> abgeleitet (z.B. ABC_Message) ist. Wahrscheinlich wird man von
> dieser Basisklasse nie ein Objekt schaffen, also virtuell machen
> (ABC = abstract base class).
> ...

Sorry, das soll nicht heißen "virtuell machen" sondern "abstrakt 
machen".

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.