PDA

View Full Version : How to redirect printf to QTextEdit?



arcull
7th November 2013, 07:16
Hi guys, I'm developing a desktop gui app which uses an open source library. The library uses a lot of "printf" statements, which are not seen on my app gui. So I thought of redirecting the printf to a QTextEdit on my app, but don't know how to do it. Till now I found a post on redirection of std::cout to a QTextEdit (http://stackoverflow.com/questions/10308425/redirect-stdcout-to-a-qtextedit) which works ok, but I'd like to make something similar for "printf". Do you know how to do it (s short sample code would be perfect)? Is it possible at all? If not, what are the alternatives? Much thanks in advance.

stampede
7th November 2013, 19:10
Probably there is a better way, but this seems to be working:
1. redirect stdout to file (freopen (http://www.cplusplus.com/reference/cstdio/freopen/))
2. disable buffering (setvbuf (http://www.cplusplus.com/reference/cstdio/setvbuf/))
3. watch for changes with QFileSystemWatcher

ok, this is kind of "sledgehammer to crack a wallnut" thing (see "notes" section in QFileSystemWatcher documentation), but maybe you can use it until someone comes with a better solution
here is some code (tested only on linux):


// widget.h
include <QPushButton>
#include <QFileSystemWatcher>
#include <QMessageBox>
#include <cstdio>

class Widget : public QWidget{
Q_OBJECT
public:
explicit Widget(const QString& fileName, QWidget * parent = NULL)
:QWidget(parent)
,_nBytes(0)
{
QPushButton * btn = new QPushButton("add text", this);
connect(btn, SIGNAL(clicked()), this, SLOT(onClick()));
QFileSystemWatcher * watcher = new QFileSystemWatcher(this);
watcher->addPath(fileName);
connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString)));
}
private slots:
void onClick(){
static int counter = 0;
printf("text (%i)",counter++);
}
void fileChanged(const QString& path){
QFile f(path);
if( f.open(QIODevice::ReadOnly) ){
f.seek(_nBytes);
const QByteArray ba = f.readAll();
_nBytes += ba.size();
QMessageBox::information(this,"stdout",path + " changed:\n\n" + ba);
} else {
QMessageBox::critical(this,"stdout","can't open!");
}
}
private:
int _nBytes;

};

#endif

// main.cpp
#include <QApplication>
#include <cstdio>

#include "widget.h"

int main(int argc, char ** argv){
stdout = freopen("test.txt","w",stdout);
setvbuf(stdout, NULL, _IONBF, 0);
QApplication app(argc,argv);
Widget w("test.txt");
w.show();
return app.exec();
}



As my first programming teacher always said (quoting B. Kernighan, I think), "first make it work, then make it fast" :)

arcull
8th November 2013, 07:06
much thanks stampede, I'll try to implement your solution. Will report back, regards.

arcull
18th November 2013, 07:13
I've been very bussy latelly so couldn't spend much time on Qt. But anyway, the solutions from stampede works with small adjustment.
stdout = freopen("test.txt","w",stdout); must change to
freopen("test.txt","w",stdout); in my case on Windows and MinGw. Still I have problem when I try to reenable buffering. Here is the relevant part of my sample code

// redirect stdout to file
freopen("test.txt","w",stdout);

// disable buffering
setvbuf(stdout,NULL,_IONBF,0);

// check if file changed
QFileSystemWatcher * watcher = new QFileSystemWatcher(this);
watcher->addPath("test.txt");
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(QString)));

printf("second print\n");

printf("third print\n");

//library usage come here....

// reenable buffering
setvbuf(stdout,NULL,_IOFBF,0);

//return stdout to display
freopen( "CON","w",stdout);
but I get "Invalid parameter passed to C runtime function" on second setvbuf. Besides there's another issue I'd like to solve. The library has many printf-s when in debug mode, the code looks like this
#ifdef DEBUG
printf("unable to do...");
#endif however when I debug it in Qt creator I don't get the prints. It looks like it doesn't recongnize I am in debug mode. Is there something I'm missing? Do I have to adjust MinGw somehow? Much thanks again for your help.

stampede
18th November 2013, 07:26
change to freopen("test.txt","w",stdout);
sure, it was a leftover after fclose(); and stdout = fopen(...);

It looks like it doesn't recongnize I am in debug mode. Is there something I'm missing?
Is symbol "DEBUG" defined in debug mode ?

arcull
18th November 2013, 08:11
Is symbol "DEBUG" defined in debug mode ? Sorry but don't know how to check/do this. Would you please paste a short snippet of code, or a tutorial reference link? Thanks again.

stampede
18th November 2013, 08:43
for example, you can put this in .pro file:

build_pass:CONFIG(debug, debug|release) {
DEFINES += <symbols you want to be defined only in debug build>
}
so in your case DEFINES += DEBUG should be ok. Of course, this is all true under assumption that you can rebuild this library when compiling your project.

