PDA

View Full Version : Calling Qt library from non-Qt C++ client



QPlace
20th February 2013, 17:17
I have a non-Qt client in C++ that I cannot convert to Qt and I also have Qt library that I need to call from this client (all pieces are in Windows). I added couple of exported functions to my library that in turn are calling the global instances of my classes. Everything works fine, I can successfully call them from the straight C++ client. With one caveat - signals seems not to work. My QObject::connect calls all return true, signals are emitted (I can see that in the debugger), but they do not reach the recepient.

My suspicion is that I am not initializing the global event handling properly.
I am using QCoreApplication as a global variable and initialize it in the thread. My pseudo code in Qt library looks like this:


int _app_i = 1;
char* _app_buf = "";
QCoreApplication _app (_app_i, &_app_buf);
...
extern "C"
{
bool start() // exported function
{
QThreadPool::globalInstance()->start(new appstarter() ); // see below the implementation of "run"
return true;
}
}

void appstarter::run()
{
_app.exec();
int i = 5; // I should not reach this line but I do!. According to docs the exec() call should only return after quit is called.
}


I know that my library is not unloaded in the C++ client, because I can call other exported functions in any time and they are accessing other global variables, so _app instance exists.


I will appreciate suggestions/comments to help me solve the problem. As a start - why _app.exec() returns immediately?

Thanks

wysota
20th February 2013, 17:29
Why are you using QThreadPool here?

QPlace
20th February 2013, 18:13
Because ”start” function is an exported one, i.e is called from the client. I expected app.exe() to enter the event loop.

wysota
20th February 2013, 18:21
It doesn't explain why you use QThreadPool and not QThread. You need to create a QThread instance, create the application instance, move it to the thread, start the thread and call exec from within the thread (e.g. by subclassing and reimplementing run()).

QPlace
20th February 2013, 19:28
You are correct, it does not explain the choice between QThreadPool and QThread. In my case I don't see any difference between them, since my goal was to start event loop for global QCoreApplication object. Anyway, I changed the code to use QThread and it produced the same result. Interestingly, if I call _app.exec() in start() directly, without the thread usage, it enters the event loop. In case of QThread or QThreadPool usage, it returns immediately.


bool start()
{
appstarter* ast = new appstarter();
QThread* wt = new QThread();
QObject::connect(wt, SIGNAL(started()), ast, SLOT(run()));
ast->moveToThread(wt);
wt->start();
// QThreadPool::globalInstance()->start(new appstarter() );
return true;
}

... and now I know why. It returns from QCoreApplication::exec() with this:
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}

wysota
20th February 2013, 19:30
In my case I don't see any difference between them
You will if you decide to use anything related to QtConcurrent or QThreadPool on a single core machine inside your Qt code :)


Anyway, I changed the code to use QThread and it produced the same result. Interestingly, if I call _app.exec() in start() directly, without the thread usage, it enters the event loop. In case of QThread or QThreadPool usage, it returns immediately.


bool start()
{
appstarter* ast = new appstarter();
QThread* wt = new QThread();
QObject::connect(wt, SIGNAL(started()), ast, SLOT(run()));
ast->moveToThread(wt);
wt->start();
// QThreadPool::globalInstance()->start(new appstarter() );
return true;
}


class ThreadedApp : public QThread {
public:
ThreadedApp() : QThread() { }
void run() {
char *argv[] = { "myapp" };
QApplication app(1, ; argv);
app.exec(); // or just exec(), try both
}
};
//...
ThreadedApp app;
app.start();


Edit: remember that all the GUI objects you create need to be pushed to this thread.

QPlace
20th February 2013, 22:34
No matter what I do I cannot make the signals to fire. QCoreApplication instance is created and event loop enters successfully. I will try to create a test project and post it here shortly.

Hopefully wysota or someone else can spot a problem.

Thanks

wysota
20th February 2013, 22:56
Signals are firing. Slots might not be called because of a missing event loop in the thread that owns receivers of those signals.

Here is something that works for me. I'm using a C++11 compiler for thread support.


