mikrocontroller.net

Forum: Compiler & IDEs Struct Tabelle mit Pointer auf Var/Fkt, wie ansprechen?


Autor: Offlineuser (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin moin Allseits,

ich habe ein frage die zwar nicht ganz zu GCC passt, aber dem am 
nächsten kommt da es eine allgemeine C-Frage ist, denke ich.
Folgendes möchte ich machen und bekommen es nicht ganz gebacken. Ein 
CANopen Verzeichniss soll umgesetzt werden. Dazu habe ich eine Typedef 
Struct angelegt mit Index, SubIndex, Attribute (read, Write, etc.) Typ 
(Uint8, uint16, etc.) und einem pointer (der soll sowohl auf Variable, 
als auch auf Funktion zeigen können, falls das überhaupt geht). Hier 
erst mal der entsprechende Code:
typedef struct
{
  unsigned short    idx;  // index des eintrags
  unsigned char    sub;  // subindex des eintrags
  enum attributes  attr;  // attribute
  enum types      type;  // variablentyp
  const uchar      *pROM;
  
} _CANopen_DICT_Template;

const uchar __dummy[4] = {0,0,0,0};

const _CANopen_DICT_Template _db_object[] = {
  //idx    sub    attributen        typ      ptr auf variable/funktion
  {0x1000,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevType)},
  {0x1001,  0x00,  RO,              u8,    ((const unsigned char *)&uCO_DevErrReg)},
  {0x1002,  0x00,  RO,              u32,    ((const unsigned char *)&uCO_DevManufacturerStatReg)},
//  {0x1005,  0x00,  FUNC | RW,          u32,    ((const unsigned char *)&_CO_COMM_SYNC_COBIDAccessEvent)},
  {0x1008,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevName)},
  {0x1009,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevHardwareVer)},
  {0x100A,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevSoftwareVer)},
//  {0x100C,  0x00,  FUNC | RW,          u16,    ((const unsigned char *)&_CO_COMM_NMTE_GuardTimeAccessEvent)},
//  {0x100D,  0x00,  FUNC | RW,          u8,    ((const unsigned char *)&_CO_COMM_NMTE_LifeFactorAccessEvent)},
//  {0x1017,  0x00,  FUNC | RW,          u16,    ((const unsigned char *)&_CO_COMM_NMTE_HeartBeatAccessEvent)},
  {0x1018,  0x00,  CONST,            u8,    ((const unsigned char *)&rCO_DevIdentityIndx)},
  {0x1018,  0x01,  CONST,            u32,    ((const unsigned char *)&rCO_DevVendorID)},
  {0x1018,  0x02,  CONST,            u32,    ((const unsigned char *)&rCO_DevProductCode)},
  {0x1018,  0x03,  CONST,            u32,    ((const unsigned char *)&rCO_DevRevNo)},    
  {0x1018,  0x04,  CONST,            u32,    ((const unsigned char *)&rCO_DevSerialNo)},

  {0xFFFF,  0x00,  CONST,            u8,    ((const uchar *)&__dummy[0])}    // letzter eintrag
  
};

Die Mitglieder des Structs werden nun ganz normal mit Beispielsweise:
if (_db_object[k].type == u8) { /* tu dies und das*/ }
angesprochen.


Nun möchte ich aber auf meine Variable über den Pointer zugreifen, evtl. 
so:
if (_db_object[k].idx == 0x1018) {
  _db_object[k].*pROM = 0;  // variable nullen
}
Oder eben auf meine Funktion, ungefähr so:
if (_db_object[k].idx == 0x100D) {
  _db_object[k].pROM();  // funktion aufrufen
}
aber bekomme ich nicht gebacken, da ich nicht ganz verstehe wie ich nun 
auf die variable zugreifen kann, bzw. meine Funktion aufrufen kann. Hat 
jemand einen Tipp für mich?

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Offlineuser schrieb:
> Nun möchte ich aber auf meine Variable über den Pointer zugreifen, evtl.
> so:if (_db_object[k].idx == 0x1018) {
>   _db_object[k].*pROM = 0;  // variable nullen
> }

