Problème avec fichier annexe *.cpp

Bonjour à toutes et à tous,

Dans une application, je voudrais placer une partie du programme dans un fichier annexe au fichier *.ino.

Ce fichier annexe comporterait une classe d'objets.

J'ai d'abord placé cette classe dans le fichier *.ino et l'ai compilé : pas de problème, voir ci-après :

class UneClasse {
  public:
    UneClasse() {};
    void faire() {

    };
};

UneClasse maClasse;

void setup() {
  maClasse.faire();
}

void loop() {

}

Maintenant, je déplace cette classe dans un fichier joint "desClasses.cpp" :

#include <Arduino.h>

class UneClasse {
  public:
    UneClasse() {};
    void faire() {

    };
};

et je modifie mon appel à cette classe dans le fichier *.ino :

extern class UneClasse maClasse;

void setup() {
  maClasse.faire();
}

void loop() {

}

A la compilation, j'obtiens les messages d'erreur suivants :

Arduino : 1.6.7 (Windows XP), Carte : "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

C:\Documents and Settings\Pierre\Mes documents\Arduino\Test_01\Test_01.ino: In function 'void setup()':

Test_01:6: error: invalid use of incomplete type 'class UneClasse'

   maClasse.faire();

           ^

Test_01:3: error: forward declaration of 'class UneClasse'

 extern class UneClasse maClasse;

              ^

exit status 1
invalid use of incomplete type 'class UneClasse'

J'avoue que je ne sais pas interpréter ces messages et en déduire ce qui ne va pas.

Si vous pouviez m'apporter un petit éclairage.

Cordialement.

Pierre

