PDA

View Full Version : How to disconnect signal/slot?



arcull
16th July 2014, 18:49
Hi everyone. I'm using a third party library in my app written in C, which uses a lot of printf statements. I wanted to redirect that prints to a textEdit on my app. So with the help of others I wrote a redirection function, which redirects stdout to a file and adds text to my textEdit whenever content of file changes. So far so good everything works ok. However at the end external lib call I would like to quit that redirection and delete the file as well, but when I do so I get an error that redirection file does not exist anymore. This would probably mean that signal/slot connection still exists. How can I fix this? Much thanks for help in advance. The code looks like this:
void MainWindow::RedirectOutput(int type) {
QFileSystemWatcher * watcher = new QFileSystemWatcher(this);
if (type==0) {
// redirect stdout to file
freopen("redirect.txt","w",stdout);

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

// check if file changed

watcher->addPath("redirect.txt");
connect(watcher,SIGNAL(fileChanged(QString)),this, SLOT(HandleFileChange(QString)));
} else {
// reenable buffering, not really needed
//setvbuf(stdout,NULL,_IOFBF,4000);

//return stdout to display
freopen( "CON","w",stdout);
disconnect(watcher,SIGNAL(fileChanged(QString)),th is,SLOT(HandleFileChange(QString))); //<doesnt' work
if (QFile::exists("redirect.txt"))
QFile::remove("redirect.txt");
}
}

void MainWindow::HandleFileChange(const QString filename){
QFile f(filename);
if (f.open(QIODevice::ReadOnly)) {
//QMessageBox::information(this,"info",QString::number(f.size()));
f.seek(fbytes);
const QByteArray ba = f.readAll();
fbytes += ba.size();
//QMessageBox::information(this,"stdout",filename + " changed:\n\n" + ba);
//cout << qPrintable(ba) << endl;
ui->textEdit->append(qPrintable(ba));
} else {
QMessageBox::critical(this,"stdout","Can't open file");
}
}

anda_skoa
16th July 2014, 19:13
Your disconnect looks correct.

Alternative disconnect() options are:

- delete either sender or receiver, in your case probably the sender
- call disconnect() on the sender to disconnect all receivers
- call disconnect with 0 for slot and signal but provide sender and receiver to disconnect a certain receiver
- (Qt5 onwards) store the result of the connect and use this QMetaObject::Connection value as an argument to QObject::disconnect()

Cheers,
_

yeye_olive
16th July 2014, 19:13
I suspect that the fact that the file exists or not has nothing to do with the connection. You claim that things do not work as expected. Here are a few hints:

have you run a debugger?
what are the return values of the functions that potentially fail (freopen, disconnect, etc.)?
have you actually seen the file being created?
have you actually seen the file grow bigger?

arcull
17th July 2014, 19:32
Thank you both for suggestions. It was my fault, I called function RedirectOutput with 0 at the beginning and RedirectOutput with 1 at the end of external library call, but this way I had different "watcher" created 2 times.
QFileSystemWatcher * watcher = new QFileSystemWatcher(this); I solved this, so the signal-slot gets disconnected, but now I have a bigger problem. It looks like the external lib function call executes so fast that signals don't fire in time, or the thing runs asynchronously. So it looks like, I disconnect signal-slot before the signals arrive. I figured out this by adding some custom marks to my textEdit and watched the sequence how they got appended. Here is some pseudo code
put redirect to file & connect signal-slot
execute external lib code
get output back to screen and disconnect signal-slot I can post entire code if necessary. Do you have any suggestion how could I solve this. I'm banging my head against the wall the entire afternoon with this, so any idea much appreciated.

yeye_olive
18th July 2014, 10:07
You cannot rely on QFileSystemWatcher to keep you informed of all changes to the file in a timely manner. With your current architecture, perhaps you should inspect the file one last time before you delete it, and catch any output not reported by the watcher.

There are alternatives, but I am afraid they are platform-specific.