Vielleicht besser so:
   *_db_object[k].pROM = 0;

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Offlineuser schrieb:
> Oder eben auf meine Funktion, ungefähr so:

Welche Funktion?
Ich sehe hier keine.

Wenn es wirklich auf eine Funktion zeigt, muß aber gecastet werden
(da der Zeiger nicht als Zeiger auf eine Funktion deklariert ist,
sondern als Zeiger auf const uchar).
Um den cast zu zeigen, bräuchte man die genaue Deklaration der Funktion.

Autor: Offlineuser (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es ist übrigens ein IAR 68HC12 C-Compiler V2.20A/386 mit dem ich hier 
arbeite.

Der meldet bei
*_db_object[k].pROM = 0;

Folgendes:
*_db_object[k].pROM = 0;                // variable zuerst nullen
-------------^
"canopen.c",364  Error[122]: Modifiable lvalue required
Keine Ahnung was damit gemeint ist.


@Klaus Wachtler:
Die Funktion
void _CO_COMM_NMTE_LifeFactorAccessEvent (void)
 muss man sich im Moment noch dazu denken, die ist noch nicht 
implementiert, da ich zuerst die Variablenaufrufe in den Griff bekommen 
wollte.
Mit casten meinst du in etwa Folgendes?:
(const unsigned char *)&(void _CO_COMM_NMTE_LifeFactorAccessEvent (void)

Wäre es übrigens eine Alternative die Tabelle zu erweitern mit separatem 
Pointer auf Funktion, evtl. so:
typedef struct
{
  unsigned short    idx;  // index des eintrags
  unsigned char    sub;  // subindex des eintrags
  enum attributes  attr;  // attribute
  enum types      type;  // variablentyp
  const uchar      *pROM;
  void ( *pFKT )(void);      //Function pointer
  
} _CANopen_DICT_Template;

const uchar __dummy[4] = {0,0,0,0};

const _CANopen_DICT_Template _db_object[] = {
  //idx    sub    attributen        typ      ptr auf variable/funktion
  {0x1000,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevType), ((void *)&__dummyFKT())},
  {0x1001,  0x00,  RO,              u8,    ((const unsigned char *)&uCO_DevErrReg)), ((void *)&__dummyFKT())},
 {0x1002,  0x00,  RO,              u32,    ((const unsigned char *)&uCO_DevManufacturerStatReg)), ((void *)&__dummyFKT())},
/*
.
.
*/
  {0xFFFF,  0x00,  CONST,            u8,    ((const uchar *)&__dummy[0])), ((void *)&__dummyFKT())}    // letzter eintrag

};

void __dummyFKT (void) {
}

Mit entsprechendem Aufruf für die Funktion?
if (_db_object[k].idx == 0x1002) {
  _db_object[k].pFKT();  // funktion aufrufen
}

Ich muss zugeben, dass ich bei den ganzen Pointern im Moment ziemlich 
verwirrt bin.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Offlineuser schrieb:

> Folgendes:
>
> *_db_object[k].pROM = 0;                // variable zuerst nullen
> -------------^
> "canopen.c",364  Error[122]: Modifiable lvalue required
> 
> Keine Ahnung was damit gemeint ist.
 *(_db_object[k].pROM) = 0;                // variable zuerst nullen

> @Klaus Wachtler:
> Die Funktion
void _CO_COMM_NMTE_LifeFactorAccessEvent (void)
 muss
> man sich im Moment noch dazu denken, die ist noch nicht implementiert,

macht nichts.
Trotzdem muss sie ja eine Funktionssignatur haben.
Welchen Returntype hat sie?
Welche und wieviele Argumente nimmt sie?
Welchen Datentyp haben die Argumente?

