views:

68

answers:

2

I'm writing a GUI application, using Qt, which links to a third-party DLL that sometimes sends error messages to stderr. I'd like these error messages to be displayed in a window within my GUI.

I couldn't find an established way to redirect stderr (as opposed to std::cerr) even after much searching, so I wrote the following class myself:

class StdErrRedirect : public QObject
{
    Q_OBJECT

public:
    // Constructor
    StdErrRedirect(QTextEdit *errorLog,
                   QObject   *parent = NULL);

    // Destructor
    ~StdErrRedirect();

private slots:
    void fileChanged(const QString &filename);

private:    
    QFile              tmp;
    QFileSystemWatcher watcher;
    QString            tmpFileNameQtFormat;
    QString            tmpFileNameNativeFormat;

    QTextEdit *m_errorLog;
    QString   oldContent;
};

StdErrRedirect::StdErrRedirect(QTextEdit *errorLog,
                               QObject   *parent)
    : QObject(parent)
{
    // Store the pointer to the error log window
    m_errorLog = errorLog;

    // Create a temporary filename: first find the path:
    tmpFileNameQtFormat = QDir::tempPath();

    // Make sure the closing slash is present:
    if (!tmpFileNameQtFormat.endsWith(QChar('/')))
        tmpFileNameQtFormat.append(QChar('/'));

    // Add the file name itself:
    tmpFileNameQtFormat.append("nb_stderrlog");

    // Obtain a version of the filename in the operating system's native format:
    tmpFileNameNativeFormat = QDir::toNativeSeparators(tmpFileNameQtFormat);

    // Set up redirection to this file:
    freopen(tmpFileNameNativeFormat.toAscii().constData(), "a+", stderr);

    // Initialise the QFileSystemWatcher:
    connect(&watcher, SIGNAL(fileChanged(const QString &)),
            this,     SLOT(fileChanged(const QString &)));
    watcher.addPath(tmpFileNameQtFormat);

    tmp.setFileName(tmpFileNameQtFormat);
}

StdErrRedirect::~StdErrRedirect()
{
    // Ensure the temporary file is properly deleted:
    fclose(stderr);
    tmp.close();
    tmp.open(QIODevice::ReadWrite);
    tmp.remove();
}

void StdErrRedirect::fileChanged(const QString &filename)
{
    tmp.open(QIODevice::ReadOnly);
    QTextStream stream(&tmp);
    QString content = stream.readAll();
    tmp.close();

    // Identify what's new, and just send this to the window:
    int newchars = content.size() - oldContent.size();
    if (newchars)
    {
        m_errorLog -> append(content.right(newchars));
        oldContent = content;
    }
}

If I instantiate this from my main window using:

errorLog = new QTextEdit;
redirector = new StdErrRedirect(errorLog);

... then everything I write to stderr appears in the window.

So far, so good. The problem is that the DLL's output still does not. In a call to a DLL function which emits an error, if I put the code:

if (error != _OK)
{
    error.PrintErrorTrace();
    fprintf(stderr, "Should have printed an error \r\n");
    fflush(stderr);
    //fsync(_fileno(stderr));    Linux version
    _commit(_fileno(stderr));
    return;
}

...then the text "Should have printed an error" appears but the error message itself does not.

Now, I've read somewhere that this is probably because the redirection is being set up after the DLL was loaded at the beginning of the application, and so it's own stderr channel is unaffected. Therefore, I should be able to fix this by loading the DLL dynamically, after setting up the redirection, instead.

Here is my question, then: how do I do this? I can try putting the following code at the beginning of my application:

QLibrary extlib;
extlib.setFileName("libname");
extlib.setLoadHints(QLibrary::ResolveAllSymbolsHint);
extlib.load();

...but on its own it has no effect. I think this is because the linker is still setting the library up to be opened automatically. However, if I remove the DLL from the linker (I'm using VS2008, so I remove extlib.lib from the dependency list) then the application won't compile because the compiler can't find the symbols from the DLL.

So there's obviously something deeply wrong with what I'm trying to do here. Can anybody help?

Thanks, Stephen.

+1  A: 

Does the DLL really write to stderr? Or does it write to GetStdHandle(STD_ERROR_HANDLE) ? The first maps to the second, initially. But with freopen() you merely change the mapping. Anything written to STD_ERROR_HANDLE will still go there.

To redirect everyones error output, you would need SetStdHandle.

MSalters
Would I still need to delay loading the DLL for SetStdHandle to work, or would it change the handle retrospectively for an already-loaded DLL?
Eos Pengwern
Check the documentation: "The standard handles of a process may have been redirected by a call to SetStdHandle, in which case GetStdHandle will return the redirected handle". That implies that if the DLL calls GetStdHandle _before_ you call SetStdHandle, it _won't_ get the redirected handle. I.e. you must first redirect, then load the DLL.
MSalters
Well, even with delayed loading, the redirection still isn't happening using my original method (i.e. just redirecting stderr) so the finger is firmly pointing at GetStdHandle. I'll try this next.
Eos Pengwern
OK, implementing SetStdHandle in this framework proved even harder than I'd expected; the killer is that whereas the windows way of doing this works with file handles (see http://stackoverflow.com/questions/7664/windows-c-how-can-i-redirect-stderr-for-calls-to-fprintf for example), the QFileSystemWatcher which monitors the resultng file (and which in princple provides the "missing piece" from the reference just given) requires a file name. I found several procedures in MSDN and elsewhere which claimed to be able to give a filename from a file handle, but couldn't get any of them to compile.
Eos Pengwern
<continued...> As it happens, the vendor of the third-party DLL insists that it's sending its output to ordinary stderr with no GetStdHandle statements anywhere. They've reproduced the issue and are investigating it for me. In the meantime, I have another workaround whichs seems to meet my immediate needs.
Eos Pengwern
When you are calling SetStdHandle, you are in control of the file handle. You create the new handle using a know filename. Just remember it. As for the third party, they could very well send it to _their_ `stderr`. It won't be "reconciled" if they use a different CRT.
MSalters
A: 

There is only one stderr, so my guess is that the problem is not that you need to load the dll dynamically, but somewhere in your redirection code. Your redirection code is written Linux style, where in windows things work differently.

if you could test your application on Linux, It would help to pin point the problem. If it works on Linux, that it is surly the redirection code.

Anyway, you should read some more about redirection and windows, as I don't think that what you are trying to do now will help you.

uvgroovy
There's actually one `stderr` per C runtime.
MSalters
It's written in the Qt style, which I've only ever used on Windows although I do have one eye on future cross-platform capability. When used on Windows, it definitely redirects everything sent to stderr from anywhere in my own application, even from different threads. The question, therefore, is where the DLL's output is going, if not to the same stderr as my own application. That is what I hope the vendor will eventually be able to tell me.
Eos Pengwern