arcull
18th November 2013, 09:25
for example, you can put this in .pro file:

build_pass:CONFIG(debug, debug|release) {
DEFINES += <symbols you want to be defined only in debug build>
}
so in your case DEFINES += DEBUG should be ok. Of course, this is all true under assumption that you can rebuild this library when compiling your project. Great, it works :) Now the only problem I have to solve is "Invalid parameter passed to C runtime function" on second setvbuf. If I comment out the code like this
// redirect stdout to file
freopen("test.txt","w",stdout);

// disable buffering
setvbuf(stdout,NULL,_IONBF,0);

// check if file changed
QFileSystemWatcher * watcher = new QFileSystemWatcher(this);
watcher->addPath("test.txt");
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(QString)));

printf("second print\n");

printf("third print\n");

//library usage come here....

// reenable buffering
//setvbuf(stdout,NULL,_IOFBF,0);

//return stdout to display
//freopen( "CON","w",stdout); the error disapers, but that is not the solution :( Any suggestion how can I fix this?

stampede
19th November 2013, 07:49
#include <stdio.h>

int main(){
freopen("test.txt","w",stdout);
setvbuf(stdout,NULL,_IONBF,0);
printf("into the file");
freopen( "CON","w",stdout);
printf("console");
return 0;
}
This works ok on windows, tested with gcc 4.5.2 and 4.7.3. But of course it is not very portable ("CON":)). I didn't try that but on linux maybe you can reopen stdout with "/dev/tty".

arcull
26th November 2013, 07:42
Hi, I've found out that the buffer size 0 causes the problem, so I tried

setvbuf(stdout,NULL,_IOFBF,4000); and it doesn't complain any more. However I'd like to set the size of the buffer as it was before my redirection to file. Is there any command that would return the current buffer size and the buffer mode? Much thanks again, regards.

stampede
26th November 2013, 09:13
setvbuf(stdout,NULL,_IOFBF,4000);
I think you can use BUFSIZ macro instead of 4000.
On linux you could use extra io functions defined in stdio_ext.h to get stream parameters, but I don't know if there exists any win32 port (if you know please post it). Here is a short manual : Controlling buffering (http://www.gnu.org/software/libc/manual/html_node/Controlling-Buffering.html#Controlling-Buffering).
Why are you concerned about the exact size of the console output buffer ?

freopen( "CON","w",stdout); should reopen output stream with default parameters.

This approach can cause some troubles if the stdout was redirected (if you start a program from the console and redirect the stdout via ">"). Example:

#include <iostream>
#include <stdio.h>

int main(){
std::cout << "std cout 1\n";
freopen("test.txt","w",stdout);
std::cout << "into test.txt\n";
freopen("con","w",stdout);
std::cout << "std cout 2\n";
return 0;
}
If you start this program from the console like "a.exe > test2.txt" then only "std cout 1\n" will be written to test2.txt. "std cout 2\n" will be printed on the console window, as the stdout was reopened with default parameters.
A solution is to duplicate the output stream before redirecting it: dup (http://man7.org/linux/man-pages/man2/dup.2.html):

#include <iostream>
#include <stdio.h>
#include <unistd.h>

int main(){
int std_fd = dup(fileno(stdout));
if (std_fd == -1){
std::cout << "dup failed: " << errno << "\n";
return 0;
}

std::cout << "std cout 1\n";

freopen("test.txt","w",stdout);
std::cout << "into test.txt\n";

fflush(stdout);
if( -1 == dup2(std_fd, fileno(stdout)) ){
return errno;
}
close(std_fd);
std::cout << "std cout 2\n";

return 0;
}
This program will correctly restore the redirected output, because it duplicates the original stdout file descriptor.

arcull
28th November 2013, 06:40
Thanks stampede. I see you know a lot about this stuff, but I don't, at least not yet ;)

Why are you concerned about the exact size of the console output buffer ? Well as I want to redirect the outputs just for the time I'm usging the external open source library in my app. So I thought, if I disable buffering before before library calls, I should also enable them again after I finish using it. But if

freopen( "CON","w",stdout); reenables buffering with default parameters, then I obviously don't need some extra commands.

Please help me understand one more thing. Does the "freopen" mean that all commands like (cout, printf,...don't know others yet) which by default output to screen/console, will get redirected to file in my case? I so, that's ok, since I want to intercept all the messages the library spits out.

If you start this program from the console like "a.exe > test2.txt" then only "std cout 1\n" will be written to test2.txt. "std cout 2\n" will be printed on the console window, as the stdout was reopened with default parameters. my app is gui desktiop app, so I don't need to take care of this. But will definitely try the solution with "dup", just for the sake of learning something new. Much thanks again.

stampede
28th November 2013, 07:28
Does the "freopen" mean that all commands like (cout, printf,...don't know others yet) which by default output to screen/console, will get redirected to file in my case?
Yes, assuming that this library won't redirect the stdout somewhere else:)