Thursday, 1 July 2021

Use async c++/qt functions in exported python module

I have a dll that is relying extensively on Qt and that exports certain async functions returning QFuture, e.g.

QFuture<QString> loadUserAsync();

I want to export such functions (through pybind11) so that customers can write scripts in python. I don't want to leak the async Qt interface into python though, hence I am writing a wrapping API in C++, something like that:

class API_EXPORT API {
public:
   std::string loadUsername();
   //...
};

std::string API::loadUsername() {
   Future<QString> future = _core->loadUserAsync();
   return future.result().toStdString(); 
}

which then gets exported through pybind11:

py::class_<API>(m, "api")
  .def(py::init<>())
  .def("loadUsername", &API::loadUsername);

Well, this has multiple issues and I am struggling how to approach this correctly.

First, I most certainly need to instantiate a QCoreApplication so that signal/slot and events within the library are working correctly. This seems to work but I am really not sure if this is considered best practise and if I have to call the exec function (I cannot call exec on the calling thread, else it will block):

API::API() {
    if (!QCoreApplication::instance()) {
      int argc = 1;
      char* argv[] = {"api"};
      _qt = std::make_shared<QCoreApplication>(argc, argv);
    }
}

Second, future.result().toStdString(); deadlocks. I could "fix" this instantiating my own QEventLoop but I am not sure if this is the way to go:

QFutureWatcher<void> watcher;
QEventLoop loop;

watcher.connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()), Qt::QueuedConnection);
watcher.setFuture(future);

loop.exec();

Third, somewhere within the dll a QTimer is instantiated so that I am getting nasty warnings printed in python and I am puzzled what to do about it:

QObject::startTimer: Timers can only be used with threads started with QThread


from Use async c++/qt functions in exported python module

No comments:

Post a Comment