Participation de Amnell :
Plante sous Windows 7, 4.7.4, MSVC2008
Plante sous Windiows Vista, 4.7.2, MSVC2008
J'ai pas réçu à passer l'écran de login... Il doit y avoir un truc de ouf derrière pour que ce soit si dur.
Participation de Amnell :
Plante sous Windows 7, 4.7.4, MSVC2008
Plante sous Windiows Vista, 4.7.2, MSVC2008
J'ai pas réçu à passer l'écran de login... Il doit y avoir un truc de ouf derrière pour que ce soit si dur.
En effet, le client a tendance à planter quand on l'exécute en dehors de Qt Creator (SDK 1.1.3, installé fraîchement aujourd'hui, Win7 64 bits, MinGW 4.4 32 bits). Par contre, en l'exécutant dans Qt Creator, ça passe sans souci.
Pareil, ça fonctionne chez moi sous QtCreator (mingw). J'ai testé avec plusieurs version de Qt sans problème (4.7.3, 4.7.4, 4.8). Pour Qt 5, c'est pas très grave, c'était juste pour m'amuser. A priori, ils ont viré la fonction qRound() donc le code compile pas
Alors ça doit être MSVC qui fait foirer le truc. C'est pas comme si c'était le compilateur le plus utilisé
Le crash doit effectivement provenir de MSVC, vu que sous toutes les plateformes que j'ai testées en compilant soit avec GCC, soit avec MinGW, ça passe sans problème. Concernant le "truc de ouf", c'est tout simplement un ping qui calcule la latence de la connexion serveur/client (comme le montre la console du serveur).
Je suis tout de même soulagé que le problème provienne "juste" du compilateur, vu que je ne voyais pas du tout ce qui aurait pu le provoquer... Sans doute une différence de norme, je ne connais pas bien les différences entre MSVC et les deux autres énoncés plus haut.
Par planter, tu entends afficher une fenêtre toute blanche ? Si c'est le cas, c'est tout simplement parce qu'il faut que le dossier qml se situe à côté de l'exécutable (Qt Creator le place par défaut dans le répertoire précédent, si je ne me trompe pas) pour que les fichiers QML puissent être interprétés (et surtout : trouvés).
Compilation ok avec msvc 2008-2010 (Qt 4.7.4 et 4.8.0) du client et serveur. Démarrage ok du client et serveur. Par contre, plantage du serveur lorsque je clique sur le bouton "connexion"... étrange
Pour rappel, ces remarques sont purement informatives et n'entre pas en ligne de compte pour la notation puisque ce n'est pas la version que tu as testé (je crois... )
EDIT : l'erreur provient d'un seg fault lors d'un changement d'états interne à QtNetwork J'ai pas le détail des informations de déboguage (je ne sais pas utiliser le débogueur de msvc dans Qt Creator) donc je sais pas si c'est un problème dans le code ou dans les libs
Malheureusement, ce n'est certainement pas un problème du compilateur :/
Et a vue de nez c'est une variable non initialisé ou un dépassement de tableau. C’est la joie de l'informatique. Ce n'est pas parce qu’une application marche qu'elle n'est pas buggé. Ca m'est arrivé assez souvent d'avoir un code qui marche jusqu'au jour où j'ajoute un innocent printf Et au finale c'est un bugs qui existait depuis très longtemps.
Quand les sources seront disponible je regarderais.
J'ai installé le débogueur de msvc pour tester. Il y a bien un pointeur null sous msvc alors qu'il n'y a pas de "problème" sous mingw (dans le sens où le pointeur est pas null... parce que du coup, il y a un problème de validation des pointeurs avant utilisation )
Si cela peut vous aider dans vos recherches, ce sont les échanges de données par socket qui posent problème après compilation avec MSVC, logiquement (vu que ça crashe lors d'un ping/paquet de login). Côté serveur, la réception des paquets de chaque client se fait dans le fichier customer.cpp suite aux signaux readyRead(). Une fois que le paquet complet est récupéré, il est transmis dans vers la classe présente dans tcpserver.cpp (fonction handleMessage()) qui va appeler la fonction associée au type de paquet (voir opcode.cpp)).
@yan > Bon courage si tu trouves. Pour ma part, je ne pense pas avoir fait d'erreur de ce type.
C'est la fonction Handler de la classe handle dans la fonction handleMessage qui est invalide. Je n'ai pas encore regardé le pourquoi. J'ai juste ajouté le code suivant :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 if (handle.handler) qDebug() << "Handler ok"; else qDebug() << "Handler pas ok";
Ce sont de simples pointeurs sur fonction présent dans le tableau des opcodes.
as tu essayer valgrind sous linux?
Oui ça crashe dans la fonction HandleMessage :
La fonction * handler est null et donc fait planter le tout.OpcodeHandler &handle = opcodes[opcode];
(this->*handle.handler)(data, customer);
qDebug() << QString("Receive opcode %1 from %2 (%3).")
.arg(handle.name, customer->getName(), customer->getPeerAddress())
.toAscii().constData();
Ce n'est pas concevable par rapport au tableau des opcodes ainsi que par rapport aux tests effectués avant ce code :
Avec OpcodeHandler de la forme :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 OpcodeHandler opcodes[MSG_MAX_OPCODES] = { { "MSG_PING", &TcpServer::handlePing }, { "MSG_PONG", &TcpServer::unhandled }, // ... };
Dans aucun cas on a un NULL passé ici, on a toujours une fonction, et &TcpServer::unhandled dans le cas où aucun traitement serait fait suite à la réception du message. On a quoi qu'il arrive un test avant l'appel du handler :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 struct OpcodeHandler { /*! Le nom de l'opcode. */ QString name; /*! Le pointeur de fonction associé à l'opcode. */ void (TcpServer::*handler)(QDataStream*, Customer*); };
Ce qui garantit que l'on va bien accéder à un élément valide du tableau. Après, il faut garder en tête que cela fonctionne à merveille sous GCC et MinGW donc comment expliquer ce problème ? Il n'aimerait pas la définition de chaque membre de opcodes (cf opcodes.cpp) ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 if (opcode >= MSG_MAX_OPCODES) { qDebug() << QString("Receive unexpected opcode %1 from %2. Skipped.") .arg(opcode).arg(customer->getPeerAddress()).toAscii().constData(); return; }
Echange par MP car Defis non terminé :
Envoyé par Amnell
Éclaircissement sur le Problème sous MSVC :
http://www.developpez.net/forums/d11...e/#post6241045
Salut,
Vu que le défi est terminé, je vais enfin pouvoir dévoiler un peu mon application qui a semble-t-il pas mal intrigué du fait de crashs inexpliqués (non, ce n'est pas la belle partie de l'application vu qu'en plus, l'appli avait été testée sur bien des plateformes mais malheureusement pas sous plusieurs compilos - qui eut cru que cette config suffirait à faire sauter une appli... enfin, je n'y croyais pas avant).
Le serveur
Le serveur est une application console qui ne fait que récupérer les paquets envoyés par les clients et agir en conséquence. Il ne fait rien de plus, dans le sens où il n'envoie jamais de son propre chef des paquets aux clients : il y a toujours une action d'un client qui le mène à faire cela.
Les paquets reçus sont identifiés par un système d'opcodes. Ainsi, toutes les réceptions de paquets sont loggués dans la console et sauvegardées dans un fichier de log. Un exemple de logs suite à quelques actions de l'utilisateur sur un client :
Chaque "MSG_[quelque chose]" correspond à un paquet. Le type est déterminé selon l'opcode spécifié en tête de paquet.2011-09-16 23:01:02: Initializing server...
2011-09-16 23:01:02: Server successfully initialized.
2011-09-16 23:01:02: Initializing database...
2011-09-16 23:01:02: Database successfully initialized.
2011-09-16 23:01:02: Initializing simulator...
2011-09-16 23:01:02: Simulator successfully initialized.
2011-09-16 23:05:53: New connection from 127.0.0.1.
2011-09-16 23:05:59: Receive opcode MSG_LOGIN from Jean Dupont (127.0.0.1).
2011-09-16 23:06:02: Receive opcode MSG_QUERY_PATIENTS_MODEL from Jean Dupont (127.0.0.1).
2011-09-16 23:06:03: Receive opcode MSG_QUERY_MEDICINE_MODEL from Jean Dupont (127.0.0.1).
2011-09-16 23:06:03: Receive opcode MSG_QUERY_BODY_PARTS from Jean Dupont (127.0.0.1).
2011-09-16 23:06:03: Receive opcode MSG_QUERY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:03: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:06:10: Receive opcode MSG_ENTER_CHAT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:10: Receive opcode MSG_QUERY_CHAT_USERS from Jean Dupont (127.0.0.1).
2011-09-16 23:06:13: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:06:16: Receive opcode MSG_SEND_CHAT_MESSAGE from Jean Dupont (127.0.0.1).
2011-09-16 23:06:19: Receive opcode MSG_SEND_CHAT_MESSAGE from Jean Dupont (127.0.0.1).
2011-09-16 23:06:21: Receive opcode MSG_SEND_CHAT_MESSAGE from Jean Dupont (127.0.0.1).
2011-09-16 23:06:22: Receive opcode MSG_LEAVE_CHAT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:23: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:06:27: Receive opcode MSG_QUERY_ANALYSIS_MODEL from Jean Dupont (127.0.0.1).
2011-09-16 23:06:33: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:06:43: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:06:43: Receive opcode MSG_QUERY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:44: Receive opcode MSG_QUERY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:50: Receive opcode MSG_MODIFY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:50: Receive opcode MSG_QUERY_PATIENTS_MODEL from Jean Dupont (127.0.0.1).
2011-09-16 23:06:50: Receive opcode MSG_QUERY_MEDICINE_MODEL from Jean Dupont (127.0.0.1).
2011-09-16 23:06:50: Receive opcode MSG_QUERY_BODY_PARTS from Jean Dupont (127.0.0.1).
2011-09-16 23:06:50: Receive opcode MSG_QUERY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:06:53: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:07:03: Receive opcode MSG_MODIFY_PRESCRIPTION from Jean Dupont (127.0.0.1).
2011-09-16 23:07:03: Receive opcode MSG_QUERY_PATIENT from Jean Dupont (127.0.0.1).
2011-09-16 23:07:03: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:07:11: Receive opcode MSG_START_ECG from Jean Dupont (127.0.0.1).
2011-09-16 23:07:13: Receive opcode MSG_PING from Jean Dupont (127.0.0.1).
2011-09-16 23:07:13: Receive opcode MSG_STOP_ECG from Jean Dupont (127.0.0.1).
2011-09-16 23:07:15: Customer Jean Dupont (127.0.0.1) has been disconnected.
Le serveur s'appuie sur deux systèmes-piliers : le simulateur (qui simule les données en provenance des divers appareil médicaux) et la base de données.
La base de données n'est pas une simple QSqlDatabase qui fait du SELECT/UPDATE/INSERT sur la db SQLite. Non, elle ne fait pas cela car selon moi, ce n'est pas optimisé et les clients risqueraient d'attendre une réponse, limitant la fluidité de l'application cliente. Au lancement de l'application, je récupère le contenu de chaque table de ma base de donnée et j'indexe chaque élément dans une classe template nommée DBStore<T>, une liste contenant des entrées de la table concernées. Par exemple :
Avec AccountStore qui contient toutes les entrées de la table "account" de la base de données. AccountEntry est une structure de ce type :
Code : Sélectionner tout - Visualiser dans une fenêtre à part DBStore<AccountEntry> AccountStore;
L'intérêt d'indexer les tables de la base de données dans ce type de chose permet de n'avoir plus aucun SELECT à faire. Lors d'une requête de la part d'un client de la couleur des chaussettes de Jean-Claude, l'infirmier, le serveur tape directement dans le DBStore<ChaussetteEntry> pour retourner la couleur, il n'y a pas de SELECT, donc au final un gain de temps.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 struct AccountEntry : public BaseEntry<AccountEntry> { int id; QString name; QString password; int level; // ... };
Le client
Le client a été codé de telle manière que ce soit la partie QML qui détermine quelle action effectuer quand. Le C++ du client n'est là que pour être exploité par la partie QML. Vous connaissez tous le concept de la séparation graphique et logique d'une application Qt avec la partie logique en C++ et la partie QML pour l'interface graphique, avec entre les deux un objet contextuel qui joue le rôle de pont pour permettre la communication. Ici, je considère la partie C++ comme étant un pont entre le QML et le serveur lui-même vu que le C++ n'a aucun rôle décisionnel dans l'application : son rôle se limite à l'envoi de paquet et à la réception de paquets (le système d'identification du paquet se fait également par opcode de manière identique à ce qui a été fait dans le serveur, donc je ne re-détaille pas).
Côté C++, lors de la phase de conception, j'ai décidé de faire un système de paquets généralisés, dans le sens où le TcpSocket joue le rôle du receveur/envoyeur de paquets, sans plus. Ce n'est pas lui qui transfère les données à l'ancre (l'objet contextuel entre le C++ et le QML), il ne fait aucun traitement. Il possède donc deux types de fonctions : des fonctions d'envoi de paquets et des fonctions de réception. Dans les fonctions de réception, il passe au gestionnaire de hooks les données reçue et son travail s'arrête là.
On en vient donc au gestionnaire de hooks : qu'est-ce ? C'est un gestionnaire qui contient plusieurs listes de hooks, des classes totalement indépendantes qui s'ajoutent et se retirent sans une ligne de code à changer. Je suppose que vous êtes en train de vous gratter la tête en vous disant : "Qu'est-ce qui me chante ?". J'explicite. Imaginons que nous avons reçu un paquet contenant le nom du patient N. Le serveur va envoyer directement l'info sans l'analyser au gestionnaire de hooks qui va appeler la fonction onReceivePatientName() de tous les hooks branchés sur lui :
Un hook se créant de la façon suivante :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 void HookMgr::onReceivePatientName(const QString &data) { foreach (ModuleHook *hook, _moduleHooks) hook->onReceivePatientName(data); }
Et dans la fonction addModuleHooks(), on ajoute juste :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 class PatientFollowUpHook : public ModuleHook { public: PatientFollowUpHook() : ModuleHook() { } };
Et c'est tout ! ModuleHook() ajoute comme un grand le hook à la liste des hooks de type ModuleHook du gestionnaire de hooks. En gros, on peut ajouter/retirer des classes quand on le souhaite sans avoir à retoucher quoi que ce soit. Au final, on crée autant de traitements qu'on le souhaite suite à la réception de paquets sans avoir à changer quoi que ce soit en interne.
Code : Sélectionner tout - Visualiser dans une fenêtre à part new PatientFollowUpHook();
Je regrette juste de ne pas l'avoir exploité jusqu'au bout dans le sens où j'ai donné aux hooks des comportements très limités vu que je suis parti sur une utilisation maximale de QML.
Trêve de paroles, on veut des images !
L'application se veut être utilisée sur des petites plateformes. C'était d'ailleurs une grosse difficulté vu que faire rentrer beaucoup de données dans un petit espace n'était pas évident.
Ce qui me désole est que l'application se veut être très esthétique : il y a beaucoup d'animations, une gestion qui me semble intéressante du tactile/souris au niveau de la manipulation des éléments de la fenêtre. Bref, j'aurais voulu vous donner des vidéos plutôt que des screens, mais je n'ai pas le matériel pour.
Ainsi, voici quelques images :
La vision du dossier d'un patient avec les prescriptions médicales.
Les informations du patient (oui, on soigne des patients morts chez nous !)
L'ECG en temps réel. Notez que le simulateur du serveur s'amuse avec un peu d'aléatoire à modifier légèrement la position des points pour avoir des courbes un tout petit peu "vivantes".
Mon super color picker QML/JS pour modifier la couleur de l'ECG.
Les radios (une grande image recomposée à partir des images données dans le dossier radios) avec zoom sur quatre niveaux et possibilité de se balader dessus comme sur Google Map, mais en plus fluide
J'aurais bien ajouté d'autres choses, comme la vue animée sur 360° du scanner avec possibilité de faire ralentir/accélérer l'animation, mais je suis arrivé au cota de 5 images. Quoi qu'il en soit, mon application ne peut pas se résumer à des screens, vu qu'il y a toute la partie animation derrière.
Bref, tout ça pour dire que je suis plutôt content de ma participation !
Bonne soirée,
Amnell.
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager