Я искал ответ по всей сети, но не нашел решения для своей проблемы. Или, может быть, я сделал, но, поскольку я новичок в C ++ / программирование / Qt, я не понял их.
Самым близким был вопрос здесь Использование библиотеки на основе Qt в не-Qt-приложении. Я пытался использовать этот метод, но пока безуспешно.
Я пытаюсь создать DLL, это API для нашего USB-устройства. Библиотека должна работать и на не-Qt приложениях. У меня есть PIMPL-ed для всех вещей Qt и частных классов, поэтому приведенный ниже код является одним слоем под открытыми классами. Я использую QSerialPort и много SIGNAL / SLOT, поэтому мне нужен цикл событий QCoreApplications. ReaderSerial — это то, с чего начинается материал Qt, он также создает другой класс, где QSerialPort работает в другом QThread.
В данный момент моя проблема в том, что все происходит сбой при ошибке: «QTimer можно использовать только с потоками, запущенными с QThread»
Я предполагаю, что мои классы на основе Qt, такие как ReaderSerial, не «видят» цикл событий QCoreApp или что-то в этом роде. Поэтому мой вопрос заключается в том, как обеспечить цикл событий QCoreApplication для моей DLL, чтобы все созданные на основе Qt классы работали, и я мог вызывать методы из моей DLL.
Большое спасибо за ответы.
reader_p.h
class ReaderPrivate
{
public:
ReaderPrivate();
~ReaderPrivate();
void static qCoreAppExec();
ReaderSerial *readerSerial;
void connectReader(std::string comPort);
void disconnectReader();
};
reader.cpp
// Private Qt application
namespace QAppPriv
{
static int argc = 1;
static char * argv[] = {"API.app", NULL};
static QCoreApplication * pApp = NULL;
};
ReaderPrivate::ReaderPrivate()
{
std::thread qCoreAppThread(qCoreAppExec);
qCoreAppThread.detach();
readerSerial = new ReaderSerial;
}
ReaderPrivate::~ReaderPrivate()
{
delete readerSerial;
}
void ReaderPrivate::qCoreAppExec()
{
if (QCoreApplication::instance() == NULL)
{
QAppPriv::pApp = new QCoreApplication(QAppPriv::argc, QAppPriv::argv);
QAppPriv::pApp->exec();
if (QAppPriv::pApp)
delete QAppPriv::pApp;
}
}
void ReaderPrivate::connectReader(std::string comPort)
{
readerSerial->openDevice(comPort);
}
void ReaderPrivate::disconnectReader()
{
readerSerial->closeDevice();
}
На основе ответа @Kuba Ober я создал общую библиотеку. Мне потребовалось некоторое время, чтобы понять, что происходит и как заставить это работать, но это все еще не делает то, что должно. Поэтому сейчас я прошу совета, как заставить этот код работать.
apic.h
#include "Windows.h"
extern "C"{
__declspec(dllexport) void WINAPI kyleHello();
}
apic.cpp
#include "apic.h"#include "appthread.h"
void WINAPI kyleHello()
{
worker->hello();
}
BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID)
{
static AppThread *thread;
switch (reason)
{
case DLL_PROCESS_ATTACH:
thread = new AppThread;
thread->start();
break;
case DLL_PROCESS_DETACH:
delete thread;
break;
default:
break;
}
return TRUE;
};
appthread.h
#include <QThread>
#include <QCoreApplication>
#include <QPointer>
#include "worker.h"
static QPointer<Worker> worker;
class AppThread : public QThread
{
public:
AppThread();
~AppThread();
// No need for the Q_OBJECT
QPointer<QCoreApplication> m_app;
void run() Q_DECL_OVERRIDE
{
std::cout << "\n AppThread::run";
int argc;
char *argv;
QCoreApplication app(argc, &argv);
m_app = &app;
std::cout << "\nAppThread::run before Worker";
Worker worker_;
worker = &worker_;
std::cout << "\nAppThread::run before app.exec";
app.exec();
}
//using QThread::wait(); // This wouldn't work here.
};
appthread.cpp
#include "appthread.h"
AppThread::AppThread()
{
std::cout << "\n AppThread::ctor";
}
AppThread::~AppThread()
{
std::cout << "\n AppThread::dtor \n";
m_app->quit();
wait();
}
worker.h
#include <QObject>
#include <QDebug>
#include <iostream>
class Worker : public QObject
{
Q_OBJECT
Q_INVOKABLE void helloImpl()
{
std::cout << "I'm alive.";
//qDebug() << "I'm alive.";
}
public:
Worker();
void hello();
};
worker.cpp
#include "worker.h"
Worker::Worker()
{
std::cout << "\nWorker::ctor";
hello();
}
void Worker::hello()
{
std::cout << "\nWorker::hello()";
// This is thread-safe, the method is invoked from the event loop
QMetaObject::invokeMethod(this, "helloImpl", Qt::QueuedConnection);
}
Выход из этого обычно:
AppThread::ctor
Worker::hello()
AppThread::dtor
иногда:
AppThread::ctor
Worker::hello()
AppThread::run
AppThread::dtor
иногда:
AppThread::ctor
Worker::hello()
AppThread::dtor
QMutex: destroying locked mutex
GitHub репо: https://github.com/KyleHectic/apic.git
Прежде всего, если вам нужно QCoreApplication
всегда будет ваш QCoreApplication
, Вам следует не попробуйте любой вид динамического связывания Qt в вашей DLL, на случай, если он в итоге получит Qt из приложения, которое является вашим потребителем. Нет никаких гарантий двоичной совместимости между этими библиотеками Qt — это заставит вашего потребителя использовать точно такую же версию компилятора и двоичную совместимую сборку Qt. Это, вообще говоря, фантастика.
Итак, идея, которую вам нужно проверить на QCoreApplication
Присутствие просто не подходит вашей модели использования. Вам это всегда будет нужно. Все, что вам нужно сделать, это запустить поток и запустить там основное приложение. Вот и все.
QPointer<Worker> worker;
extern "C" {
__declspec(DLLEXPORT) WINAPI VOID kyleHello() {
worker->hello();
}
}
class Worker() : public Q_OBJECT {
Q_OBJECT
Q_INVOKABLE void helloImpl() { qDebug() << "I'm alive."; }
public:
void hello() {
// This is thread-safe, the method is invoked from the event loop
QMetaObject::invokeMethod(this, "helloImpl", Qt::QueuedConnection);
}
Worker() { hello(); }
};
class AppThread : public QThread {
// No need for the Q_OBJECT
QPointer<QCoreApplication> m_app;
void run() Q_DECL_OVERRIDE {
int argc;
char * argv;
QCoreApplication app(argc, &argv);
m_app = &app;
Worker worker_;
worker = &worker_;
app.exec();
}
using QThread::wait(); // This wouldn't work here.
public:
AppThread() {}
~AppThread() { m_app->quit(); wait(); }
}
BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID) {
static AppThread * thread;
switch (reason) {
case DLL_PROCESS_ATTACH:
thread = new AppThread;
thread->start();
break;
case DLL_PROCESS_DETACH:
delete thread;
break;
default:
break;
}
return TRUE;
}
API, предоставляемый вашему потребителю, бывает нескольких видов:
API только для записи, которые не ждут результата. Внутренне вы просто публикуете событие в любом из ваших QObject
s. Вы также можете использовать QMetaObject::invokeMethod
с Qt::QueuedConnection
— это заканчивается просто отправка QMetaCallEvent
к целевому объекту. События могут быть опубликованы на любой QObject
из любого потока, включая не QThread-начатые потоки.
Обратные вызовы сторонних потоков: выделите отдельный поток, в котором выполняются предоставленные потребителем обратные вызовы. Они будут вызваны одним или несколькими QObject
живет в этой теме.
Обратные вызовы клиентских потоков: используйте платформу вызовы асинхронных процедур которые выполняют обратный вызов в контексте любого потока — обычно потока, из которого была вызвана функция регистрации обратного вызова. Эти обратные вызовы выполняются, когда поток находится в состоянии оповещения.
Если вы хотите ограничиться подмножеством состояний оповещения, в которых работает насос сообщений (GetMessage
называется), вы можете создать только для сообщений, невидимое окно, отправлять на него сообщения и выдавать обратные вызовы потребителя из функции обратного вызова окна. Если вы умны об этом, вы можете передать QEvent
указатели через эти сообщения и передать их QObject::event
в обратном вызове. Вот как вы можете сделать QObject
эффективно жить в потоке с собственным циклом событий и без запуска цикла событий Qt.
Блокирующие API, которые эффективно синхронизируют вызывающий поток с вашим потоком: используйте QMetaObject::invokeMethod
с Qt::BlockingQueuedConnection
, Вызывающая сторона будет ждать, пока слот завершит выполнение в принимающем потоке, необязательно передавая результат обратно.
API блокировки, которые используют детальную блокировку. Они также синхронизируют поток вызывающего с вашим потоком, но только на уровне блокировки определенных структур данных. Они полезны в основном для чтения параметров или извлечения данных — когда накладные расходы на прохождение цикла обработки событий затмевают небольшой объем выполняемой вами работы.
Какие API вы предлагаете, зависит от критериев разработки вашего API.
Все API должны быть extern C
и не должен использовать C ++. Вы можете предлагать API C ++ только в том случае, если вы планируете создавать DLL с использованием нескольких версий VS (скажем, 2008, 2010, 2012, 2013) — даже тогда вы не должны предоставлять Qt потребителю, так как потребитель все еще может использовать двоичную несовместимую версию.
QtWinMigrate решает проблему с циклом событий Qt в Win32 или MFC. Один из ответы к вопрос вы ссылку упоминает об этом.
Для библиотеки DLL Qt, которой необходимо связать цикл обработки событий в DllMain, просто используйте QMfcApp::pluginInstance