si je ne dis pas de bétises (mais il est fort probable que j'en dise) l'IDE arduino avant d'envoyer le fichier ino au compilateur l'analyse et crée un .h automatiquement généré avec toutes les déclarations utiles. Mais il ne le fait que pour le .ino, si tu as des fichiers .cpp en plus, il faut le faire manuellement, comme avec le .h d'une librairie : tu met les prototypes de tes classes dans le fichier.h, tu inclus avec #include "ton_truc.h" et ça fonctionne, sans besoin d'utiliser "extern" autant que je sache.

Bonjour,

Il faut que tu définisses ta classe dans un fichier .h et que tu l'inclues dans ton fichier .ino et dans ton fichier .cpp

La déclaration que tu utilises est une déclaration partielle de la classe. Elle est en général utilisée quand on définit un pointeur sur cette classe dans une autre classe et qu'on ne veut pas inclure la définition de classe entière.
Mais pour utiliser la classe (par exemple appel des fonctions membres), il faut que le compilateur connaisse la définition complète de la classe, donc il faut inclure la définition complète.

Pour continuer les infos de Bricofoy et de Kamill ce qui est envoyé au compilateur est un vrai fichier C/C++

Pour ce faire l'IDE réalise un certain nombre d'actions sans le dire mais uniquement sur le fichier ino:

  • Prédéclaration de toutes les fonctions dont bien entendu setup() et loop()
  • Création de la fonction obligatoire main()

La structure du fichier qui est envoyé au compilateur est (en gros) :

reprise de la liste des #include
reprise de la liste de déclaration de variables utilisateur
Ajout des déclarations des fonctions setup(), loop() plus toutes les fonctions utilisateur comprises dans le fichier ino

// création fichier main
void main()
{
init(); // Configuration des registres du micro pour le mode Arduino (Timers, config ADC, config UART, etc.....)
setup();
for( ; ; ) // --> création de la boucle infinie
{
loop();
}
}

C'est un peu dommage que cette information ne soit pas plus voyante sur le site Arduino. A trop vouloir simplifier la tâche des débutants on oublie l'essentiel qui intéresse les non débutants.

Voir un tutoriel pour se faire une bibliothèque ICI

kamill:
... Il faut que tu définisses ta classe dans un fichier .h et que tu l'inclues dans ton fichier .ino et dans ton fichier .cpp ...

Ça revient donc à faire une bibliothèque classique. Je pensais que la structure de fichier joint permettait de s'en passer.

kamill:
... La déclaration que tu utilises est une déclaration partielle de la classe ...

C'est là où je ne comprends pas. En quoi est-elle partielle ? Je la pensais complète dans le sens où, lorsque je met le même code dans le fichier.ino, tout fonctionne.

ard_newbie:
Voir un tutoriel pour se faire une bibliothèque ICI

Ben oui, ça je sais faire, je pensais pouvoir m'en passer.

Cordialement.

Pierre

ChPr:
C'est là où je ne comprends pas. En quoi est-elle partielle ? Je la pensais complète dans le sens où, lorsque je met le même code dans le fichier.ino, tout fonctionne.

La déclaration 'extern class UneClasse maClasse;' est partielle car elle ne défini pas complètement la classe UneClasse et par là ne déclare pas non plus complètement la variable maClasse.

Ca ne fonctionne pas non plus dans un fichier .ino. Essaies le code suivant pour t'en convaincre.

extern class UneClasse maClasse;

UneClasse maClasse;

void setup() {
  maClasse.faire();
}

void loop() {

}

class UneClasse {
  public:
    UneClasse() {};
    void faire() {

    };
};

La class UneClasse n'est pas définie au moment de son appel -> erreur

kamill:
... Ca ne fonctionne pas non plus dans un fichier .ino. Essaies le code suivant pour t'en convaincre. ...

Il ne me viendrait pas à l'idée d'utiliser "extern" si je n'ai qu'un seul fichier.

Je regarderai ces subtilités à tête reposée et en attendant, je vais me faire une bibliothèque classique.

Merci à tous pour ces renseignements.

Cordialement.

Pierre

En C/C++ extern ne veut pas forcément dire que la définition est externe au fichier, mais que la définition n'est pas connue au moment de la déclaration. Elle peut être définie plus loin dans le fichier.

Pour certains compilateurs il faut absolument que le nom de la classe et le nom des fichiers .h et .cpp soient identiques

Si tu déclares

class MaClasse

dans un fichier nommé "UneClasse.cpp", ça marchera pas

kamill:
En C/C++ extern ne veut pas forcément dire que la définition est externe au fichier, mais que la définition n'est pas connue au moment de la déclaration. Elle peut être définie plus loin dans le fichier.

Je peux avoir un petit exemple d'application dans un fichier unique ?

Cordialement.

Pierre

extern int toto;

void setup() {
  toto=0;
}

int toto;

void loop() {
  toto++;
}

C'est rarement utilisé pour des variables simples.
Ca présente plus d'intérêt si on a gros tableau de constantes qu'on veut reléguer à la fin du fichier pour améliorer la lisibilité du source.

Merci kamill pour cet exemple.

C'est l'équivalent de la déclaration forward en Pascal ?

J'ai remplacé le "int" par une "class" : ça ne le fait pas !

extern class Tutu danse;

void setup() {
  danse.tourner();
}

void loop() {
}

class Tutu {
  public:
    Tutu() {};
    void tourner() {};
};

QCM :

  • Je n'utilise pas la bonne syntaxe
  • ça ne peut pas fonctionner
  • je n'ai rien compris.
    NOTA : la déclaration forward en Pascal fonctionne avec les objets.

Cordialement

Pierre

J'ai utilisé Pascal que de manière superficielle, donc je ne saurais dire si forward et externes sont équivalents.

Mais en C/C++ le type de la variable doit être complètement défini avant son utilisation.
Tu pourrais donc faire

extern class Tutu danse;

// définition de la class Tutu avant son utilisation
class Tutu {
  public:
    Tutu() {};
    void tourner() {};
};

void setup() {
  danse.tourner();
}

void loop() {
}

Mais ça présente quand même un intérêt limité.

Oui, c'est effectivement limité que de remplacer :

class Tutu {
  public:
    Tutu() {};
    void tourner() {};

Tutu danse;
};

par :

extern class Tutu danse;

// définition de la class Tutu avant son utilisation
class Tutu {
  public:
    Tutu() {};
    void tourner() {};
};

Bon, mais j'aurai appris des choses.

Cordialement.

Pierre