1

The goal is to display all the application output shown in QtCreator to a QTextEdit as a debug console window so with the same application only those who have password are allowed to see the console window while normal users cannot. There is an exe with several dlls. All std::cout from DLLs and qDebug are needed to be shown in the debug console window.

To achieve this, I have followed http://www.qtforum.org/article/39768/redirecting-std-cout-std-cerf-qdebug-to-qtextedit.html

The code works great for single thread but is hanged when a thread is started to call functions in DLL. I would like to know how to fix the problem.


code

In mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>




MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
     ui->setupUi(this);

    m_qd = new Q_DebugStream(std::cout,ui->textEdit); //Redirect Console output to QTextEdit
    m_qd->Q_DebugStream::registerQDebugMessageHandler(); //Redirect qDebug() output to QTextEdit

    connect(ui->pushButtonSingleThreadTest, SIGNAL(clicked()),this,SLOT(SingleThreadTest()));
    connect(ui->pushButtonMultiThreadTest, SIGNAL(clicked()),this,SLOT(MultiThreadTest()));


}

void MainWindow::SingleThreadTest()
{
    run();
}

void MainWindow::MultiThreadTest()
{
    QThread     *workerThread= new QThread;
    ThreadWorker *worker  = new ThreadWorker();
    worker->moveToThread(workerThread);
    connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));
    connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    workerThread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

In mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "q_debugstream.h"
#include "../ToyProj1/Header.h"
namespace Ui {
class MainWindow;
}

class ThreadWorker : public QObject
{
    Q_OBJECT
public:
    ThreadWorker()
    {
    }
private:

signals:

    void finished();

public slots:
    void doWork()
    {
        run();
        emit finished();
    }
};


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void SingleThreadTest();
    void MultiThreadTest();



private:
    Ui::MainWindow *ui;

      Q_DebugStream* m_qd;
};

#endif // MAINWINDOW_H

in q_debugstream.h

#ifndef Q_DEBUGSTREAM_H
#define Q_DEBUGSTREAM_H



#include <iostream>
#include <streambuf>
#include <string>

#include "QTextEdit.h"

class Q_DebugStream : public std::basic_streambuf<char>
{
public:
    Q_DebugStream(std::ostream &stream, QTextEdit* text_edit) : m_stream(stream)
    {
        log_window = text_edit;
        m_old_buf = stream.rdbuf();
        stream.rdbuf(this);
    }

    ~Q_DebugStream()
    {
        m_stream.rdbuf(m_old_buf);
    }

    static void registerQDebugMessageHandler(){
        qInstallMessageHandler(myQDebugMessageHandler);
    }

private:

    static void myQDebugMessageHandler(QtMsgType, const QMessageLogContext &, const QString &msg)
    {
        std::cout << msg.toStdString().c_str();
    }

protected:

    //This is called when a std::endl has been inserted into the stream
    virtual int_type overflow(int_type v)
    {
        if (v == '\n')
        {
            log_window->append("");
        }
        return v;
    }


    virtual std::streamsize xsputn(const char *p, std::streamsize n)
    {
        QString str(p);
        if(str.contains("\n")){
            QStringList strSplitted = str.split("\n");

            log_window->moveCursor (QTextCursor::End);
            log_window->insertPlainText (strSplitted.at(0)); //Index 0 is still on the same old line

            for(int i = 1; i < strSplitted.size(); i++){
                log_window->append(strSplitted.at(i));
            }
        }else{
            log_window->moveCursor (QTextCursor::End);
            log_window->insertPlainText (str);
        }
        return n;
    }

private:
    std::ostream &m_stream;
    std::streambuf *m_old_buf;
    QTextEdit* log_window;
};

#endif // Q_DEBUGSTREAM_H

In DLL,

int run()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

The code sample is uploaded to github for reference. Build ToyProj1 and ToyProj1GUI when repeat the problem.

https://github.com/kuwt/ToyProject.git