Under a POSIX system, you can call open_memstream() to open a growable memory stream and try to assign the resulting stream to stdin (but that may not work as stdin is not guaranteed to be assignable), but you will need to poll the stream regularly to get its contents.

Alternatively, you may use pipe() and dup2() to redirect the underlying stdout file descriptor to a pipe, and use QSocketNotifier to get notifications whenever data arrives in the pipe. Beware of deadlocks though: printf() in the library will block whenever the pipe's buffer is full, so it is vital that the QSocketNotifier live in another thread whose job is to:
quickly read from the pipe,
decode the bytes with a stateful decoder (QTextDecoder) into a growable QString,
emit a signal to notify the GUI thread that data is available,
NEVER write anything to stdout!

When the main GUI returns to its event loop and picks up the signal, it can consume the contents of the QString and append them to the display widget.

I see no way around the asynchrony of these solutions: you have no way to execute arbitrary code (like appending text to a display widget) in the middle of a printf() call by the library. Arguably it would be much easier if your library lived in another process or had alternative ways to log its output.

arcull
18th July 2014, 10:42
thanks yeye_olive, the solution you describe looks a bit complicated for my current level of knowledge in c++ and qt, however the idea of having a second thread for invoking external lib would probably be simpler or at least better, I've done something like that once in Java, but I have to refresh/check how the principle how that works. One question though: if I redirect display of stdout to file in second thread, do I still have normal stdout in first (main) thread? Much thanks again.

yeye_olive
18th July 2014, 11:00
thanks yeye_olive, the solution you describe looks a bit complicated for my current level of knowledge in c++ and qt, however the idea of having a second thread for invoking external lib would probably be simpler or at least better, I've done something like that once in Java, but I have to refresh/check how the principle how that works.
Actually the solution I outlined in my previous post assumed that the library calls were executed in the main (GUI) thread; the other thread is only needed to read from the pipe while the library writes to it through stdout. The thread does not even need to be visible outside of the logging mechanism.


if I redirect display of stdout to file in second thread, do I still have normal stdout in first (main) thread?
Unfortunately, the answer is no. stdout is a process-wide stream. It would be great if the library were able to write to a custom logger instead of stdout; you could then implement a logger that would append the text to your widget. If you need a separate stdout for the rest of your program, then I am afraid your only option is to spawn a process that will execute the library calls on your behalf.

arcull
18th July 2014, 11:45
thanks yeye_olive. Well I have to consider how much gain do I get with how much trouble. The point is that the external lib uses pritfs when you invoke it's functions and since I have a gui app, I could leave the stdout redirection to file active the whole time, just for the purpose of external lib. If I want to print out something to the qui interface like textEdit, I don't really need printfs. I mean I can avoid the print to stdout from my main app, I can use textEdit->append and similar methods. Or am I missing something here? Thanks for your help.

anda_skoa
18th July 2014, 13:32
Depending on what kind of data you need to transfer between your code and the library, one option could be to run the library in a helper program through QProcess.

Cheers,
_

arcull
18th July 2014, 13:42
thanks anda_skoa, but I'm not sure I understand what you mean by QProcess. Is this second thread approach? Please provide more info and how to intercept library string output this way? Type of data the library outputs is more or less just some strings explaining progress and results nothing more. Thanks.

anda_skoa
18th July 2014, 15:37
Not a thread, a separate process.

QProcess is Qt's facility for running other programs, it can write to the other programs stdin and read its stdout and stderr.

Depending on the data to the library it might be possible to create a small helper program that is run by the main application as a child process.

Cheers,
_

arcull
20th July 2014, 09:45
I've been tackling this issue a bit more and came to a conclusion, that in my case it is better not to use signal/slots at all. I've done it this way: whenever I need to call the external lib functions I first invoke my function which redirects stdout to a file, then call the function and afterwards call another my function which set stdout to normal, reads content of the redirection file and appends it to my textEdit. Thank you again all for you suggestions.