1
votes

I'm writing a little hobby application in C++ using Qt. The application has to read some dictionary files at startup which takes some time, so I created a custom thread class to parse the dictionaries in the background:

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;
};

The three "Dictionary" classes are created by me and they all inherit a common interface, which includes Q_OBJECT functionality to signal the main class over the head of the setup thread in Qt::QueuedConnection mode with progress updates while parsing, so it can display a progress bar. The setup thread is invoked from the main classes' constructor like this:

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();
}

I started having some trouble with the application crashing on exit and I couldn't see what the problem was so I tried to run it in the Visual C++ 2008's debugger. Then I get a huge crash during start-up:

Unhandled exception at 0x7568b727 in kanjiflash.exe: Microsoft C++ exception: std::exception at memory location 0x024ffa1c..

The stack trace shows:

KernelBase.dll!7568b727()
[Frames below may be incorrect and/or missing, no symbols loaded for KernelBase.dll]
KernelBase.dll!7568b727()
msvcr90d.dll!_heap_alloc_dbg_impl(unsigned int nSize=72, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0, int * errno_tmp=0x024ff9f8) Line 497 + 0xc bytes C++
msvcr90d.dll!_nh_malloc_dbg_impl(unsigned int nSize=72, int nhFlag=0, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0, int * errno_tmp=0x024ff9f8) Line 239 + 0x19 bytes C++
msvcr90d.dll!_nh_malloc_dbg(unsigned int nSize=72, int nhFlag=0, int nBlockUse=1, const char * szFileName=0x00000000, int nLine=0) Line 296 + 0x1d bytes C++
msvcr90d.dll!malloc(unsigned int nSize=1) Line 56 + 0x15 bytes C++
020bea68()
kanjiflash.exe!SetupThread::run() Line 391 + 0x2c bytes C++
QtCored4.dll!QThreadPrivate::start(void * arg=0x020bd0c8) Line 317 C++
msvcr90d.dll!_callthreadstartex() Line 348 + 0xf bytes C
msvcr90d.dll!_threadstartex(void * ptd=0x020bd8f0) Line 331 C
kernel32.dll!75593677()
ntdll.dll!77739d72()
ntdll.dll!77739d45()

The particular line in SetupThread::run() this refers to is the one where I try to run parseDictionary("..."). This call stack is obtained from under Windows7 64bit. In Windows XP 32bit I had an identical problem, only difference was that the stack went to the constructor of a QString(const char *ch) from SetupThread::run(), where it complained and showed the *ch buffer to be a few characters of garbage.

Now the strange thing is this only happens inside the debugger. Both the Debug and Release configurations work fine outside the debugger. While fumbling with the application investigating this, I found and fixed the error which made me use the debugger in the first place but I'm wondering what is really happening and what will I do if I need to use the debugger some day. As I am not fully versed in multi-threaded programming, I'm not sure if it's even possible to run them in a debugger meaningfully, or perhaps I'm doing something wrong, like working on pointers of the main class in the worker thread (access violation?). Any insight greatly appreciated.

1
hard to tell without seeing the code used to stop/terminate the thread, but suppose your MainForm object is deleted while the thread is still running, you're guaranteed to be in trouble. Also it might seem the exe works when no debugger is attached, while in fact it behaves exactly the same but the problem does not show because certain debugging features are only enabled under the debugger.stijn
The crash mentions a std::exception can you surround your main and your thread::run with a try catch and see from where the exception is thrown?David Feurle
@David: I did as you suggested but my catch statements don't seem to be executed at all, even with catch(...), and VS complains about "unhandled exception". Another mystery.neuviemeporte
@stijn: I can't imagine how or why the MainForm could be deleted, but I'll try setting up a breakpoint later and inspecting the object. As for the code, I have included the starting of the thread and am editing to show the termination (even though it's never reached as of now). Tell me what else you would like me to post. Thanks.neuviemeporte
it should be deleted else your program leaks memory, and it probably will be automatically by Qt at some point (if I remember correctly that's how Qt works: writing tons of new but never a corresponding delete); note that for all this I was referring to you saying the app crashes on exit, it might have nothing to do with the crash under the debugger when starting the threadstijn

1 Answers

1
votes

I stumbled on an answer today while stepping through the program in the debugger. Based on this stack trace I got before:

kernel32.dll!7d4e2366()
[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]
kernel32.dll!7d4e2366()
QtCored4.dll!QString::QString(const char * ch=0x0265ff24) Line 427 + 0x12 bytes C++
KanjiFlash.exe!SetupThread::run() Line 400 + 0x2c bytes C++
QtCored4.dll!QThreadPrivate::start(void * arg=0x02137f68) Line 317 C++
msvcr90d.dll!_callthreadstartex() Line 348 + 0xf bytes C
msvcr90d.dll!_threadstartex(void * ptd=0x02138828) Line 331 C
kernel32.dll!7d4dfe21()

...and seeing the "const char *ch" buffer contain garbage after the crash, I'd assumed the string literal somehow got corrupted and that was the cause of the exception. But I tried stepping into the first parseDictionary() function and it worked. The beggining of the function looks like this:

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");
    }
    /* ... */

I noticed the crash occured after this exception was thrown. Apparently Qt doesn't go along well with exceptions so this went unhandled and the message was silently discarded, but the exception crashed the application anyway. The reason this happened inside the debugger was that VS sets the working directory for a debugged binary in the project's directory rather than the output directory. I had a copy of my dictionaries/ directory in the "Debug" and "Release" output directories only so the files couldn't be found which caused the crash. When run from either the "Debug" or "Release" directories, they were opened correctly and nothing happened.

I made a copy of the dictionaries/ directory in the project directory also and it ran under the debugger without a problem. Furthermore, I removed all exception throwing directives from my code to avoid problems in the future (they don't seem to work well in Qt applications). I am not making this an accepted answer however, because I'd still like to know:

  1. Why didn't the exception thrown from the parser get caught anywhere (main(), MainForm::MainForm())? Did Qt do this and if so, how? Can you use exceptions in multithreaded code anyway?
    Edit: answer: I just found out exceptions can't cross thread boundaries. My try-catch directives were placed in main around the whole contraption and inside the MainForm's constructor where I called SetupThread::run(). When I put a try-catch block in run() itself, the contents of the catch block were correctly executed when an exception was thrown.
  2. Why did I get such a confusing stack trace output, leading me on a wild goose chase? It looked like there were problems with creating a QString from a const char * and/or with a malloc somewhere. I developed many "interesting" theories on the probable causes of such errors before I found out what really caused this...
    Actually, the stack traces from the debug-symbols-free Release made more sense now in retrospect: there were references to something like _CxxExceptionThrown in it and no references to either malloc or QString, but I just assumed it lacked the info to show the origin.