Ronald Ku
  • 319
  • 2
  • 14
  • An exe isn't "connected" to a DLL. Once it's loaded the DLL code is effectively part of the executable's code. You can't normally detect whether a call comes from a DLL function or from a main program function. – Cheers and hth. - Alf Oct 25 '17 at 08:10
  • Thanks for your clarification. I will modify my wordings. Do you mean the problem is not in the dll but multithread? – Ronald Ku Oct 25 '17 at 08:17
  • @Cheersandhth.-Alf You are correct. I thought DLL was part of the problem. In fact it is not. – Ronald Ku Oct 25 '17 at 08:22
  • The problem here is your QTextEdit, Qt Application GUI run in the main thread, but if each Thread write something with std::cout, each thread will attempt to write inside your QTextEdit wich is in your main thread. – Bastien Thonnat Oct 25 '17 at 08:26
  • @BastienThonnat How does application output handle this? – Ronald Ku Oct 25 '17 at 08:30
  • 1
    You have multiple option, one would be to redirect each call of std::out inside your mainthread with signal slot, another would be to use mutex to block and release your writting with std::out – Bastien Thonnat Oct 25 '17 at 08:34
  • Look at that https://stackoverflow.com/questions/29449237/qt5-how-to-use-qdebug-to-log-in-a-file-multi-thread-application – Bastien Thonnat Oct 25 '17 at 08:39
  • @RonaldKu If you want to know how QtCreator handle the multiple output, they have created multiple private class for that https://github.com/qt-creator/qt-creator/blob/v4.4.1/src/libs/utils/synchronousprocess.h – Bastien Thonnat Oct 25 '17 at 09:11
  • @BastienThonnat Thanks and the code works when using mutex. Can I understand more about the details of the first option you mention? – Ronald Ku Oct 25 '17 at 09:35
  • For the first you could create one singleton in the main thread, and connect your thread output with signal to the singleton, place your std::out in your singleton slot and everything will be good, SIGNAL/SLOT are thread safe – Bastien Thonnat Oct 25 '17 at 09:40
  • If you need some example I could fork your project on github – Bastien Thonnat Oct 25 '17 at 09:42
  • @BastienThonnat I cannot understand how to do "connect your thread output with signal" and "place your std::out in your singleton slot". If examples can be provided in the project. It would be perfect. – Ronald Ku Oct 25 '17 at 09:54
  • @RonaldKu look at your github project, I send you one pull request with 2 subproject made with Qt 5.9.2 MSVC 2015 x64 & QtCreator, one with Mutex and one with SIGNAL/SLOT behaviour – Bastien Thonnat Oct 25 '17 at 11:44
  • Use _ReturnAdress() and VirtualQuery to obtain if the address is from a .DLL. – user3606329 Oct 25 '17 at 20:12
  • @BastienThonnat I have read the Mutex example. The mutex is installed in the thread function. I don't understand why the mutex is not install in the message handler directly. In your case, why does single thread not compete resources with the multi thread one. – Ronald Ku Oct 26 '17 at 03:21
  • @RonaldKu, you are perfectly right, I forgot to add it in the main thread, my bad – Bastien Thonnat Oct 26 '17 at 05:06

2 Answers2

2

With the help comments in question, this solution works well with signal and slot mechanism. std::cout and qDebug are redirected to QTextEdit.

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    // Setup QMessageCatch
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
     qInstallMessageHandler(MainWindow::QMessageOutput);
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    return a.exec();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
#include <QTextEdit>
#include "q_debugstream.h"
namespace Ui {
class MainWindow;
}

class ThreadWorker : public QObject
{
    Q_OBJECT
public:
    ThreadWorker()
    {
    }
private:

signals:

    void finished();

public slots:
    void doWork()
    {

         std::cout<< "Hello World2" <<std::endl;
        qDebug() << "Hello World2q" ;
        emit finished();
    }
};



class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    // QMessage
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
   static  void QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg);
   /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/

public slots:
    void SingleThreadTest();
    void MultiThreadTest();



private:
    Ui::MainWindow *ui;

    // MessageHandler for display and ThreadLogStream for redirecting cout
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    MessageHandler *msgHandler = Q_NULLPTR;
      ThreadLogStream* m_qd;
        /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QObject>