#include <QtGui>
#include <thread>

void thrmain() {
int argc = 1;
char *argv[] = { "testapp" };
QApplication app(argc, argv);
QPushButton pb("Click");
QLineEdit le;
QObject::connect(&pb, SIGNAL(clicked()), &le, SLOT(clear()));
pb.show();
le.show();
app.exec();
}

int main() {
std::thread thr(thrmain);
thr.join();
return 0;
}

I think the important thing is to not use Qt threads or else Qt will complain about the app being created not in the main thread.

QPlace
21st February 2013, 01:04
You are correct (as usual :) ). I misspoke, I can see signals being emitted, it is the slots that are not being called. I will check your solution before I continue with test project.

wysota
21st February 2013, 01:08
One thing that came to my mind... If you wish to use cross-thread signals-slots involving the UI, make sure you explicitly make connections Qt::DirectConnection if the signal is fired by the UI thread and the receiver lives in a thread not controlled by Qt. Otherwise slots will not work properly. Just make sure you do proper synchronization.

QPlace
21st February 2013, 01:42
I am not using any GUI stuff, so my app is QCoreApplication and not QApplication. Since I moved app.exe under QThread (per your advise) it correctly enters the event loop, so QThread seems not to be the problem. However, I still cannot get my slots being called, regardless of where I instantiate the receiver (in the main thread or in the spawned thread). :(

I also tried to use Qt::DirectConnection. No luck.

(In my current version, there are two threads. The child thread is spawned by the main thread where app.exec() is being called. Main thread calls app.exec in it' run method. The child thread instantiate the sender of signals in constructor, it is also the receiver. Child thread connects sender and itself with "connect" and calls "exec()" in its run method.)

wysota
21st February 2013, 02:00
I am not using any GUI stuff, so my app is QCoreApplication and not QApplication.
Whatever... :) The same applies.


Since I moved app.exe under QThread (per your advise) it correctly enters the event loop, so QThread seems not to be the problem.
My test was entering the event loop as well (I think) but I was getting a warning on a console about the application being created in a wrong thread if I used QThread.


(In my current version, there are two threads. The child thread is spawned by the main thread where app.exec() is being called. Main thread calls app.exec in it' run method. The child thread instantiate the sender of signals in constructor, it is also the receiver. Child thread connects sender and itself with "connect" and calls "exec()" in its run method.)

I don't get it. So how many threads are there in the application? Two or three? Is the application accepting signals from its own thread (check that by not creating this worker thread and create a timer object and connect its timeout signal to something that lives in the same thread as the application object)?

QPlace
21st February 2013, 02:15
The "application" is a qt library that normally is loaded as a plugin. I had to call this library from the non-Qt client. This library implements a socket connection to a server. Upon receiving a message it emits a signal.
Normal structure of the qtlibrary is:

class socketImpl
{
...
signals:
void onMessage();
};

and


class messageHandler
{
public:
messsageHandler()
{
connect (&_socket, SIGNAL(onMessage()), this, SIGNAL (onMessage()));
}
signals:
void onMessage();
private:
socketImpl _socket;
}

messageHandler is also derived from the interface, but this is unimportant.

When this plugin is loaded in the Qt-based client everything works as expected, slots are being called etc.

Now, I need to use this library in non-Qt C++ client. For this I created two exported functions, init() and start()
In a separate C++ file that is a part of a library I created a QThread-based (as you advised) global variable (say globalApp) that instantiates QCoreApplication and invokes .exec() on it.
The first function, init(), starts this thread by calling globalApp.start. Next function, init() tries all sorts of things to instantiate messageHandler and connect it's signal to the slots of some class, either globalApp or another one, like a separate thread spawned from globalApp.
This thread is totally optional, I am just trying to instantiate messageHandler class. I tried to instantiate it in the globalApp, then tried to create another thread that is spawned by globalApp to instantiate messageHandler there, but in all cases my slots that I created in globalApp or in the auxillary thread and connected them to messageHandler signals are not called.

wysota
21st February 2013, 02:25
So where exactly is the application object instantiated? Where is the socket created? Is this a QAbstractSocket subclass? How do you communicate with the socket?

Let me stress this again --- all objects that are to use signals and slots need to be created in threads that are going to have an active event loop, which basically means you have to create all those objects (the application object, sockets, etc.) in the thread that is going to call QCoreApplication::exec().

QPlace
21st February 2013, 02:55
I believe that it is exactly what I am doing.


#include <sstream>

// header
class servicemanager : public QThread
{
Q_OBJECT
public:
servicemanager();
~servicemanager();
void run() { exec(); }
bool init(const char* ip, int port, const char* account );

void setAccount(const char* acct);
const char* positions();
public slots:
void onAccountInfo(QString acct, QString what, int why);
void onConnectStatus(bool stat);
private:
ITservice _service;
QString _account;
};

class ThreadedApp : public QThread {
Q_OBJECT
public:
ThreadedApp() : QThread() { }
void run();
bool init(const char* ip, int port, const char* account );
private:
servicemanager _manager;
};


// implementation
servicemanager::servicemanager() : QThread(){}

servicemanager::~servicemanager(){}

bool servicemanager::init(const char* ip, int port, const char* account )
{
bool res = QObject::connect(&_service, SIGNAL (onConnect(bool)), this, SLOT(onConnectStatus(bool)));
res = QObject::connect(&_service, SIGNAL(onAccountInfo(QString, QString, int)), this, SLOT(onAccountInfo(QString, QString, int)));
bool val = _service.connect(true);
return val;
}


void servicemanager::onAccountInfo(QString acct, QString what, int why)
{
}

void servicemanager::onConnectStatus(bool stat)
{
}

void ThreadedApp::run() {
int i = 1;
char *argv[] = { "myapp" };
QCoreApplication app(i, argv);
app.exec();
}

bool ThreadedApp::init(const char* ip, int port, const char* account )
{
_manager.start();
aia::support::waitHere(1000); // wait one sec
return _manager.init(ip, port, account);
}

ThreadedApp app;

extern "C"
{
bool start()
{
app.start();
return true;
}

bool init(const char* ip, int port, const char* account )
{
return app.init(ip,port,account);
}
};

wysota
21st February 2013, 03:02
Let's see...

ThreadedApp is a global object, created when the library is loaded so it's the thread that is running the main application.

This object contains the "servicemanager" instance so it is also created in the main application thread.

This object in turn contains the ITservice instance you try to connect to, so it also lives in the main application thread.

Your QCoreApplication instance is created in ThreadedApp::run() so it lives in "ThreadedApp" thread.

So in the end your application object lives in a different thread than all the other objects. Since they do not live in a thread that runs an event loop, they won't be calling any slots as a result to any cross-thread signal they may achieve. Assuming one of those objects contains a QAbstractSocket, this socket will not receive any data either (since it relies on a running event loop as well).

To sum it up -- your application instance is totally useless as it is the only object living in a thread containing a running event loop.

QPlace
21st February 2013, 03:15
Thank you. Your help is invaluable. I made a slight( :) )change and it works now.

wysota
21st February 2013, 03:19
Out of curiosity... does the main application have some kind of event loop?

QPlace
21st February 2013, 03:25
Yes, the C++ client is a non Qt C++ app, in my test case I am using autogenerated atl project with mainwindow, in my real case I am going to use excel c++ addin.

wysota
21st February 2013, 03:30
Can't you just provide means to plugins from within the main application to process their events periodically? There you could just call QCoreApplication::processEvents() without any need for additional threads and similar problems.

QPlace
21st February 2013, 03:40
I will have to periodically call my exported function from either excel or non-Qt-gui app to get the data I needed (most likely using some timers), but I don't understand your suggestion about processEvents. Can you illustrate it with some pseudo-code?

wysota
21st February 2013, 10:32
It can be used instead of an event loop. So you can treat this:


app.exec();

and this:


while(1) app.processEvents();

as more or less equivalents. With the latter you would simply integrate Qt's event loop with the one from the calling app. This has to be initiated from the calling side though (some heartbeat or whatever).