mikrocontroller.net

Forum: Compiler & IDEs Struc as header einbinden - Compiler dreht durch


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Chris K. (deadeye5589)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Hallo Forum,

wer kann mit bezüglich Programmierung in C und insbesondere Arduino auf 
die Sprünge helfen?

Ich habe folgendes Problem, bei dem ich ein bestehendes ESP8266 Projekt 
auf meine Bedürfnisse abändern muss. In dem Projekt, welches in seinem 
Ausgangszustand erfolgreich kompiliert, gibt es eine Datei Namens 
store.h
#ifndef store_h
#define store_h

// Change this if the settings structure changes
#define CONFIG_VERSION "b5g"

// Dont change this
#define CONFIG_START 0

enum fx_mode {
  FX_MODE_PIXEL_MAP = 0,
  FX_MODE_12 = 1
};

enum p_type {
  TYPE_DMX_OUT = 0,
  TYPE_RDM_OUT = 1,
  TYPE_DMX_IN = 2,
  TYPE_WS2812 = 3,
  TYPE_SK6812 = 4
};

enum p_protocol {
  PROT_ARTNET = 0,
  PROT_ARTNET_SACN = 1
};

enum p_merge {
  MERGE_LTP = 0,
  MERGE_HTP = 1
};

struct StoreStruct {
  // StoreStruct version
  char version[4];

  // Device settings:
  IPAddress ip, subnet, gateway, broadcast, hotspotIp, hotspotSubnet, hotspotBroadcast, dmxInBroadcast;
  bool dhcpEnable, standAloneEnable;
  char nodeName[18], longName[64], wifiSSID[40], wifiPass[40], hotspotSSID[20], hotspotPass[20];
  uint16_t hotspotDelay;
  uint8_t portAmode, portBmode, portAprot, portBprot, portAmerge, portBmerge;
  uint8_t portAnet, portAsub, portAuni[4], portBnet, portBsub, portBuni[4], portAsACNuni[4], portBsACNuni[4];
  uint16_t portAnumPix, portBnumPix, portApixConfig, portBpixConfig;
  bool doFirmwareUpdate;
  uint8_t portApixMode, portBpixMode;
  uint16_t portApixFXstart, portBpixFXstart;
  uint8_t resetCounter, wdtCounter;
  
} deviceSettings = {
  CONFIG_VERSION,
  
  // The default values
  IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,0,0,1), IPAddress(2,255,255,255), IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,255,255,255), IPAddress(2,255,255,255),
  true, false,
  "espArtNetNode", "espArtNetNode by Matthew Tong", "", "", "espArtNetNode", "byMtongnz2017",
  15,
  TYPE_DMX_OUT, TYPE_DMX_OUT, PROT_ARTNET, PROT_ARTNET, MERGE_HTP, MERGE_HTP,
  0, 0, {0, 1, 2, 3}, 0, 0, {4, 5, 6, 7}, {1, 2, 3, 4}, {5, 6, 7, 8},
  680, 680, 0, 0,
  false,
  FX_MODE_PIXEL_MAP, FX_MODE_PIXEL_MAP,
  1, 1,
  0, 0
};


void eepromSave() {
  for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
    EEPROM.write(CONFIG_START + t, *((char*)&deviceSettings + t));
  
  EEPROM.commit();
}

void eepromLoad() {
  // To make sure there are settings, and they are YOURS!
  // If nothing is found it will use the default settings.

  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2]) {

    // Store defaults for if we need them
    StoreStruct tmpStore;
    tmpStore = deviceSettings;
    
    // Copy data to deviceSettings structure
    for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
      *((char*)&deviceSettings + t) = EEPROM.read(CONFIG_START + t);
    
    // If we want to restore all our settings
    if (deviceSettings.resetCounter >= 5 || deviceSettings.wdtCounter >= 10) {
      deviceSettings.wdtCounter = 0;
      deviceSettings.resetCounter = 0;

      // Store defaults back into main settings
      deviceSettings = tmpStore;
    }


  // If config files dont match, save defaults then erase the ESP config to clear away any residue
  } else {
    eepromSave();
    delay(500);
    
    ESP.eraseConfig();
    while(1);
  }
}

#endif

