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.