PDA

View Full Version : Redirect printf to QTextEdit?



Randulf
5th October 2006, 11:29
Hi!
I first posted this question in the qtforum.org and was asked to also post it here. Link to the thread (http://www.qtforum.org/thread.php?threadid=18362).

I have qtgui that uses a lot of classes that dont use qt. In those classes there are a lot of printf. I need to redirect them form the console to a QTextEdit or some sort of component in my gui for example one of these (http://www.qtcentre.org/forum/f-qt-software-16/t-qlogtext-qlogtable-2-widgets-to-display-text-log-2792.html/?highlight=qlogtext):
. I cant change the printfs since its not my code and there could be a lot of them hidden.
I have tried the class in this thread (http://www.qtforum.org/thread.php?threadid=678&hilight=redirect+cout)
and it works great for std::cout but not for printf and there is also seems to be some issues when using threaded classes.

I guess I have to pipe the stdout in some way but I haven't a clue on how to do it.
Would really appriciate any help I could get.

There is some code that might help in understanding what I want to do in the original post (http://www.qtforum.org/thread.php?threadid=18362).

The q_debugstream.h is here (http://www.qtforum.org/thread.php?threadid=678&hilight=redirect+cout):

[Edit] Forgot to say, it's windows

wysota
5th October 2006, 11:52
You could try:


QFile f;
f.open(STDOUT, QFile::ReadOnly);
and then read from it. It will probably not work, because probably you can't read from STDOUT, but it's the only way I can think of that would work on Windows without using an external process (using QProcess). If the "printf-enabled" application was a separate process which you could call from your application, there wouldn't be a problem.

high_flyer
5th October 2006, 11:54
I don't see how the Qt qrapper classes in the threads you quoten could help you even if they would redirect printf().
You said you can't/don't want to change the code where the printf()'s are, since you are not sure you can find them (which is not true, you can apply a search on all the files in the project).
Never mind for what reason, you don't want to change the code - but applying the wrapper classes will demand that you insert code that uses the wrappers.

A posibility to do what you want would be to use QProcess and to read the printf() output from the console in to a QTextEdit or any other place.
This way you add only one code portion that deals with all the printf() output.
EDIT: beat by wysota :)

Randulf
9th October 2006, 09:55
I don't see how the Qt qrapper classes in the threads you quoten could help you even if they would redirect printf().
Well they won't, the first one was just an example of an output and the other link was able to redirect std::cout's to an QTextEdit but not printf.


You said you can't/don't want to change the code where the printf()'s are, since you are not sure you can find them (which is not true, you can apply a search on all the files in the project).
Yes I can find them but I don't want to change them because then I have to do it everytime the code is updated. But I can't find the ones or change the ones that might be in the libs, and I know there are a few.


A posibility to do what you want would be to use QProcess and to read the printf() output from the console in to a QTextEdit or any other place.
Hmm, that might actually be a good idea. I will take a look at that.

This is what I have done so far. It doesent really work yet but I think this is one way to do it. I created a pipe and made a dublicate of stdout. Then I just have to read from it and output it in my TE.

#include <QApplication>

#include <QTimer>
//#include "q_debugstream.h"
#include "stdoutredirector.h"
#include "testwidget.h"

int main(int argc, char **argv)
{
printf("This should be display!\n");


StdOutRedirector *redir = new StdOutRedirector;

//printf("This should go to fdguistd\n");


QApplication app(argc, argv);
TestWidget tw;
tw.show();
redir->setOutputTF(tw.qte);
printf("Testing redirection\n");
redir->readOutsToTF();

// Make a QTimer that update the read every 40 ms
QTimer *redirTimer = new QTimer;
//QObject::connect(redirTimer, SIGNAL(timeout()), redir, SLOT(readOutsToTF()));
//redirTimer->start(40);

//Q_DebugStream cout(std::cout, tw.qte);
//Q_DebugStream cerr(std::cerr, tw.qte);

std::cout << "Printing something else" << std::endl;
redir->readOutsToTF();
return app.exec();
}

#ifndef STDOUTREDIRECTOR_H
#define STDOUTREDIRECTOR_H
#include <QString>
#include <iostream>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <QTextEdit>

class StdOutRedirector : public QObject
{
Q_OBJECT
public:
StdOutRedirector()
{
// Redirect
if(_pipe(fdguistd, 512, _O_BINARY) == -1)
printf("failed!");
// Duplicate stdout file descriptor (next line will close original)
fdStdOut = _dup(_fileno(stdout));
// Duplicate write end of pipe to stdout file descriptor
if(_dup2(fdguistd[1], _fileno(stdout)) != 0)
printf("failed!");
// Close original
close(1);
// Duplicate write end of original
dup2(fdguistd[1], 1);

}
void setOutputTF(QTextEdit *_output)
{
output = _output;
}
//public slots:
void readOutsToTF()
{
int nout;
char *buffer = new char [512];
//char buffer[512];
printf(" ");
fflush(stdout);

nout = _read(fdguistd[0], buffer, 512);

if(nout <= 0)
return;
if(nout) {
QString str;
//output->append(str.setNum(nout));
output->append(QString(buffer));
}
delete buffer;
}
private:
QTextEdit *output;
int fdStdOut;
int fdguistd[2];

};


#endif

One problem is that when there is nothing in the fdguistd buffer it stops there. Another is that it doesent output the text in TE until it reaches the last line in the main method.

cwomeijer
9th October 2006, 19:00
I seem to have a similar request for my project.
In my huge C code base we printed:
Informational messages,
Warning messages,
Error messages.

All of the obviously doen with printf().
So we created our own version that sended out signals
with the corresponding ascii line as an argument.
I was lucky that we had our own routines for printing
but I guess some nifty #define can do the trick/ or a big grep.

Anyway the signal solution is realy neat. It's thread safe and does
not add a lot of overhead. I tested it with several million of messages
and there was not a big impact on the performance.

You do need to take care of the C -> C++ wrappers though.

gfunk
9th October 2006, 20:17
One problem is that when there is nothing in the fdguistd buffer it stops there.
Perhaps there is a non-blocking version of _read() that you can call?


Another is that it doesent output the text in TE until it reaches the last line in the main method.
Perhaps need a call to fflush(stdout) ? Or maybe some way to send EOF byte?

Randulf
11th October 2006, 08:16
So we created our own version that sended out signals
with the corresponding ascii line as an argument.
But then you had to implement a signal in every class?
Or how did you do it?

Perhaps there is a non-blocking version of _read() that you can call? Exactly, been looking for that or the possibility to set fcntl O_NONBLOCK but I cant find how to that in windows. For now problem was solved by adding a \n before I do fflush.

Now I think it's working with some cheating.


#ifndef STDOUTREDIRECTOR_H
#define STDOUTREDIRECTOR_H
#include <QString>
#include <iostream>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <QTextEdit>

class StdOutRedirector : public QObject
{
Q_OBJECT
public:
StdOutRedirector()
{
// Redirect
if(_pipe(fdguistd, 4096, _O_BINARY) == -1)
printf("failed!");
//int tr = fcntl(fdguistd, O_NONBLOCK);
// Duplicate stdout file descriptor (next line will close original)
fdStdOut = _dup(_fileno(stdout));
// Duplicate write end of pipe to stdout file descriptor
if(_dup2(fdguistd[1], _fileno(stdout)) != 0)
printf("failed!");
// Close original
close(1);
// Duplicate write end of original
dup2(fdguistd[1], 1);

}
void setOutputTF(QTextEdit *_output)
{
output = _output;
}
public slots:
void readOutsToTF()
{
int n_out;
char *buffer = new char [4096];
QString str;
//char buffer[512];
printf("\n");
fflush(stdout);

n_out = _read(fdguistd[0], buffer, 4096);

if(n_out <= 0)
return;
if(n_out > 1) {
str.append(QString(buffer));
int con = str.lastIndexOf('\n');
int remv = str.at(con-1) == '/n' ? 1 : 0;
if(con) {
str = str.remove(con-remv, str.length());
output->append(str);
}
}

}
private:
QTextEdit *output;
int fdStdOut;
int fdguistd[2];
};
#endif

cwomeijer
11th October 2006, 17:59
Because we had a huge code base (10M lines C code)
we wrote a wrapper around all of this as a single class.
Now this class obvious has the information, warning and error singals
defined in them.

From there it can be used throughout the code as any other class would be.
We needed to write little wrapper routines such that the emits could be
called inside our C code but that was not to difficult.

Again it is not the cleanest solution, however it did the trick with minimal
modification to our original code base.

If wanted I can post a little snippet of our code.
(I don't have acces to the code right now)

Randulf
11th October 2006, 20:42
Because we had a huge code base (10M lines C code)
we wrote a wrapper around all of this as a single class.
Now this class obvious has the information, warning and error singals
defined in them.

From there it can be used throughout the code as any other class would be.
We needed to write little wrapper routines such that the emits could be
called inside our C code but that was not to difficult.

Again it is not the cleanest solution, however it did the trick with minimal
modification to our original code base.

If wanted I can post a little snippet of our code.
(I don't have acces to the code right now)

That would be really nice, it's always intresting to view other solutions.
The above code actually works (to my surprise), tried it today in my application. And it works for both std::cout and printf's but not qDebug. It still needs some work though.

cwomeijer
12th October 2006, 14:57
Here is the code we use inside the class:

void CatsCore::makeSignal(int type, char *str)
{
QString msg(str);

switch (type) {
default:
case 0:
emit information(msg);
break;
case 1:
emit warning(msg);
break;
case 2:
emit error(msg);
break;
case 3:
emit progress(msg);
break;
}
}
}

extern "C" void emitInfo(char *str)
{
Cats::globalCats->makeSignal(0,str);
}

extern "C" void emitWarning(char *str)
{
Cats::globalCats->makeSignal(1,str);
}

extern "C" void emitError(char *str)
{
Cats::globalCats->makeSignal(2,str);
}

extern "C" void emitProgress(char *str)
{
Cats::globalCats->makeSignal(3,str);
}


Because there can only be one Cats class we use the Cats::globalCats
to get a hold of the instance.