#include <QThread>


// Catch QMessage, redirect to cout
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
void MainWindow::QMessageOutput(QtMsgType , const QMessageLogContext &, const QString &msg)
{
   std::cout<<msg.toStdString().c_str()<<std::endl;
}
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/




MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
     ui->setupUi(this);

     // Set up ThreadLogStream, which redirect cout to signal sendLogString
     // Set up  MessageHandler,  wgucg catch message from sendLogString and Display
     /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    m_qd = new ThreadLogStream(std::cout); //Redirect Console output to QTextEdit
    this->msgHandler = new MessageHandler(this->ui->textEdit, this);
    connect(m_qd, &ThreadLogStream::sendLogString, msgHandler, &MessageHandler::catchMessage);
    /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/


    connect(ui->pushButtonSingleThreadTest, SIGNAL(clicked()),this,SLOT(SingleThreadTest()));
    connect(ui->pushButtonMultiThreadTest, SIGNAL(clicked()),this,SLOT(MultiThreadTest()));
}



void MainWindow::SingleThreadTest()
{

    std::cout<< "Hello World1" <<std::endl;
    qDebug() << "Hello World1q" ;
}

void MainWindow::MultiThreadTest()
{
    QThread     *workerThread= new QThread;
    ThreadWorker *worker  = new ThreadWorker();
    worker->moveToThread(workerThread);
    connect(workerThread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), workerThread, SLOT(quit()));

    connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
    connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
    workerThread->start();
}

MainWindow::~MainWindow()
{
    delete ui;
}

q_debugstream.h

#ifndef ThreadLogStream_H
#define ThreadLogStream_H
#include <iostream>
#include <streambuf>
#include <string>
#include <QScrollBar>
#include "QTextEdit"
#include "QDateTime"

// MessageHandler
 /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
class MessageHandler : public QObject
{
    Q_OBJECT
    public :
        MessageHandler(QTextEdit *textEdit, QObject * parent = Q_NULLPTR) : QObject(parent), m_textEdit(textEdit){}

    public slots:
        void catchMessage(QString msg)
        {
           this->m_textEdit->append(msg);
        }
        /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
    private:
        QTextEdit * m_textEdit;
};
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/


class ThreadLogStream : public QObject, public std::basic_streambuf<char>
{
Q_OBJECT
public:
    ThreadLogStream(std::ostream &stream, QObject * parent = Q_NULLPTR) :QObject(parent), m_stream(stream)
    {
        m_old_buf = stream.rdbuf();
        stream.rdbuf(this);
    }
    ~ThreadLogStream()
    {
        // output anything that is left
        if (!m_string.empty())
        {
        emit sendLogString(QString::fromStdString(m_string));
        }
        m_stream.rdbuf(m_old_buf);
        }
protected:
    virtual int_type overflow(int_type v)
    {
        if (v == '\n')
        {
            emit sendLogString(QString::fromStdString(m_string));
            m_string.erase(m_string.begin(), m_string.end());
        }
        else
            m_string += v;
        return v;
    }
    virtual std::streamsize xsputn(const char *p, std::streamsize n)
    {
        m_string.append(p, p + n);
        long pos = 0;
        while (pos != static_cast<long>(std::string::npos))
        {
            pos = static_cast<long>(m_string.find('\n'));
            if (pos != static_cast<long>(std::string::npos))
            {
                std::string tmp(m_string.begin(), m_string.begin() + pos);
                emit sendLogString(QString::fromStdString(tmp));
                m_string.erase(m_string.begin(), m_string.begin() + pos + 1);
            }
        }
        return n;
    }
private:
    std::ostream &m_stream;
    std::streambuf *m_old_buf;
    std::string m_string;
signals:
    void sendLogString(const QString& str);
};
#endif // ThreadLogStream_H
Ronald Ku
  • 319
  • 2
  • 14
0

for long transactions, you can add: QCoreApplication::processEvents(); in the catchMessage(QString(msg) method of the class MessageHandler in q_debugstream.h, after the append() call. This updates textEdit 'as soon as possible'.

Paul
  • 1