How to write a nice console application with Qt and Qt Creator.

Wim Peeters

Image LubbyLogo5

http://www.lubby.org

Abstract:

You find quite a lot of information about writing Qt applications but the information for console application is hard to find. This article shows you a few problems you can run into when writing your first console application. The application will be created with the free version of qt and qt creator will be used as an IDE. Everything is compiled under Debian linux but can also be compiled under Windows.

1 Getting started

We start this article with a working Qt Creator IDE environment. Please have a look at the ebook about how to install Qt creator when you do not have the IDE installed yet. The goal of the application is to create a working template of a console application by running into all problems you might get. I will explain why these problems exist and show you some different ways to solve them. I love console applications and need them for systems without GUI. Qt is a wonderfull framework and not limited to GUI applications.

1.1 Creating a console application in Qt creator

We start by creating a new project.

Image creatingproject

We give the project a name and a directory.

Image naming

We select the modules we need for the application. If we do now add modules now, we can add them to the project later.

Image selectingmodules

Qt creater created two files for us. The first one is consoleapp.pro. The contents is the following:

#-------------------------------------------------
#
# Project created by QtCreator 2009-11-18T21:47:34
#
#-------------------------------------------------

QT       -= gui

TARGET = consoleapp
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp

This is a Qt project file and we do not have to change it now. The second one is the main.cpp file. As you might expect, this program is not doing anything. I even have bad news for you, if you run it, it does not end!

#include <QtCore/QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    return a.exec();
}

1.2 Ending the application and show some output

The application does not end because we never told it to end. It is running a messageloop and exactly doing what it should. To make it end, we need to send it a signal. Now we want to run into all the problems we might run into. I changed the application and gave it a timer which will end the application after 5 seconds. Using the timer makes it possible to show you a few pitfalls. To see some output, I created a QTextStream which will be written to stdout. To have some more output, I also added an iostream and a QDebug message. This program will compile just normal, the funny thing about it, is the output and the timing. Why? (In a normal application you might use one of the output possibilities and stick to it.)

#include <QtCore/QCoreApplication>
#include <QTimer>
#include <QTextStream>
#include <QDebug>
#include <iostream>

//by Wim Peeters: a console application
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv); //renamed the a to app
    QTextStream qout(stdout); //I connect the stout to my qout textstream

    qout <<     "1. Starting the application\n";
    std::cout << "2. Some normal iostream output before using qDebug\n";
    qDebug() << "3. Some output with qDebug after the iostream output\n";

    QTimer::singleShot(5000, &app, SLOT(quit())); //stop after 5 seconds

    return app.exec(); //and we run the application
}

The output of this application is not what you expect. You will have to run the application to know exactly what I mean. The first two lines of output will be shown immediately. The one you expect to be the first will be the last and it will be shown after 5 seconds. The last funny thing is the qDebug output. It has an extra newline after it! Did you expect this output?

wpeeters@debian5:~/data/code/consoleapp/consoleapp$ ./consoleapp
2. Some normal iostream output before using qDebug
3. Some output with qDebug after the iostream output

1. Starting the application
wpeeters@debian5:~/data/code/consoleapp/consoleapp$

1.3 Explication and solutions

QTextStream is buffered, as long as the buffer is not flushed, it will not show anything on the screen. To flush the buffer, we have two possibilities.

  1. flush the buffer explicitly
  2. use endl instead of the backslash n at the end of the string

Using endl will flush the buffer inplicitly. In this case, it is the prefered way. You could have used qout.flush() to explicitly flush to buffer to. The qDebug has a kind of build in endl and does not need to have an extra one. I took it out! Now this is what the code looks like:

#include <QtCore/QCoreApplication>
#include <QTimer>
#include <QTextStream>
#include <QDebug>
#include <iostream>

//by Wim Peeters: a console application
int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv); //renamed the a to app
    QTextStream qout(stdout); //I connect the stout to my qout textstream

    //I took \n out and replace it with endl
    qout <<       "1. Starting the application" << endl; 
    std::cout <<  "2. Some normal iostream output before using qDebug\n";
    //built in endl!
    qDebug() << "3. Some output with qDebug after the iostream output"; 

    QTimer::singleShot(5000, &app, SLOT(quit())); //stop after 5 seconds

    return app.exec(); //and we run the application
}

The output you expected to get in the first place.

wpeeters@debian5:~/data/code/consoleapp/consoleapp$ ./consoleapp
1. Starting the application
2. Some normal iostream output before using qDebug
3. Some output with qDebug after the iostream output
wpeeters@debian5:~/data/code/consoleapp/consoleapp$

You will see the output at once, than the application waits till the 5 seconds are over and quits. So far so good. To write some nice console application we need to do something. Now we will add some sourcecode which can be filled to do something usefull. When the task is done, we will end the application. For demonstration purposes, we still leave the 5 seconds trigger at the end. We will need it! The first thing to do is to add a new class. This can be done by right clicking on the project and selecting add.