Wie ihr sehen könnt, sorgt diese store.h Datei im wesentlichen für zwei 
Dinge. Zum einen gibt es ein paar Funktionen, die EEPROM Zugriffe 
umsetzten. Dann gibt es noch eine Struktur, die Programmparameter 
speichert und welche auch gleich in der store.h initialisiert und mit 
default Werten geladen wird. Genau diese Programmparameter aus der 
Struktur benötige ich nun an anderer Stelle im Quelltext als der Main 
Datei. Und das will nicht klappen.

1. Problem:
Ich inkludiere die Store.h in einer weiteren Quelltext Datei. Nun 
meckert Arduino an, dass die Funktion eeprom.read nicht bekannt sei. Nun 
diese entstammt der Arduino eigenen EEPROM.h und wird in der Store.h 
auch wirklich nicht inkludiert. Fügt man
#include <EEPROM.h>

zur store.h hinzu erhält man Problem 2:
sketch\ws2812Driver.cpp.o:(.data.deviceSettings+0x0): multiple definition of `deviceSettings'

sketch\source.ino.cpp.o:(.data.deviceSettings+0x0): first defined here

sketch\ws2812Driver.cpp.o: In function `ws2812Driver::ws2812Driver()':

sketch/store.h:50: multiple definition of `eepromSave()'

sketch\source.ino.cpp.o:sketch/store.h:85: first defined here

sketch\ws2812Driver.cpp.o: In function `ws2812Driver::doPixelDouble(unsigned char*, unsigned char, unsigned char*, unsigned char, unsigned short)':

