2010-12-07 29 views
1

J'écris une petite application de loisir en C++ en utilisant Qt. L'application doit lire certains fichiers de dictionnaire au démarrage qui prend un certain temps, donc je créé une classe de fil personnalisé pour analyser les dictionnaires en arrière-plan:Etranges erreurs d'exécution avec une application C++ Qt multithreadée présentant dans le débogueur VS uniquement

class SetupThread : public QThread 
{ 
    Q_OBJECT 
public: 
    SetupThread(QObject *p_parent); 
    void setDictOutputs(WordDictionary *word, KanjiDictionary *kanji, RadicalDictionary *rad); 
    void run() 
    { 
     emit message("Parsing JMdict dictionary..."); 
     m_wordDict->parseDictionary("dictionaries/JMdict_e.xml"); 

     emit message("Parsing KANJIDIC dictionary..."); 
     m_kanjiDict->parseDictionary("dictionaries/kanjidic2.xml"); 

     emit message("Parsing RADKFILEX dictionary..."); 
     m_radDict->parseDictionary("dictionaries/radkfilex.utf8"); 
    } 

signals: 
    void message(const QString &p_msg); 

private: 
    WordDictionary *m_wordDict; 
    KanjiDictionary *m_kanjiDict; 
    RadicalDictionary *m_radDict; 
}; 

Les trois classes « Dictionnaire » sont créés par moi et ils ont tous hérite d'une interface commune, qui inclut la fonctionnalité Q_OBJECT pour signaler la classe principale sur la tête du thread d'installation en mode Qt :: QueuedConnection avec des mises à jour de progression lors de l'analyse, afin qu'elle puisse afficher une barre de progression. Le fil de configuration est appelé à partir des principaux constructeur de cours comme celui-ci:

MainForm::MainForm(QWidget *parent, Qt::WFlags flags) 
{ 
    /* ... */ 
    m_wordDict = new WordDictionary(this); 
    m_kanjiDict = new KanjiDictionary(this); 
    m_radDict = new RadicalDictionary(this); 
    m_setupThread = new SetupThread(this); 
    m_setupThread->setDictOutputs(m_wordDict, m_kanjiDict, m_radDict); 
    m_setupThread->start(); 
} 

j'ai commencé à avoir des problèmes avec l'application s'écraser à la sortie et je ne pouvais pas voir quel était le problème alors j'ai essayé de l'exécuter dans la Le débogueur de Visual C++ 2008. Puis-je obtenir un énorme plantage au démarrage:

exception non gérée à 0x7568b727 dans kanjiflash.exe: exception Microsoft C++: std :: exception à l'emplacement mémoire 0x024ffa1c ..

Les émissions de traces de la pile :

KernelBase.dll 7568b727()
[Cadres ci-dessous peuvent être incorrectes et/ou manquant, pas de symboles chargés pour KernelBase.dll]
KernelBase.dll 7568b727()! msvcr90d.dll! _heap_alloc_dbg_impl (unsigned int nTaille = 72, int nBlockUse = 1, const char * szFileName = 0x00000000, int nLigne = 0, int * errno_tmp = 0x024ff9f8) Ligne 497 + 0xc octets C++
msvcr90d.dll! _nh_malloc_dbg_impl (non signé int nSize = 72, int nhFlag = 0, int nBlockUse = 1, const caractère * szFileName = 0x00000000, int nLine = 0, int * errno_tmp = 0x024ff9f8) Ligne 239 + 0x19 octets C++
msvcr90d.dll! _nh_malloc_dbg (non signé int nSize = 72, int nhFlag = 0, nBlockUse int = 1, const char * szFileName = 0x00000000, int nLine = 0) Ligne 296 + 0x1d octets C++
msvcr90d.dll! malloc (unsigned int nSize = 1) Ligne 56 + 0x15 octets C++
020bea68()
kanjiflash.exe! SetupThread :: run() Ligne 391 + 0x2c octets C++
QtCored4.dll! QThreadPrivate :: start (void * arg = 0x020bd0c8) Ligne 317 C++
msvcr90d.dll! _callthreadstartex() Ligne 348 + 0xf octets C
msvcr90d.dll! _threadstartex (void * PTD = 0x020bd8f0) ligne 331 C
kernel32.dll! 75593677()
ntdll.dll! 77739d72()
ntdll.dll! 77739d45()

La ligne particulière dans SetupThread :: run() cela fait référence est celui où j'essaye d'exécuter parseDictionary ("..."). Cette pile d'appels est obtenue à partir de Windows7 64bit. Dans Windows XP 32 bits, j'ai eu un problème identique, seule différence était que la pile est allée au constructeur d'un QString (const char * ch) de SetupThread :: run(), où il s'est plaint et a montré le tampon * ch à quelques caractères d'ordures.

Maintenant, l'étrange est que cela n'arrive que dans le débogueur. Les deux configurations Debug et Release fonctionnent bien en dehors du débogueur.En tâtonnant avec l'application enquêtant sur ceci, j'ai trouvé et corrigé l'erreur qui m'a fait utiliser le débogueur en premier lieu mais je me demande ce qui se passe réellement et que ferai-je si j'ai besoin d'utiliser le débogueur un jour. Comme je ne suis pas complètement versé dans la programmation multi-thread, je ne suis pas sûr qu'il soit possible de les exécuter dans un débogueur de manière significative, ou peut-être que je fasse quelque chose de mal, comme travailler sur des pointeurs de la classe principale (violation d'accès?). Toute idée grandement appréciée.

