0

I've a Qt application, that must be able to load plugins. When the plugin is loaded (I use boost::dll) I call the plugin factory method for retrieving a class with the followed (simplified) interface:

#ifdef _WIN32
using WindowHandle = HWND;
#else
using WindowHandle = void*;
#endif

class PluginInterface {
public:

  /**
   * @brief Retrieve the window handle of the plugin.
   *
   * The plugin should have a single main window, that's the main window of the
   * plugin itself. The method retrieve this window handler and pass it to the
   * caller, in order to allow it to attach this window to its main gui
   * interface. The pointer ownership is on the plugin, so the caller mustn't
   * delete it for any reason.
   *
   * @return Window handle.
   */
  virtual WindowHandle getWindowHandle() const = 0;
};

The interface allows to retrieve a HWND pointer to a window, that then I attach on the application. The basic idea is to create with my application a container where other libraries can be loaded and run.

In a plugin I realize this interface by creating another QApplication. I create a forward declaration so when I load the plugin the application does not know anything about the fact that the plugin is using Qt, creating a opaque pointer:

class Application;

class MyPluginInterface final : public PluginInterface {
public:

  MyPluginInterface();
  virtual ~MyPluginInterface();

public:
  WindowHandle getWindowHandle() const override;

private:

  Application* m_application;
};


////// CPP file

#include "Application.hpp"

MyPluginInterface::MyPluginInterface() :
  PluginInterface() {
  m_application = new Application();
}

MyPluginInterface ::~MyPluginInterface () {

}

WindowHandle MyPluginInterface ::getWindowHandle() const {
  return m_application->getWindowHandle();
}

Application creates a QApplication and a QMainWindow, then I run the QApplication::exec() in another thread. I then create a method for retrieving the HWND handle of the QMainWindow, that should be passed through the plugin interface to the container application, that then will show it.

#ifndef APPLICATION_HPP_
#define APPLICATION_HPP_

#include "WindowHandle.hpp"
#include <QApplication>
#include <QMainWindow>
#include <memory>
#include <thread>

class Application final {
public:

  Application();
  ~Application();
  WindowHandle getWindowHandle() const;

private:

  void createMainWindow();

private:

  std::unique_ptr<QApplication> m_application;
  std::thread m_applicationThread;
  QMainWindow* m_mainWindow;
};

#endif // !APPLICATION_HPP_

/////////////// CPP FILE

#include "Application.hpp"
#include <qpa/qplatformnativeinterface.h>
#include <QLabel>


///////////////////////////////////////////////////////////////////////////////
// USING SECTION                                                             //
///////////////////////////////////////////////////////////////////////////////

using WindowHandle;
using std::thread;

///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION                                                            //
///////////////////////////////////////////////////////////////////////////////

Application::Application() {
  int argc = 1;
  char* argv[] = { "plugin" };
  QCoreApplication::addLibraryPath("./");
  m_application = std::make_unique<QApplication>(argc, argv);
  m_applicationThread = thread([this]() {
    createMainWindow();
    m_application->exec();
  });
}

Application::~Application() {
  if (m_applicationThread.joinable()) {
    m_applicationThread.join();
  }
}

WindowHandle Application::getWindowHandle() const {
  WindowHandle handle{ nullptr };
  if (QWindow* w = m_mainWindow->windowHandle()) {
    auto nativeInterface = QGuiApplication::platformNativeInterface();
    handle = static_cast<HWND>(nativeInterface->nativeResourceForWindow(QByteArrayLiteral("handle"), w));
  }
  return handle;
}

///////////////////////////////////////////////////////////////////////////////
// PRIVATE SECTION                                                           //
///////////////////////////////////////////////////////////////////////////////

void Application::createMainWindow() {
  m_mainWindow = new QMainWindow();
  QLabel* label = new QLabel(m_mainWindow);
  label->setText("Questo e' un plugin");
  m_mainWindow->setCentralWidget(label);
  m_mainWindow->show();
}

When I run the application, the container loads the plugin, but then I've an assertion in Application.cpp, at following line:

m_application = std::make_unique<QApplication>(argc, argv);

The assertion says

ASSERT failure in QCoreApplication: "there should be only one application object", file H:\...\corelib\kernel\qcoreapplication.cpp, line 792

Since the container application does now know anything about the content of the plugin (the interface does not expose Qt classes and logic) I was expecting to be able to run both QApplication.

What causes the problem and how can I solve it?

Jepessen
  • 11,744
  • 14
  • 82
  • 149
  • 1
    The various `Q*Application` classes are classic examples of singletons -- strictly one per process. From the [documentation](https://doc.qt.io/qt-5/qapplication.html#details): `"For any GUI application using Qt, there is precisely one QApplication object, no matter whether the application has 0, 1, 2 or more windows at any given time"`. – G.M. Mar 16 '20 at 19:25
  • You can simply have public method `bool isQt();` in your interface ). But anyway seems you forced to use `QApplication` in both the main applications and in Qt-based plugins. See also [QCoreApplication::instance()](https://doc.qt.io/qt-5/qcoreapplication.html#instance), may be it will be possible to use static instance pointer inside a plugin. Also note that widget creation is not possible in non-main threads. You can also forward declare `class QWidget` just like the `class Application;` – Vladimir Bershov Mar 16 '20 at 21:54
  • You can also separate plugin interface from a Qt-based application itself via [Qt Remote Objects](https://doc.qt.io/qt-5/qtremoteobjects-index.html). – Vladimir Bershov Mar 16 '20 at 22:03
  • I don't know if I can, because the same plugin must be loaded also by a C# container application. – Jepessen Mar 17 '20 at 07:51
  • You want to use a full application as a plugin. But Qt plugin system is not designed for that. Therefore you need some intermediate interface between your Qt-based container application and Qt-based plugins. This intermediate interface will be hidden inside Qt-based container. Qt Remote Objects could provide this intermediate interface. – Vladimir Bershov Mar 17 '20 at 14:38
  • It's not a Qt plugin system. The plugin must be able to be loaded by any C++ application and any C# application, once that the plugin interface is set. At the moment I'm using a Qt application for testing the loading, but I'm load the plugin with boost::dll library. The idea is that the plugin interface must not be tied to any gui library. – Jepessen Mar 18 '20 at 08:11

0 Answers0