Image addnewclass
We give the class a name.
Image classname
We need to inherit from QObject and add the new class to the project!
Image addtoproject

Now I changed the generated class a bit so it can be used. The ctodo.h file has been changed to:

#ifndef CTODO_H
#define CTODO_H
#include <QObject>

//by Wim Peeters showing a console application
class CTodo : public QObject
{
    Q_OBJECT
public:
    CTodo(QObject *parent=0); //we need the *parent here
    ~CTodo(); //destructor
    void go(); //here we will do the work
signals:
    void allDone(int); //we add a new signal
public slots:
    void showDone(int); //just needed to explain something about signals
};
#endif // CTODO_H

I also changed the ctodo.cpp file so it can show something on the screen.

#include <QTextStream>
#include <QCoreApplication>
#include <QTimer>
#include <QDebug>
#include "ctodo.h"

CTodo::CTodo(QObject *parent)
        : QObject(parent)
{
    QTextStream qout(stdout);
    qout << "a. Constructor...\n";
    qout.flush(); //the other way to flush the buffer!    
}

CTodo::~CTodo()
{
    qDebug() << "d. Destructor..."; //no need for and endl!
}

void CTodo::go()
{
    qDebug() <<   "b. Working very hard...";
    //we want to know if the signal has been emitted
    QObject::connect(this, SIGNAL(allDone(int)),this,
             SLOT(showDone(int)));
    //we want to connect it to the quit of the application to
    QObject::connect(this,SIGNAL(allDone(int)),this->parent(),
             SLOT(quit()));
    emit allDone(5); //sending the signal to stop the app
}

void CTodo::showDone(int)
{
    qDebug() <<   "c. Yes, the signal has been sent";
}

//Do you miss the implementation of the signal?
//We defined the following the ctodo.h file:

//signals:
//    void allDone(int); //we add a new signal

//We should not define it, the framework will do it for us!
//You get the following error if you do define it:
//error: multiple definition of CTodo::allDone(int)

If you get error messages concerning "vtables not found" you need to run qmake. This can be done by selecting it from the menu or by involing a complete build from the menu. If you run the programm you will see that the program will not quit with your signal. Instead it will wait and end after 5 seconds. This again is not what I would expect. If you would not have this 5 seconds signal, it will run forever! The program will output is shown below.

wpeeters@debian5:~/data/code/consoleapp/consoleapp$ ./consoleapp
1. Starting the application
2. Some normal iostream output before using qDebug
3. Some output with qDebug after the iostream output
a. Constructor...
b. Working very hard...
c. Yes, the signal has been sent
d. Destructor...

1.4 More pitfalls you can run into

In the program I also introduced an own created signal. The code for the implementation will be generated by the framework! If you generate it you will get an errormessage stating a multiple definition of CTodo::allDone(int). I gave it an int parameter for demonstration purposes. The parameter is not used in the program. Another pitfall you might run into is the Q_OBJECT keyword in the headerfile. This is needed for signals and slots. That's why you will have to run qmake to solve the vtables error messages. I used the flush function in the constructor to demonstrate the usage of it. Try to leave it out and run the program again! The most interested problem is the signal not being sent to the app in the main function. That's why the program does not stop and need the 5 seconds timer to get stopped.

1.5 Signal explication

It is wrong to state that the signal is not sent to the application. That's why I wrote the showDone(int) function. The function is called, meaning the signal is sent. The problem we have here is the fact that the signal is sent to early. The messagloop of the app is not running yet. When the signal is sent, it will be seen except from our showDone(int) function. To solve this problem we have to tell the connection to queue the signal. This can be done with the Qt::QueuedConnection parameter. I just changed the line

QObject::connect(this,SIGNAL(allDone(int)),this->parent(),SLOT(quit()));

into

QObject::connect(this,SIGNAL(allDone(int)),this->parent(),SLOT(quit()),
         Qt::QueuedConnection);
If you run the application now, you will notice that the 5 seconds timer is not needed anymore. The program will stop before it can be triggered. If you fill this template application with life, do not forget to take the 5 seconds timer out just in case your application runs longer than 5 seconds. This timer (QTimer::singleShot()) was only needed for explaining the application so we did not have to kill it all the time during experimenting.

2 Conclusion

Qt is a wonderfull framework not just for creating GUI application on different platforms but also for writing powerfull console applications. I use Qt console applications for connection to Postgres databases and doing some interactive database work, as well as for my GUI applications on windows and linux. I hope you liked this introduction, based on this template you can start doing some work. Hope you liked it.



The Lubby Project
http://www.lubby.org
Converted by Wim Peeters
2009-11-19