+0

difficile à dire sans voir le code utilisé pour arrêter/terminer le thread, mais supposons que votre objet MainForm est supprimé pendant que le thread est encore en cours d'exécution, vous êtes sûr d'avoir des problèmes. En outre, il peut sembler que l'exe fonctionne quand aucun débogueur n'est attaché, alors qu'en fait il se comporte exactement de la même manière mais le problème ne s'affiche pas car certaines fonctionnalités de débogage sont uniquement activées sous le débogueur. – stijn

+0

Le crash mentionne une exception std ::: pouvez-vous entourer votre main et votre thread :: run avec un try catch et voir d'où l'exception est lancée? –

+0

@David: J'ai fait comme vous l'avez suggéré mais mes instructions catch ne semblent pas du tout être exécutées, même avec catch (...), et VS se plaint de "exception non gérée". Un autre mystère. – neuviemeporte

Répondre

1

Je suis tombé sur une réponse aujourd'hui tout en traversant le programme dans le débogueur. Sur la base de cette trace de la pile, je suis arrivé avant:

kernel32.dll 7d4e2366()
[Cadres ci-dessous peuvent être incorrectes et/ou manquant, pas de symboles chargés pour kernel32.dll]
kernel32.dll 7d4e2366!()
QtCored4.dll! QString :: QString (const char * ch = 0x0265ff24) ligne 427 + 0x12 octets C++
KanjiFlash.exe! SetupThread :: run() ligne 400 + 0x2c octets C++
QtCored4.dll! QThreadPrivate :: start (void * arg = 0x02137f68) Ligne 317 C++
msvcr90d.dll! _callthreadstartex() Ligne 348 + 0xf octets C
msvcr90d.dll! _threadstartex (void * PTD = 0x02138828) Ligne 331 C
kernel32.dll! 7d4dfe21()

... et de voir le tampon "const char * ch" contiennent des déchets après le crash , Je supposais que le littéral de chaîne était corrompu et que c'était la cause de l'exception. Mais j'ai essayé d'entrer dans la première fonction parseDictionary() et cela a fonctionné. Le début de la fonction ressemble à ceci:

void WordDictionary::parseDictionary(const QString &p_dictPath) 
{ 
    // open XML file 
    QFile file(p_dictPath); 
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) 
    { 
     throw std::exception("Unable to open dictionary file in WordDictionary constructor"); 
    } 
    /* ... */ 

J'ai remarqué que le crash s'est produit après que cette exception ait été levée. Apparemment, Qt ne va pas bien avec les exceptions, donc cela n'est pas géré et le message a été discrètement rejeté, mais l'exception a quand même écrasé l'application. La raison pour laquelle cela s'est produit dans le débogueur était que VS définissait le répertoire de travail pour un binaire débogué dans le répertoire du projet plutôt que dans le répertoire de sortie. J'ai eu une copie de mon répertoire dictionnaires/dans les répertoires de sortie "Debug" et "Release" seulement pour que les fichiers ne puissent pas être trouvés qui ont causé le crash. Lorsqu'ils sont exécutés à partir des répertoires "Debug" ou "Release", ils ont été ouverts correctement et rien ne s'est passé.

J'ai fait une copie du répertoire dictionnaires/dans le répertoire du projet et il a couru sous le débogueur sans problème. En outre, j'ai supprimé toutes les directives de lancement d'exception de mon code pour éviter des problèmes dans le futur (elles ne semblent pas bien fonctionner dans les applications Qt). Je ne fais pas cela une réponse acceptée mais, parce que je voudrais encore savoir:

  1. Pourquoi ne pas l'exception jeté de l'analyseur se faire prendre partout (main(), MainForm :: MainForm()) ? Est-ce que Qt a fait cela et si oui, comment? Pouvez-vous utiliser des exceptions dans le code multithread de toute façon?
    Editer: réponse: Je viens de découvrir que les exceptions ne peuvent pas traverser les limites de fil. Mes directives try-catch ont été placées dans main autour de l'ensemble de l'engin et dans le constructeur de MainForm où j'ai appelé SetupThread :: run(). Lorsque j'ai placé un bloc try-catch dans run() lui-même, le contenu du bloc catch a été correctement exécuté lorsqu'une exception a été levée.
  2. Pourquoi ai-je reçu une sortie de trace de pile si confuse, me menant à une chasse aux oies sauvage? Il semblait y avoir des problèmes avec la création d'un QString à partir d'un const char * et/ou avec un malloc quelque part. J'ai développé de nombreuses théories "intéressantes" sur les causes probables de telles erreurs avant que je découvre ce qui a vraiment causé ça ...
    En fait, les traces de pile de la version libre de debug-symbols avaient plus de sens maintenant rétrospectivement: il y avait des références à quelque chose comme _CxxExceptionThrown dedans et aucune référence à malloc ou QString, mais je supposais juste qu'il manquait l'information pour montrer le origine.
+1

Les exceptions ne peuvent être attrapées que par une stackframe plus haute sur la pile. Dans votre thread main() n'est pas sur le stackframe du thread –

+0

Je ne suis pas sûr que vous vouliez dire la même chose que j'ai écrit dans mon edit, mais je vous + juste pour être en sécurité. ;) – neuviemeporte