Qt C++ - Chat multicast

un de mes slots n'existe pas ?

Le problème exposé dans ce sujet a été résolu.

Bonjour tout le monde,

J’essaie de réaliser un chat multicast en C++ à l’aide du framework Qt. J’ai commencé par la partie serveur. Je me suis basé sur un cours trouvé sur internet, que j’ai réadapté pour qu’il soit adapté à Qt 6. Je ne sais pas de quelle version il s’agit, mais le programme trouvé sur internet utilise directement :

#include <QApplication>
#include <QPushButton>
// ou encore
connect(client, ... // Sans le Qobject

Je sais que ça ne marche plus en tout cas avec Qt 6, ce qui m’a obligé à faire des modifications après le copier-coller. Je pense que mon problème vient de mes modifs, mais je ne vois pas où.

Mes fichiers sont en fin du post pour la lisibilté. Tout ça compile et s’éxécute. Cependant, dans la "sortie de l’application" de Qt Creator, j’ai le message "qt.core.qobject.connect: QObject::connect: No such slot QWidget::nouvelleConnexion()" (aucun problème dans la "sortie du compilateur"). Le problème semble venir du fichier "fenetre_serveur.cpp" à la ligne 28.

//...
        QObject::connect(serveur, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
//...

Je ne vois pas pourquoi il me dit que ce slot n’existe pas alors que je l’ai bien écrit dans le .cpp et le .h. Pour éviter les erreurs de nom, je les ai copier-coller là où il faut à partir de cette ligne justement.

Pourtant il me semble bien avoir compris les paramètres de connect :

  • serveur : car j’atend un signal du widget serveur;
  • SIGNAL(newConnection()) : le signal du widget serveur qui m’intéresse;
  • this : Parceque je veux activer un slot de la fenêtre où je suis (parent de serveur);
  • SLOT(nouvelleConnexion()) : C’est le slot que je veux activer, qui est un slot ajouté.

Je me serai trompé sur l’argument this ? je devrais mettre quoi alors ? :/ Je me dit que c’est this le problème car apparemment il cherche le slot "QWidget::nouvelleConnexion" et pas "fenetre_serveur::nouvelleConnexion". j’ai essayé de l’écrire directement dans la ligne 28, mais alors l’erreur est la même mais avec "QWidget::fenetre_serveur::nouvelleConnexion".

Je vois une autre possibilité, c’est la ligne 12 de mon *.h :

//...
class fenetre_serveur : public QWidget {
    //Q_OBJECT  J'arrive pas à voir pourquoi mais ça compile pas si je laisse ça...
//...

Je ne comprend pas pourquoi, mais si je laisse la macro cpp Q_OBJECT , je ne peux pas compiler, et je ne comprend pas pourquoi car dans le cours que j’ai on me demande de la mettre, dans le programme trouvé sur internet elle est également présente.

De mon côté, je vais temporairement ignorer le problème, faire le client, puis voir ce qu’il se passe… Avez-vous des pistes pour m’aider ?

Merci par avance


FICHIERS COMPLETS

Les commentaires entre /**/ Sont des modifications que je veux apporter plus tard, elles ne sont pas importantes pour l’instant.

fenetre_serveur.cpp :

#include "fenetre_serveur.h"

// Constructeur par défaut
fenetre_serveur::fenetre_serveur() : QWidget()
{
    // Texte d'information du serveur
    etatServeur = new QLabel(this);

    // Parametres du bouton
    boutonQuitter = new QPushButton("Quitter", this);
    QObject::connect(boutonQuitter, SIGNAL(clicked()), qApp, SLOT(quit()));

    // Disposition
    layout = new QVBoxLayout(this); //Pour ne pas s'embêter avec les ->setGeometry
    layout->addWidget(etatServeur);
    layout->addWidget(boutonQuitter);
    setWindowTitle("Fenetre du serveur");

    // Demarrage du serveur
    /* Songer a mettre ceci hors du constructeur plus tard : J'aimerai pouvoir faire plusieurs serveurs en simultanné */
    serveur = new QTcpServer(this);
    // Attente de la connexion de n'importe qui sur le port 50885.
    if( !serveur->listen(QHostAddress::Any, 50885)){ /* Si possible laisser l'utilisateur choisir son port ou un port automatique*/
        etatServeur->setText("Le serveur n'a pas pu demarrer : <br />" + serveur->errorString());
    }
    else{
        etatServeur->setText("Le serveur a démarré sur le port <strong>" + QString::number(serveur->serverPort()) + "</strong>.<br /> Des clients peuvent maintenant se connecter");
        QObject::connect(serveur, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));
    }
}


void fenetre_serveur::nouvelleConnexion()
{
    envoyerATous("Un nouveau client vient de se connecter"); // Annonce

    // Nouveau client
    QTcpSocket *nouveauClient = serveur->nextPendingConnection();
    clients << nouveauClient;

    // Signaux
    QObject::connect(nouveauClient, SIGNAL(readyRead()), this, SLOT(donneesRecues())); // signal readyRead introuvable dans la doc de QTcpSocket
    QObject::connect(nouveauClient, SIGNAL(disconnected()), this, SLOT(deconnexionClient())); //idem
}

void fenetre_serveur::donneesRecues()
{
    // Recherche du QTcpSoccket du client qui envoi
    QTcpSocket *socket = qobject_cast< QTcpSocket *>(sender());
    if(socket == 0){ return; }

    // Recuperation du message
    QDataStream in(socket); /*rendre les if plus propres */
    if(tailleMessage == 0){ // Si on a pas déjà la taille du message

        if(socket->bytesAvailable() < (int)sizeof(quint16)){ // Si on a pas au moins un int, alors on a pas encore reçu le message en entier
            return;
        }
        in >> tailleMessage; // Si on au moins un entier, alors on a la taille du message
    }

    if(socket->bytesAvailable() < tailleMessage) {return;} // Si on a pas encore le message entier, on attend

    QString message;
    in >> message; // On vide entièrement in dans message;
    envoyerATous(message); // On l'envoi à tout le monde;
    tailleMessage = 0; // On se rend prêt à recevoir un nouveau message;
}

void fenetre_serveur::envoyerATous(const QString& message)
{
    QByteArray paquet; // Le paquet a envoyer est un vecteur d'octets
    QDataStream out(&paquet, QIODevice::WriteOnly); // Simplement pour pouvoir ecrire dans le paquet avec <<. C'est du sucre syntaxique

    out << (quint16) 0; // Réservation de l'emplacement pour la taille du message
    out << message; // On place le message a proprement parler
    /* Ajouter un traitement du message, pour mettre le pseudo de qui envoi, l'heure... */
    out.device()->seek(0); // On se repalce au début du paquet

    out << (quint16) (paquet.size() - sizeof(quint16)); // La taille du message compte pas dans la taille du message...

    // Envoi
    for(int i = 0; i < clients.size(); i++)
    {
        clients[i]->write(paquet);
    }
}

void fenetre_serveur::deconnexionClient()
{
    envoyerATous("Un client vient de se déconnecter");

    // Qui ?
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
    if( socket == 0) { return; }

    clients.removeOne(socket); // On retire le client
    socket->deleteLater();
    /* Mettre des else !!! */

}

fenetre_serveur.h

#ifndef FENETRE_SERVEUR_H
#define FENETRE_SERVEUR_H

// fichiers par défaut
#include <QWidget>
#include <QtNetwork>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>

class fenetre_serveur : public QWidget {
    //Q_OBJECT  J'arrive pas à voir pourquoi mais ça compile pas si je laisse ça...

public:
    fenetre_serveur();
    void envoyerATous(const QString& message); /*renommer en envoyer*/

private slots:
    void nouvelleConnexion(); /*renommer en client_connexion*/
    void donneesRecues(); /*renommer en recevoir*/
    void deconnexionClient(); /*renommer en client_deconnexion*/

private:
    // Elements de l'interface
    QLabel *etatServeur;
    QPushButton *boutonQuitter;
    QVBoxLayout *layout;

    // Elements de réseau
    QTcpServer *serveur;
    QList<QTcpSocket *> clients;
    quint16 tailleMessage = 0;


};

#endif // FENETRE_SERVEUR_H

(Je ne crois pas que mon main soit utile donc je le met pas pour des raisons de place)

//Q_OBJECT  J'arrive pas à voir pourquoi mais ça compile pas si je laisse ça...

Ca doit compiler. Si ca ne compile pas, la solution n’est pas de le supprimer, mais de corriger le problème de compilation. Donne les messages d’erreur.

QObject::connect(serveur, SIGNAL(newConnection()), this, SLOT(nouvelleConnexion()));

La syntaxe avec SIGNAL et SLOT n’est pas recommandé depuis Qt5 (10 ans). Utilise la syntaxe avec les pointeurs de fonctions, pour avoir une erreur a la compilation.

QObject::connect(serveur, &QTcpServer::newConnection, this, &fenetre_serveur::nouvelleConnexion);

Tu suis quel cours ?

+1 -0

Salut, merci de la réponse !

Ca doit compiler. Si ca ne compile pas, la solution n’est pas de le supprimer, mais de corriger le problème de compilation. Donne les messages d’erreur.

Il y a 3 messages d’erreur :

:-1: erreur : debug/fenetre_serveur.o:fenetre_serveur.cpp:(.rdata$.refptr._ZTV15fenetre_serveur[.refptr._ZTV15fenetre_serveur]+0x0): undefined reference to `vtable for fenetre_serveur'

:-1: erreur : collect2.exe: error: ld returned 1 exit status

:-1: erreur : [Makefile.Debug:81: debug/Chat_multicast—Partie_serveur.exe] Error 1

J’avais un peu cherché, et j’ai trouvé un forum anglophone où ils discutaient de ce problème. Certains disaient d’ajouter un destructeur virtuel, que c’était nécessaire à cause de l’éhritage je crois. D’autre disait plutôt de supprimer la macro. Dans mon cas c’est la seconde option qui a "fonctionné".

La syntaxe avec SIGNAL et SLOT n’est pas recommandé depuis Qt5 (10 ans). […] Tu suis quel cours ?

Je m’y attendais à cette question et au problème lié, je le sentais gros comme une maison, quand au début du cours j’ai vu #include <QPushButton> ça ne fonctionnait pas !

Je suis le cours d’Open Classroom… Pas taper ! :’D Je sais les problèmes du cours C++ d’OC. Je sais aussi qu’il a subi très peu de mises à jour, si ce n’est la suppression de la section sur Qt. J’ai suivi ce cours pour une raison simple. Je suis en école d’ingénieur, et j’ai un devoir sur ce fameux chat multicast. On me demande d’utiliser Qt, et les enseignants nous ont fourni le PDF du cours Qt d’OC. (à l’époque, c’était le site du zéro, je me souviens qu’on pouvait récupérer les PDF des cours du site, très mal formaté d’ailleurs !) Comme je sais que certains de mes camarades ont déjà fini ce devoir, je me suis dit que, au moins sur cette section Qt, j’aurai pas de problème, et donc que c’était pas la peine d’aller chercher les best practice et que je pouvais me contenter de ce cours. Voilà mon erreur…

BON ! Du coup je suis retourné à la doc (je vais finir par laisser tomber tous les cours de programmation et me contenter de la doc à chaque fois que je veux apprendre un nouveau langage je crois X( ) à la page Signals & Slots 6.3.0. Je vois que dans la fonction connect, tout passe par pointeur. Donc la ligne que je doit écrire c’est :

QObject::connect(serveur /*déjà un pointeur*/, &QTcpServer::newConnection, this, fenetre_serveur::nouvelleConnexion);

(je me permet de corriger le dernier argument pour aussi le passer par pointeur au cas où d’autres passent par là). Ca fonctionne, je n’ai plus le problème qui m’a amené ici initialement. Merci !

Pour le problème de la macro… J’ai une intuition (fondée sur rien !), c’est que mes différents connect posent problème. Je vais tous les réécrire pour qu’ils soient en accord avec la façon de faire que tu as indiquée (la "bonne façon" ? la "façon moderne" ?) pour voir si ça change quelque chose… Au pire si ça change rien j’aurai pas perdu mon temps j’aurai un code moins faux je suppose.

Je ferai ça dans la nuit et j’éditerai mon message je pense.

Bonne soirée !

J’ai fait une erreur de frappe pour le dernier argument, c’est &fenetre_serveur::nouvelleConnexion.

c’était pas la peine d’aller chercher les best practice

C’est pas en réalité une question de bonnes pratiques, mais simplement que la "nouvelle" syntaxe donne un message d’erreur explicite à la compilation. Donc c’est plus simple de trouver les problèmes.

`vtable for fenetre_serveur'

Ca sent le problème de génération de qmake. Relance qmake (dans Qt Creator, dans le menu Build > Run qmake) puis fait un rebuild (dans le menu Build > Rebuild).

Certains disaient d’ajouter un destructeur virtuel

Quand tu as une classe de base qui a un destructeur virtuel (dans ton cas QWidget), alors ta classe aura aussi un destructeur virtuel ajouté automatiquement par le compilateur.

Perso, je le mets, mais pas sur que ce soit nécessaire et que ca soit la cause du problème. Tu peux tester en ajoutant :

~fenetre_serveur() override = default;

D’autre disait plutôt de supprimer la macro.

La macro sert a Qt pour déclarer des fonctionnalités internes, comme les signaux ou les meta objets (pour faire du QML). En fonction de ce que tu fais dans ta classe, la macro Q_OBJECT peut être obligatoire ou pas.

je le sentais gros comme une maison

C’est pas pour embêter les gens ou par snobisme qu’on déconseille les cours pas à jour. C’est que souvent, c’est un problème dans l’apprentissage : les langages ont évolués pour devenir plus simple et plus safe et utiliser une approche historique, cela veut dire se taper à la main toutes les merdes qui passent (et qui souvent n’ont aucun intérêt en termes d’apprentissage).

+3 -0

Ca sent le problème de génération de qmake. Relance qmake (dans Qt Creator, dans le menu Build > Run qmake) puis fait un rebuild (dans le menu Build > Rebuild).

Ca a suffit, pas besoin de destructeur virtuel en fin de compte. J’étais persuadé d’avoir bien lancé qmake… J’ai pas dû faire attention à ses messages en sortie, j’ai manqué d’attention, my bad ! ^^

C’est pas pour embêter les gens ou par snobisme qu’on déconseille les cours pas à jour.

Je m’en doute t’inquiète, et c’est pas ce que je voulais dire, je me suis mal exprimé. En fait ce que je voulais dire, c’est que étant donné la source de mon cours, étant donné que dès les premières pages du cours j’avais des choses qui fonctionnaient pas, et étant donné ce que j’ai compris d’un article d’un certain "développeurzéro" (je suis plus du tout sûr du pseudo de cette personne), je pouvais prévoir que j’allais me confronter à des problèmes à un moment ou à un autre, en mélangeant des info récentes et anciennes, et en m’entêtant à utiliser la dernière version de Qt. (Oui dans ma formation on m’apprend qu’il est courant de travailler avec des anciennes versions des outils et langages pour des questions de compatibilité, mais là j’en suis à apprendre, pas à maintenir des trucs qui ont 30 ans donc go dernières versions selon moi :( )

Enfin bref, ça fonctionne comme il faut. Je serai plus attentif pour la partie client de mon programme pour ne pas avoir les même problèmes. Merci ! :D

Ca sent le problème de génération de qmake. Relance qmake (dans Qt Creator, dans le menu Build > Run qmake) puis fait un rebuild (dans le menu Build > Rebuild).

Ca a suffit, pas besoin de destructeur virtuel en fin de compte. J’étais persuadé d’avoir bien lancé qmake… J’ai pas dû faire attention à ses messages en sortie, j’ai manqué d’attention, my bad ! ^^

tsuruba

Non, ce n’est pas une erreur de ta part. QtCreator lance automatiquement qmake normalement. Mais il arrive juste que de temps en temps, qmake n’est pas lancé.

Quand tu vois cette erreur de "vtable" avec un objet Qt, pense a lancer manuellement qmake. C’est juste un truc a savoir.

Oui dans ma formation on m’apprend qu’il est courant de travailler avec des anciennes versions des outils et langages pour des questions de compatibilité,

tsuruba

Ce qui est totalement vrai, on travaille souvent sur du code ancien à maintenir.

Mais ce n’est en réalité pas un argument pour ne pas apprendre les approches modernes. Parce que l’une des choses importantes que l’on apprend quand on apprend les approches modernes, c’est pourquoi on ne fait plus comme avant. Quels sont les problèmes posés par les anciennes approches. Et donc comment écrire du code ancien qui est correct.

Le problème de beaucoup de formation "ancienne", c’est qu’elles n’enseignent pas à écrire du code correct. C’est des bugs partout.

Un exemple qui montre le problèmes : https://alexandre-laurent.developpez.com/cpp/retour-fonctions-ou-exceptions. Regarde le code final correct en C. Tu as déjà vu des cours de C qui montre correctement faire les choses ? Beaucoup de cours se content de monter les syntaxes de base, sans montrer la complexité d’écrire du code correct. Un exemple d’exception : le cours CS50 de Harvard (gratuit et en anglais), qui montre par exemple que les pointeurs peuvent créer des bugs et comment utiliser valgrind pour détecter les problèmes.

+0 -0

Tu as déjà vu des cours de C qui montre correctement faire les choses ?

Je ne pense pas… En tout cas je suis sûr et certain de ne pas avoir vu la manière de faire indiquée dans le poste d’Alexandre Laurent avec les exceptions. Même sans aller jusqu’aux exceptions, le fait de mettre des if toutes les 2 lignes et de goto vers un bloc de nettoyage, je ne le fait que quand je vois que mon code fonctionne pas : Je met des if et des print dedans pour localiser l’erreur, je corrige, et ensuite j’enlève tout. Le cours d’Harvard (merci pour la référence) je vais m’y pencher, vu que c’est le C que j’utilise le plus souvent. Même si j’ai peur que ça va être compliqué de désapprendre. :(

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte