在非 Qt 应用程序中使用基于 Qt 的 DLL

Posted

技术标签:

【中文标题】在非 Qt 应用程序中使用基于 Qt 的 DLL【英文标题】:Using a Qt-based DLL in a non-Qt application 【发布时间】:2010-01-27 21:46:17 【问题描述】:

我做得对吗?

我的一个客户有一个小组,我正在开发基于 Qt 的客户端-服务器,其中包含许多有趣的小部件和套接字。

公司内的另一个小组想要使用基于 QTcpSocket 的客户端数据提供程序类的包装版本。 (这基本上就像它听起来的那样,将数据从服务器提供给客户端显示)

但是,该团队有一个主要使用 MFC 构建的庞大应用程序,而且这种情况不会很快改变。基于 Qt 的 DLL 也是延迟加载的,因此在某些配置中无需此功能即可部署它。

我已经让它工作了,但它有点 hacky。这是我目前的解决方案:

DLL 包装类构造函数调用 QCoreApplication::instance() 来查看它是否为 NULL。如果它为 NULL,则假定它在非 Qt 应用程序中,并创建它自己的 QCoreApplication 实例:

if (QCoreApplication::instance() == NULL)

    int argc = 1;
    char* argv[] =  "dummy.exe", NULL ;
    d->_app = new QCoreApplication(argc, argv);  // safe?

else
    d->_app = NULL;

然后它会设置一个windows定时器来偶尔调用processEvents():

if (eventTimerInterval > 0)

    // STATE: start a timer to occasionally process the Qt events in the event queue
    SetTimer(NULL, (UINT_PTR)this, eventTimerInterval, CDatabaseLayer_TimerCallback);

回调只是使用 timerID 作为指向类实例的指针调用 processEvents() 函数。 SetTimer() 文档说,当 HWND 为 NULL 时,它会忽略 timerID,因此这似乎是完全有效的。

VOID CALLBACK BLAHBLAH_TimerCallback(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)

    ((BLAHBLAH*)idEvent)->processEvents(); // basically just calls d->_app->processEvents();

然后我将 QCoreApplication 实例作为析构函数中的最后一件事销毁。

BLAHBLAH::~BLAHBLAH()

    .. other stuff

   QCoreApplication* app = d->_app;
   d->_app = NULL;
   delete d;
   if (app != NULL)
       delete app;

如果宿主应用程序希望自己对 processEvents() 的调用进行计时,它可以为 eventTimerInterval 传递 0 并自己调用 BLAHBLAH::processEvents()。

对此有什么想法吗?将该应用程序移植到 Qt 不是一种选择。这不是我们的。

它似乎有效,但这里可能有几个假设被打破。我可以用这样的虚拟参数构造一个 QCoreApplication 吗?以这种方式操作事件队列是否安全?

我不想以后这件事在我的脸上炸开。想法?

【问题讨论】:

我自己正在诉诸于此。 Qt 3 显然支持创建插件 dll - 例如用于基于 chrome safari 和 mozilla 的浏览器的 npapi 类型插件。但这似乎已从 Qt 4 中删除。 感谢您提出问题!你知道这是否是现在 4.8 中最好的方法,还是有更好的方法。实际上我运行一个单独的 QThread 并在那里创建和执行 QCoreApplication(全局 argc 和 argv)。 QThread本身不需要QCoreApplication,只有一些系统级的消息比如timer需要dispatcher。 【参考方案1】:

研究 Qt 代码似乎需要 QCoreApplication 来调度系统范围的消息,例如计时器事件。信号/插槽甚至 QThreads 之类的东西不依赖于它,除非它们与那些系统范围的消息相关。以下是我在共享库中执行此操作的方法(以使用 Qt 本身的跨平台方式),实际上我确实调用了 exec,因为单独的 processEvents() 并不能处理所有内容.

我有一个全局命名空间:

// Private Qt application
namespace QAppPriv

    static int argc = 1;
    static char * argv[] = "sharedlib.app", NULL;
    static QCoreApplication * pApp = NULL;
    static QThread * pThread = NULL;
;

我在 QObject 中有一个 OpenApp 方法(即 moc'ed),如下所示:

// Initialize the app
if (QAppPriv::pThread == NULL)

    // Separate thread for application thread
    QAppPriv::pThread = new QThread();
    // Direct connection is mandatory
    connect(QAppPriv::pThread, SIGNAL(started()), this, SLOT(OnExec()), Qt::DirectConnection);
    QAppPriv::pThread->start();

这里是 OnExec 槽:

if (QCoreApplication::instance() == NULL)

    QAppPriv::pApp = new QCoreApplication(QAppPriv::argc, QAppPriv::argv);
    QAppPriv::pApp->exec();
    if (QAppPriv::pApp)
        delete QAppPriv::pApp;

到目前为止,它似乎工作正常,我不确定最后是否需要删除该应用程序,如果我发现了什么,我会更新我的答案。

【讨论】:

@ExpatEgghead 实际上效果很好(github.com/dashesy/CereLink) 你需要使用Qt::BlockingQueuedConnection。这将阻塞,直到插槽在另一个线程中运行。 Qt::DirectConnection 适用于信号和槽存在于同一线程中的情况。我认为在这里使用 Qt::DirectConnection 会导致系统退回到 Qt::QueuedConnection,这可以解释为什么我的插槽被异步调用。 @It'sYourAppLLC 不,你错了。根据Qt的文档,信号started是从关联线程发出的,所以DirectConnection是正确的方法,因为OnExec函数需要在关联线程中运行。【参考方案2】:

4.5.2 的 Qt 文档说 QCoreApplication 的参数需要有与应用程序对象一样长的生命周期 - 所以你不应该真正使用局部变量。

除了那件小事:

我正在努力解决同样的问题,而且一切似乎也对我有用。但是,我建议在卸载/退出时要非常小心,因为如果您正在使用来自另一个应用程序的事件循环并且该事件循环在您的库被卸载之前停止,那么当您尝试关闭时可能会发生各种崩溃() 套接字并删除 QObjects。

【讨论】:

以上是关于在非 Qt 应用程序中使用基于 Qt 的 DLL的主要内容,如果未能解决你的问题,请参考以下文章

MFC 应用程序中的 Qt DLL - 如何使 QDialog *真正* 模态?

Qt/MFC 迁移框架工具:正确退出 DLL?

Qt Installer Framework - 防止安装在非空文件夹中

VS-Qt环境下dll

如何使用 .dll 注入覆盖 Qt 成员函数

Qt系列文章之三十八(基于QWidget 创建和使用动态dll共享库)