Das alles musst du wissen wenn du eine Funktion aufrufen willst.
Das ist auch nicht anders, wenn man die Funktion per Pointer aufruft.

> Mit casten meinst du in etwa Folgendes?:
>
(const unsigned char *)&(void _CO_COMM_NMTE_LifeFactorAccessEvent
> (void)

Nein.
Funktionspointer sind anders.
http://www.mikrocontroller.net/articles/FAQ#Funktionszeiger

> Wäre es übrigens eine Alternative die Tabelle zu erweitern mit separatem
> Pointer auf Funktion, evtl. so:

Ich würde ehrlich gesagt, die ganzen Funktionspointer von den 
Datenpointern komplett trennen. Komplett eigene Struktur. Schon alleine 
die Tatsache, dass du hier wie ein Wilder rumcasten musst, zeigt schon 
dass da was nicht stimmt.
Exzessives Casten ist meistens ein deutliches Indiz, dass der Aufbau der 
Datenstruktur nicht stimmt.


> Ich muss zugeben, dass ich bei den ganzen Pointern im Moment ziemlich
> verwirrt bin.

Das Gefühl habe ich auch. Du übernimmst dich. Du bist noch nicht soweit 
um das in den Griff zu kriegen.

Autor: Offlineuser (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:

> macht nichts.
>
> Trotzdem muss sie ja eine Funktionssignatur haben.
>
> Welchen Returntype hat sie?
>
> Welche und wieviele Argumente nimmt sie?
>
> Welchen Datentyp haben die Argumente?
>
>
>
> Das alles musst du wissen wenn du eine Funktion aufrufen willst.
>
> Das ist auch nicht anders, wenn man die Funktion per Pointer aufruft.

Ist das nicht alles mit:
void _CO_COMM_NMTE_LifeFactorAccessEvent (void) {}
 bzw.
void __dummyFKT (void) {}
 erledigt? Keine Übergabeparameter und void als return.


> Nein.
>
> Funktionspointer sind anders.
>
> http://www.mikrocontroller.net/articles/FAQ#Funktionszeiger

Ok, wenn ich das richtig verstehe, müsste ich das so realisieren:
typedef void (*VoidFnct)( void );
const uchar __dummy[4] = {0,0,0,0};


typedef struct
{
  unsigned short    idx;  // index des eintrags
  unsigned char     sub;  // subindex des eintrags
  enum attributes   attr;  // attribute
  enum types        type;  // variablentyp
  const uchar       *pROM;
  VoidFnct          Function;
} _CANopen_DICT_Template;


const _CANopen_DICT_Template _db_object[] = {
  //idx    sub    attributen        typ      ptr auf variable/funktion
  {0x1000,  0x00,  CONST,            u32,    ((const unsigned char *)&rCO_DevType), __dummyFKT},
  {0x1001,  0x00,  RO,              u8,    ((const unsigned char *)&uCO_DevErrReg), __dummyFKT},
 {0x1002,  0x00,  RO,              u32,    ((const unsigned char *)&uCO_DevManufacturerStatReg), __dummyFKT},
/*
.
.
*/
  {0xFFFF,  0x00,  CONST,            u8,    ((const uchar *)&__dummy[0]),  __dummyFKT}    // letzter eintrag

};

void __dummyFKT (void) {
}

Mit solchem Aufruf:
if (_db_object[k].idx == 0x1002) {
  _db_object[k].Function();  // funktion aufrufen
}
Sieht das plausibel aus?


> Ich würde ehrlich gesagt, die ganzen Funktionspointer von den
>
> Datenpointern komplett trennen. Komplett eigene Struktur. Schon alleine
>
> die Tatsache, dass du hier wie ein Wilder rumcasten musst, zeigt schon
>
> dass da was nicht stimmt.
>
> Exzessives Casten ist meistens ein deutliches Indiz, dass der Aufbau der
>
> Datenstruktur nicht stimmt.

Komplett eigene Struktur für Funktionen und Variablen möchte ich 
vermeiden. Die Tabelle soll flexibel sein und je nach Eintrag mal das 
eine mal das andere ausführen können.
Aber der Kompromis mit eigenem Eintrag für Variable und Funktion ist ein 
guter Mittelweg, denke ich. Den Versuch der Umsetzung ist ein Absatz 
höher aufgeführt....



>> Ich muss zugeben, dass ich bei den ganzen Pointern im Moment ziemlich
>
>> verwirrt bin.
>
> Das Gefühl habe ich auch. Du übernimmst dich. Du bist noch nicht soweit
>
> um das in den Griff zu kriegen.

Genau deswegen wende ich mich ja an euch und versuche zu lernen und das 
in den Griff zu bekommen.
Danke schon mal an alle an dieser Stelle für ihre Unterstützung!

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Offlineuser schrieb:
> Mit solchem Aufruf:
> if (_db_object[k].idx == 0x1002) {
>   _db_object[k].Function();  // funktion aufrufen
> }
> Sieht das plausibel aus?

So in etwa, ja.
Dabei muß aber noch __dummyFKT vor der ersten Verwendung deklariert
werden.

In deiner ersten Version hattest du jeweils einen Eintrag in
der struct weniger, indem du pROM mal als Zeiger auf Daten und mal
als Zeiger auf eine Funktion genutzt hattest.
Wenn sichergestellt ist, daß wirklich immer nur der eine oder
der andere Wert genutzt wird und du am Zusammenhang erkennen kannst,
welches der beiden jeweils drin steht, kann man das auch mit einem
Element erledigen. Dazu sollte man dann eine union aus dem
Funktionszeiger und dem Zeiger auf die Daten machen.
Solange der Platz aber nicht zu knapp ist im Programm, würde
ich aber nicht dazu raten. Solche Mehrfachnutzungen machen
das Programm nicht gerade übersichtlicher und dadurch
fehleranfälliger.

Abgesehen davon kommt mir das Programm auch nicht so ganz
elegant vor (das kann aber auch daran liegen, daß mnan nur
wenig davon sieht).
Z.B. würde ich (soweit man den Wert nicht braucht) nicht
ein Dummy-Feld oder eine Dummy-Funktion nehmen, sondern einfach
NULL. Dann darf man natürlich auch nicht darauf zugreifen, aber
es ist offenkundig, daß man den Wert nicht nutzen will.
Will man wirklich den Wert haben und nutzen, ist irgendwas
mit dummy im Namen eher eine schlechte Wahl.
Naja, nicht die einzige schlechte Wahl zur Zeit.

Autor: Offlineuser (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> So in etwa, ja.
> Dabei muß aber noch __dummyFKT vor der ersten Verwendung deklariert
> werden.
Jo, das ist klar.

> In deiner ersten Version hattest du jeweils einen Eintrag in
> der struct weniger, indem du pROM mal als Zeiger auf Daten und mal
> als Zeiger auf eine Funktion genutzt hattest.
> Wenn sichergestellt ist, daß wirklich immer nur der eine oder
> der andere Wert genutzt wird und du am Zusammenhang erkennen kannst,
> welches der beiden jeweils drin steht, kann man das auch mit einem
> Element erledigen. Dazu sollte man dann eine union aus dem
> Funktionszeiger und dem Zeiger auf die Daten machen.
> Solange der Platz aber nicht zu knapp ist im Programm, würde
> ich aber nicht dazu raten. Solche Mehrfachnutzungen machen
> das Programm nicht gerade übersichtlicher und dadurch
> fehleranfälliger.
Ja, stimmt, das sehe ich mittlerweile genau so und habe die Tabelle nun 
um getrennte Variablen und Funktionszeiger erweitert.


> Abgesehen davon kommt mir das Programm auch nicht so ganz
> elegant vor (das kann aber auch daran liegen, daß mnan nur
> wenig davon sieht).
> Z.B. würde ich (soweit man den Wert nicht braucht) nicht
> ein Dummy-Feld oder eine Dummy-Funktion nehmen, sondern einfach
> NULL. Dann darf man natürlich auch nicht darauf zugreifen, aber
> es ist offenkundig, daß man den Wert nicht nutzen will.
> Will man wirklich den Wert haben und nutzen, ist irgendwas
> mit dummy im Namen eher eine schlechte Wahl.
> Naja, nicht die einzige schlechte Wahl zur Zeit.
Das mit Null hatte ich mir auch überlegt und man könnte natürlich auch 
den Pointer vor dem Zugriff auch auf NULL abfragen, außerdem hat die 
tabelle auch einen Eintrag der besagt was auszuführen ist. Naja, mal 
sehen.

Vielen Dank nochmals für eure Unterstützung!

Ich denke ich habe das nun soweit ausgeknobbelt, dass es einwandfrei 
funktioniert (zumindest die hier im Thread angesprochenen Funktionen). 
Und ich habe entsprechend ein kleines Testprogramm geschrieben, das man 
im Simulator laufen lassen kann um dies gegenzuchecken (im MPLAB IDE mit 
C30 Compiler funktioniert alles exact wie gewünscht). Für die 
Interessierten hier noch das Testprogramm:

main.c

#include <p30Fxxxx.h>
#include <stdio.h>
#include "canopen_defs.h"


int main (void)
{
  unsigned char  ucTest = 0;
  unsigned short  usTest = 0;
  unsigned long  ulTest = 0, ulTest2 = 0;
  
  unsigned char *pChar;
  
  _CANopen_DICT_Template localObject;
  
  for (;;)
  {
    //__asm__("NOP");
    
    // test, wie viele einträge hat die tabelle?
    ucTest = sizeof(_db_object)/sizeof(localObject);

    //---------------------------
    // aus dem speicher in can buffer schreiben
    //---------------------------
    // unsigned char    
    ucTest = (unsigned char)(*_db_object[1].pVar);
    
    // unsigned short, 2 byte
    pChar = _db_object[2].pVar;
    usTest = (unsigned short)(*pChar++);
    usTest += ((unsigned short)(*pChar))<<8;
    
    // unsigned long, 4 byte
    pChar = _db_object[0].pVar;
    ulTest =   (unsigned long)(*pChar++);
    ulTest += ((unsigned long)(*pChar++))<<8;
    ulTest += ((unsigned long)(*pChar++))<<16;
    ulTest += ((unsigned long)(*pChar))<<24;
    
    
    //---------------------------
    // aus dem Buffer etwas in den speicher schreiben
    //---------------------------
    // fiktiven CAN-Buffer füllen
    ulTest2 = 0x12345678;
    // vom CAN-Buffer in speicher holen, unsigned long
    pChar = _db_object[2].pVar;
    *pChar++ = ulTest2;
    *pChar++ = ulTest2>>8;
    *pChar++ = ulTest2>>16;
    *pChar = ulTest2>>24;


    //---------------------------
    // ein paar funktionen ausführen
    //---------------------------
    _db_object[0].pFkt();
    _db_object[3].pFkt();
    _db_object[7].pFkt();
  }
  
}


void CO_COBIDAccessEvent (void) {
}

void CO_GuardTimeAccessEvent (void) {
}

void CO_LifeFactorAccessEvent (void) {
}

void CO_HeartBeatAccessEvent (void) {
}



void __dummyFKT (void) {
}


canopen_defs.h

enum attributes          // Memory access type
{  //              ls
  NA      = 0x00,//0b00000000,  // Default, non-existant
  CONST    = 0x40,//0b01000000,  // Default, read only from ROM
  RW      = 0x60,//0b01100000,  // Default, read/write
  RO      = 0x40,//0b01000000,  // Default, read only
  WO      = 0x20 //0b00100000,  // Default, write only
};
  
enum types            // Variable type 
{
  u8    = 0x0F,//0b00001111,
  u16  = 0x0B,//0b00001011,
  u32  = 0x03,//0b00000011,
  fkt  = 0x80 //0b10000000    // funktionsaufruf
};


typedef void (*VoidFnct)( void );

typedef struct
{
  unsigned short    idx;    // index des eintrags
  unsigned char    sub;    // subindex des eintrags
  enum attributes  attr;    // attribute
  enum types      type;    // variablentyp
  unsigned char    *pVar;
  VoidFnct          pFkt;    //Function of Entry
  
} _CANopen_DICT_Template;


  
// CANopen object 0x1000
const unsigned long rCO_DevType =         0x8934AL;

// CANopen object 0x1008
const unsigned char rCO_DevName[] =       "Sens";

// CANopen object 0x1009
const unsigned char rCO_DevHardwareVer[] =   "V1.0";

// CANopen object 0x100A
const unsigned char rCO_DevSoftwareVer[] =   "V1.0";

// CANopen object 0x1018
const unsigned char rCO_DevIdentityIndx =   0x4;
const unsigned long rCO_DevVendorID =       0x12345678L;
const unsigned long rCO_DevProductCode =     0x555AAA11L;
const unsigned long rCO_DevRevNo =         0x10305070L;
const unsigned long rCO_DevSerialNo =       0x87654321L;

// CANopen object 0x1001
unsigned char uCO_DevErrReg;

// CANopen object 0x1002
unsigned long uCO_DevManufacturerStatReg;


unsigned char __dummy[4] = {0,0,0,0};
void __dummyFKT (void);
void CO_COBIDAccessEvent (void);
void CO_GuardTimeAccessEvent (void);
void CO_LifeFactorAccessEvent (void);
void CO_HeartBeatAccessEvent (void);


_CANopen_DICT_Template _db_object[] = {
  //idx    sub    attributen      typ    ptr auf variable                        funktion
  {0x1000,  0x00,  CONST,        u32,  (unsigned char *)&rCO_DevType,                  __dummyFKT},
  {0x1001,  0x00,  RO,          u8,    &uCO_DevErrReg,                          __dummyFKT},
  {0x1002,  0x00,  RO,          u32,  (unsigned char *)&uCO_DevManufacturerStatReg,          __dummyFKT},
  {0x1005,  0x00,  RW,        u32 | fkt,  (unsigned char *)&__dummy[0],                  CO_COBIDAccessEvent},
  {0x1008,  0x00,  CONST,        u32,  (unsigned char *)&rCO_DevName,                  __dummyFKT},
  {0x1009,  0x00,  CONST,        u32,  (unsigned char *)&rCO_DevHardwareVer,              __dummyFKT},
  {0x100A,  0x00,  CONST,        u32,  (unsigned char *)&rCO_DevSoftwareVer,              __dummyFKT},
  {0x100C,  0x00,  RW,        u16 | fkt,  (unsigned char *)&__dummy[0],                  CO_GuardTimeAccessEvent},
  {0x100D,  0x00,  RW,         u8 | fkt,  (unsigned char *)&__dummy[0],                  CO_LifeFactorAccessEvent},
  {0x1017,  0x00,  RW,        u16 | fkt,  (unsigned char *)&__dummy[0],                  CO_HeartBeatAccessEvent},
  {0x1018,  0x00,  CONST,        u8,    (unsigned char *)&rCO_DevIdentityIndx,              __dummyFKT},
  {0x1018,  0x01,  CONST,        u32,  (unsigned char *)&rCO_DevVendorID,                __dummyFKT},
  {0x1018,  0x02,  CONST,        u32,  (unsigned char *)&rCO_DevProductCode,              __dummyFKT},
  {0x1018,  0x03,  CONST,        u32,  (unsigned char *)&rCO_DevRevNo,                  __dummyFKT},    
  {0x1018,  0x04,  CONST,        u32,  (unsigned char *)&rCO_DevSerialNo,                __dummyFKT}
};

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.