sketch/store.h:92: multiple definition of `eepromLoad()'

sketch\source.ino.cpp.o:sketch/store.h:92: first defined here

collect2.exe: error: ld returned 1 exit status

Die source.ino ist die Hautdatei des ganzen Programmes und dort werden 
sowohl die EEPROM.h als Header als auch die Store.h bislang eingebunden.

Ich verstehe nur nicht, warum ich die store.h nicht auch noch woanders 
einbinden kann. Das ist doch der Sinn von Headern und eigentlich sollten 
so doch auch Klassen und Strukturen im Programm überall bekannt gemacht 
werden können.

Jemand eine Idee, wie man diese bestimmt sehr triviale Problematik 
richtig auflösen kann?

Autor: A. K. (prx)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Deklaration von Typen, Variablen und Funktionen sind in .h richtig. Die 
Definition von Variablen (also mit Inhalt, implizit oder explizit) 
hingegen nicht, ebensowenig Funktionsdefinitionen, weil:
  multiple definition of `deviceSettings'
  multiple definition of `eepromLoad()'

: Bearbeitet durch User
Autor: Der Andere (Gast)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Chris K. schrieb:
> Wie ihr sehen könnt, sorgt diese store.h Datei im wesentlichen für zwei
> Dinge. Zum einen gibt es ein paar Funktionen, die EEPROM Zugriffe
> umsetzten. Dann gibt es noch eine Struktur, die Programmparameter
> speichert und welche auch gleich in der store.h initialisiert und mit
> default Werten geladen wird.

Und genau das ist Scheiße und gegen alle Regeln der C Programmierung.
Man trennt Deklarationen von Strukturen, makros und Typedefs von der 
Implementation.
Die Deklaration in ein .h file, die Implementation in ein .c File, das 
dann einmal compiliert und zu dem Projekt dazugelinkt wird.

Besorge die ein C Buch und arbeite es von vorne durch. Alles andere wird 
nix.

Beitrag #5436623 wurde vom Autor gelöscht.
Autor: Richtig Steller (Gast)
Datum:

Bewertung
4 lesenswert
nicht lesenswert
Chris K. schrieb:
> Struc as header einbinden - Compiler dreht durch

Nein, er dreht nicht durch sondern er meldet dir einfach
dass du dich nicht an die Regeln hältst.

Und das ist gut so. Es hilft dir dein Unwissen und Chaos
zu bewältigen.

Autor: Chris K. (deadeye5589)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Gut,

ich sehe ein, dass der Compiler es nicht mag, wenn die gesamte Funktion 
im Header definiert wird und dann der Header an mehreren Stellen 
eingebunden wird. Hilft mir jetzt aber leider auch nicht dabei weiter 
das Problem zu lösen, dass der ursprüngliche Verfasser des Quelltextes 
eingebaut hat.

Die erste Baustelle ist also die store.h aufzuräumen, damit die 
Definition aus der Header Datei verschwindet.
Naiver Ansatz also:
#ifndef store_h
#define store_h

// Change this if the settings structure changes
#define CONFIG_VERSION "b5g"

// Dont change this
#define CONFIG_START 0

enum fx_mode {
  FX_MODE_PIXEL_MAP = 0,
  FX_MODE_12 = 1
};

enum p_type {
  TYPE_DMX_OUT = 0,
  TYPE_RDM_OUT = 1,
  TYPE_DMX_IN = 2,
  TYPE_WS2812 = 3,
  TYPE_SK6812 = 4
};

enum p_protocol {
  PROT_ARTNET = 0,
  PROT_ARTNET_SACN = 1
};

enum p_merge {
  MERGE_LTP = 0,
  MERGE_HTP = 1
};


struct StoreStruct {
  // StoreStruct version
  char version[4];

  // Device settings:
  IPAddress ip, subnet, gateway, broadcast, hotspotIp, hotspotSubnet, hotspotBroadcast, dmxInBroadcast;
  bool dhcpEnable, standAloneEnable;
  char nodeName[18], longName[64], wifiSSID[40], wifiPass[40], hotspotSSID[20], hotspotPass[20];
  uint16_t hotspotDelay;
  uint8_t portAmode, portBmode, portAprot, portBprot, portAmerge, portBmerge;
  uint8_t portAnet, portAsub, portAuni[4], portBnet, portBsub, portBuni[4], portAsACNuni[4], portBsACNuni[4];
  uint16_t portAnumPix, portBnumPix, portApixConfig, portBpixConfig;
  bool doFirmwareUpdate;
  uint8_t portApixMode, portBpixMode;
  uint16_t portApixFXstart, portBpixFXstart;
  uint8_t resetCounter, wdtCounter;
  
}; 

extern StoreStruct deviceSettings; 

extern void eepromSave(void);
extern void eepromLoad(void);

#endif

und dann eine store.c Datei erzeugen, in der die Funktionen 
ausgearbeitet werden:
#include "store.h"
#include <EEPROM.h>

struct StoreStruct {
  // StoreStruct version
  char version[4];

  // Device settings:
  IPAddress ip, subnet, gateway, broadcast, hotspotIp, hotspotSubnet, hotspotBroadcast, dmxInBroadcast;
  bool dhcpEnable, standAloneEnable;
  char nodeName[18], longName[64], wifiSSID[40], wifiPass[40], hotspotSSID[20], hotspotPass[20];
  uint16_t hotspotDelay;
  uint8_t portAmode, portBmode, portAprot, portBprot, portAmerge, portBmerge;
  uint8_t portAnet, portAsub, portAuni[4], portBnet, portBsub, portBuni[4], portAsACNuni[4], portBsACNuni[4];
  uint16_t portAnumPix, portBnumPix, portApixConfig, portBpixConfig;
  bool doFirmwareUpdate;
  uint8_t portApixMode, portBpixMode;
  uint16_t portApixFXstart, portBpixFXstart;
  uint8_t resetCounter, wdtCounter;
  
} deviceSettings = {
  CONFIG_VERSION,
  
  // The default values
  IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,0,0,1), IPAddress(2,255,255,255), IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,255,255,255), IPAddress(2,255,255,255),
  true, false,
  "espArtNetNode", "espArtNetNode by Matthew Tong", "", "", "espArtNetNode", "byMtongnz2017",
  15,
  TYPE_DMX_OUT, TYPE_DMX_OUT, PROT_ARTNET, PROT_ARTNET, MERGE_HTP, MERGE_HTP,
  0, 0, {0, 1, 2, 3}, 0, 0, {4, 5, 6, 7}, {1, 2, 3, 4}, {5, 6, 7, 8},
  680, 680, 0, 0,
  false,
  FX_MODE_PIXEL_MAP, FX_MODE_PIXEL_MAP,
  1, 1,
  0, 0
};


void eepromSave() {
  for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
    EEPROM.write(CONFIG_START + t, *((char*)&deviceSettings + t));
  
  EEPROM.commit();
}

void eepromLoad() {
  // To make sure there are settings, and they are YOURS!
  // If nothing is found it will use the default settings.

  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2]) {

    // Store defaults for if we need them
    StoreStruct tmpStore;
    tmpStore = deviceSettings;
    
    // Copy data to deviceSettings structure
    for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
      *((char*)&deviceSettings + t) = EEPROM.read(CONFIG_START + t);
    
    // If we want to restore all our settings
    if (deviceSettings.resetCounter >= 5 || deviceSettings.wdtCounter >= 10) {
      deviceSettings.wdtCounter = 0;
      deviceSettings.resetCounter = 0;

      // Store defaults back into main settings
      deviceSettings = tmpStore;
    }


  // If config files dont match, save defaults then erase the ESP config to clear away any residue
  } else {
    eepromSave();
    delay(500);
    
    ESP.eraseConfig();
    while(1);
  }
}

Ist aber auch blöd, weil es so die Deklaration der Struktur 2 mal gibt 
und das macht ja eigentlich auch keinen Sinn. Aber wie muss die store.c 
aussehen, wenn dort der Konstruktor für die Struktur sein soll?

Autor: foobar (Gast)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
> Ist aber auch blöd, weil es so die Deklaration der Struktur 2 mal gibt

Dann lass es halt sein! Im .c-File ist sie unnötig, gar falsch ;-)

Ins .c-File einfach nur nen
struct StoreStruct deviceSettings = { CONFIG_VERSION, ... };

Autor: foobar (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Btw:

[c]
-extern StoreStruct deviceSettings;
+extern struct StoreStruct deviceSettings;
[/c[

Autor: Chris K. (deadeye5589)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke foobar,

habe die Dateien umgebaut und kotze schon wieder im Strahl:

store.h
ifndef store_h
#define store_h

#include <ESP8266WiFi.h>

// Change this if the settings structure changes
#define CONFIG_VERSION "b5g"

// Dont change this
#define CONFIG_START 0

enum fx_mode {
  FX_MODE_PIXEL_MAP = 0,
  FX_MODE_12 = 1
};

enum p_type {
  TYPE_DMX_OUT = 0,
  TYPE_RDM_OUT = 1,
  TYPE_DMX_IN = 2,
  TYPE_WS2812 = 3,
  TYPE_SK6812 = 4
};

enum p_protocol {
  PROT_ARTNET = 0,
  PROT_ARTNET_SACN = 1
};

enum p_merge {
  MERGE_LTP = 0,
  MERGE_HTP = 1
};


struct StoreStruct {
  // StoreStruct version
  char version[4];

  // Device settings:
  IPAddress ip, subnet, gateway, broadcast, hotspotIp, hotspotSubnet, hotspotBroadcast, dmxInBroadcast;
  bool dhcpEnable, standAloneEnable;
  char nodeName[18], longName[64], wifiSSID[40], wifiPass[40], hotspotSSID[20], hotspotPass[20];
  uint16_t hotspotDelay;
  uint8_t portAmode, portBmode, portAprot, portBprot, portAmerge, portBmerge;
  uint8_t portAnet, portAsub, portAuni[4], portBnet, portBsub, portBuni[4], portAsACNuni[4], portBsACNuni[4];
  uint16_t portAnumPix, portBnumPix, portApixConfig, portBpixConfig;
  bool doFirmwareUpdate;
  uint8_t portApixMode, portBpixMode;
  uint16_t portApixFXstart, portBpixFXstart;
  uint8_t resetCounter, wdtCounter;
  
}; 

extern struct StoreStruct deviceSettings;

extern void eepromSave(void);
extern void eepromLoad(void);

#endif


und die store.cpp
#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include "store.h"



struct StoreStruct deviceSettings = {
  CONFIG_VERSION,
  
  // The default values
  IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,0,0,1), IPAddress(2,255,255,255), IPAddress(2,0,0,1), IPAddress(255,0,0,0), IPAddress(2,255,255,255), IPAddress(2,255,255,255),
  true, false,
  "espArtNetNode", "espArtNetNode by Matthew Tong", "", "", "espArtNetNode", "byMtongnz2017",
  15,
  TYPE_DMX_OUT, TYPE_DMX_OUT, PROT_ARTNET, PROT_ARTNET, MERGE_HTP, MERGE_HTP,
  0, 0, {0, 1, 2, 3}, 0, 0, {4, 5, 6, 7}, {1, 2, 3, 4}, {5, 6, 7, 8},
  680, 680, 0, 0,
  false,
  FX_MODE_PIXEL_MAP, FX_MODE_PIXEL_MAP,
  1, 1,
  0, 0
};


void eepromSave() {
  for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
    EEPROM.write(CONFIG_START + t, *((char*)&deviceSettings + t));
  
  EEPROM.commit();
}

void eepromLoad() {
  // To make sure there are settings, and they are YOURS!
  // If nothing is found it will use the default settings.

  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2]) {

    // Store defaults for if we need them
    StoreStruct tmpStore;
    tmpStore = deviceSettings;
    
    // Copy data to deviceSettings structure
    for (uint16_t t = 0; t < sizeof(deviceSettings); t++)
      *((char*)&deviceSettings + t) = EEPROM.read(CONFIG_START + t);
    
    // If we want to restore all our settings
    if (deviceSettings.resetCounter >= 5 || deviceSettings.wdtCounter >= 10) {
      deviceSettings.wdtCounter = 0;
      deviceSettings.resetCounter = 0;

      // Store defaults back into main settings
      deviceSettings = tmpStore;
    }


  // If config files dont match, save defaults then erase the ESP config to clear away any residue
  } else {
    eepromSave();
    delay(500);
    
    ESP.eraseConfig();
    while(1);
  }
}

nun meckert der Compiler zurecht, dass er die "variable" IPAdresse in 
der Struktur nicht auflösen kann. Muss auch wieder sowas Adurino 
spezifisches sein. Scheinbar wird die in der Header Datei ESP8266WiFi.h 
beschrieben. Diese ist in der Main auch inkludiert. Will ich die 
zusätzlich in der Store.h mit einbinden erhält man folgenden Fehler

...AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libra 
ries\ESP8266WiFi\src/ESP8266WiFiGeneric.h:27:22:  fatal error: 
functional: No such file or directory

 #include <functional>

Argh... als Programmieranfänger kann einen so etwas wirklich dazu 
bringen in die Tischkante zu beißen. Warum hat er denn keine Probleme 
mit dem Inkludieren in der Main Datei, aber in einer anderen Header 
Datei?

Autor: Joachim B. (jar)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Chris K. schrieb:
> Danke foobar,
> habe die Dateien umgebaut und kotze schon wieder im Strahl:
>
> store.h
>
> ifndef store_h
> #define store_h

wie wäre es mit # vor ifndef?

Autor: Richtig Steller (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Chris K. schrieb:
> Argh... als Programmieranfänger kann einen so etwas wirklich dazu
> bringen in die Tischkante zu beißen.

Nein, du hast soeben eine Situation erlebt die dir die
Macher des Arduino-Environments eingebrockt haben.

Autor: Der Andere (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Chris K. schrieb:
> und kotze schon wieder im Strahl

Dagegen hilft Vomex oder ein gutes C-Buch.
Allerdings letzteres nicht einnehmen sondern geistig inhalieren.

Du musst die Konzepte verstehen, sonst wird das nix, sorry aber 
programmieren in C ist nix für "maker".

Autor: Chris K. (deadeye5589)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Joachim,

ist drin. Fehlte beim copy und paste des Quelltextes.

Autor: Random .. (thorstendb) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du kannst das Problem "lösen", indem du das chaotische Headerfile nur 
genau ein Mal #include-ierst. Schön wird es deswegen trotzdem nicht. 
Variablen gehören allenfalls mit "extern" in ein Headerfile (Ausnahme: 
inline).

Die fehlenden Funktionsexports musst du dir dann über ein anderes 
Headerfile exportieren.

: Bearbeitet durch User
Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Chris K. schrieb:
> Hilft mir jetzt aber leider auch nicht dabei weiter
> das Problem zu lösen, dass der ursprüngliche Verfasser des Quelltextes
> eingebaut hat.

Und was hindert dich dran, das Problem auszubauen? Du hast den gesamten 
Quelltext doch vor dir. Also teile die Datei in eine Header- und eine 
Sourcedatei auf, und alles wird gut.

Oliver

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.