[gelöst] QtConcurrent und QAxObject

Alles rund um die Programmierung mit Qt
Antworten
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

[gelöst] QtConcurrent und QAxObject

Beitrag von bobcat »

Hallo,

ich habe eine Klasse, die ein QAxObject enthält, mit dem ein Motor angesteuert wird. Während der Motor sich bewegt, blockiert die Funktion den GUI Thread. Ich möchte den Aufruf daher in einen eigenen Thread auslagern und habe es mit QtConcurrent probiert. Allerdings wird dann die move() Funktion des AxControls nicht mehr gefunden. Konkret sieht das bei mir so aus:

Code: Alles auswählen

class MyMotor
{
    ...
    MyMotorController* controller; // Klasse, die das Ax Objekt wrappt.
    ...
}

int MyMotor::moveMotor(double angle)
{
    ...
    QFuture<int> f1 = QtConcurrent::run(controller, &MyMotorController::move, angle);
    f1.waitForFinished();
}

class MyMotorController
{
    ...
    QAxObject* axMotor;
    ...
}

MyMotorController::move(double angle)
{
    ...
    axMotor->dynamicCall("move(double)", angle);
}
Führe ich den Code aus, bekomme ich folgende Fehlermeldung und der Motor dreht sich nicht:
QAxBase: Error calling IDispatch member move: Member not found
Mache ich den Aufruf, ohne in MyMotor::moveMotor QtConcurrent zu benutzen, dann klappt's. Im Thread scheint also das QAxObject leer zu sein ... hat jemand eine gute Idee, wo mein Fehler liegt?
Zuletzt geändert von bobcat am 15. September 2015 20:21, insgesamt 1-mal geändert.
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Re: QtConcurrent und QAxObject

Beitrag von Christian81 »

Mal mit QThread probiert?
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

Ja, habe ich versucht, aber trotz Doku und Beispielen lesen habe ich leider immer noch keinen lauffähigen Code. Allerdings ein paar Verständnisfragen ... hier nochmal die Situation (jetzt ohne QtConcurrent):

Ich habe eine von QThread abgeleitete Klasse, die eine Membervariable vom Typ QAxObject besitzt. Die Klasse hat die Aufgabe, den ActiveX Control zu kapseln und seine Methoden aufzurufen. Diese Aufrufe sollen in einem eigenen Thread passieren.

Code: Alles auswählen

class MyMotor : public QObject
{
    ...
public:
    int moveMotor(double angle);
    ...
private:
    MyMotorController controller;
}

class MyMotorController : public QThread
{
    Q_OBJECT
    ...

public:
    void run();
    void move(double angle);
    void axCall01();
    void axCall02();
    ...

private:
   QAxObject myAxObject;
}
Was mir nicht klar ist, ist, wie ich aus MyMotor::moveMotor(...) die Methode MyMotorController::move(...) aufrufe, so dass diese in einem eigenen Thread ausgeführt wird. Wenn ich zuerst controller.start() aufrufen muss (das dann QThread::run() aufruft), was muss ich in run() machen, damit der Thread "losläuft"? Oder kann ich die Methoden move(...), axCall01(), axCall02(), ... nur dann in einem eigenen Thread aufrufen, indem ich sie aus run() aufrufe und dort eine Abfrage einbaue, welche Funktion ich grerade benutzen möchte?
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Re: QtConcurrent und QAxObject

Beitrag von Christian81 »

Was man in QThread::run machen muss steht in der Doku:
The starting point for the thread. After calling start(), the newly created thread calls this function. The default implementation simply calls exec().

You can reimplement this function to facilitate advanced thread management. Returning from this method will end the execution of the thread.
Da Du allerdings das QAxObject - Objekt als Member von MyMotorController angelegt hast, ist auch der Thread-Kontext für dieses Objekt im Haupt-Thread.
Ein gutes Beispiel ist in der Doku zu finden: http://doc.qt.io/qt-4.8/qthread.html#details
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

Danke für den Hinweis zum Beispiel! Das hatte ich bisher übersehen.

Bevor ich versuche, die Hinweise umzusetzen: Der Thread-Kontext meines QAxObject - Objektes ist vermutlich der Grund, weshalb ich anfangs auch mit QtConcurrent nicht weiter gekommen bin. Sinnvoll scheint mir, das QAxObject in meinem Worker-Thread zu haben, und eben nicht im Haupt-Thread. Was wäre denn eine gute Strategie, es dauerhaft in einem Worker-Thread zu haben? Bei jedem Aufruf neu erstellen, ist in meiner Anwendung nicht möglich. Reicht es schon, meinem Worker-Thread einen Pointer auf mein QAxObject, das im Haupt-Thread erstellt wurde, zu übergeben oder sollte das QAxObject besser direkt im Worker-Thread existieren? Könnte ich dazu in meinem Worker-Thread einen Pointer als Member haben und dann in run() mit new QAxObject() das Objekt instanziieren?
RHBaum
Beiträge: 1436
Registriert: 17. Juni 2005 09:58

Re: QtConcurrent und QAxObject

Beitrag von RHBaum »

Sinnvoll scheint mir, das QAxObject in meinem Worker-Thread zu haben, und eben nicht im Haupt-Thread.
COM Programmierung ... lang ist es her ^^ meine Kentnisse sind auch etwas eingerostet.

Für jeden Thread der in ne com funktion aufgerufen wird, must COM initialisieren (Single Thread Apartment STA) oder du verwendest global MTA (Multithreaded Appartment).
Ob dein gekapseltes COM Object MTA kann, keine Ahnung, gehen wir mal von nein aus ... also STA

D.h. CoInitialize(EX) erzeugt nen Appartment für deinen Thread.
Alle deine "calls" muessen dann von dem Thread kommen ...

Ich hab keine Ahnung eine was QAxObject Kapselt und was das intern macht ^^ Ob dir das intern threads mappt ...
Aber ich denk es ist ne gute Idee das komplettes COM Object in deinem Worker Thread laufen zu lassen wenn du das wirklich assynchron laufen lassen willst ....
Die Frage ist ob das wirklich notwendig ist ? Hat der Lange oder blockierende Aufrufe ?
Also sowohl die erzeugung als auch der DTor an den Thread ketten.
und alle Aufrufe von aussen über SIgnals/Slot, bzw andere Mechaniken (MsgQueue ...) in den Thread mappen.
Also aufpassen dass deine connections von anderen Threads alle queued sind ...

Ciao ...
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

Ich habe die bisherigen Tipps und Hinweise ausprobiert, also die beiden Vorschläge aus der Doku von QThread (Link von Christian):
  • Meine Klasse mit QObject::moveToThread(...) in einen Worker - Thread schieben
  • Worker - Thread, der in seiner run() Methode meinen QAxObject - Aufruf ausführt
Desweiteren
  • QAxObject in der Worker Klasse aggregieren
  • Pointer auf QAxObject in der Worker Klasse aggregieren und im Konstruktor den Zeiger auf das Objekt übergeben
  • QAxObject in run() neu instanziieren
Der letzte Punkt schien mir am erfolgversprechendsten:

Code: Alles auswählen

void MyMotorController::run()
{
    _axMotor = new QAxObject();
    bool ok = _axMotor->setControl(QString::fromUtf8("{... axID ...}"));
    exec();
}
Allerdings bekomme ich da folgenden Fehler:
CoCreateInstance failure (CoInitialize wurde nicht aufgerufen.)
QAxBase::setControl: requested control {...} could not be instantiated
Mir ist nicht klar, wie ich CoInitialize aufrufen soll, ich dachte, mit setControl(...) wird das QAxObject initialisiert. Jedenfalls passiert das ja in meinem Hauptthread.
Und leider ja, ich brauche den Motor in einem anderen Thread ... während sich das Ding bewegt, muss ich den Verlauf auf der GUI darstellen und der Aufruf ist blockierend.
softwaremaker
Beiträge: 149
Registriert: 1. April 2009 19:25

Re: QtConcurrent und QAxObject

Beitrag von softwaremaker »

CoInitialize(0);
oder
CoInitializeEx(NULL, COINIT_MULTITHREADED);

http://stackoverflow.com/questions/1592 ... wo-threads
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

Okay, wieder ein Stück weiter ... danke für die Aufrufsyntax. Den Befehl habe ich in der windows.h gefunden. So wie's aussieht, wird der AxControl jetzt im Thread initialisiert. Dafür kommt jetzt folgender Fehler:
QAxBase::dynamicCallHelper: Object does not support automation
Ich registriere den AxControl im neuen Thread mit seiner UUID:

Code: Alles auswählen

void MotorController::run()
{
    _motor = new QAxObject();
    ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    bool ok = _motor->setControl(QString::fromUtf8("{E63B11AE-0BF1-4B40-96A3-4D7501A50E11}"));
    exec();
}
stackoverflow und msdn haben Artikel, nach denen die UUID hier das Problem sein könnte. Allerdings verstehe ich nicht, was ich ändern könnte. Hat der AxControl im neuen Thread eine andere UUID? Wie finde ich die?
softwaremaker
Beiträge: 149
Registriert: 1. April 2009 19:25

Re: QtConcurrent und QAxObject

Beitrag von softwaremaker »

setControl funktioniert auf jeden Fall mit der ProgID (Text), anscheinend hast du auch die falsche UUID genommen, denn das COM-Objekt hat anscheinend keine IDispatch-Schnittstelle (ist die Fehlermeldung). Wenns ohne Thread schon ging, liegts an der UUID.
::CoInitializeEx mach mal vor new QAxObject();
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

An der UUID liegt's nicht, die verwende ich auch in der Version ohne Threads und in regedit kann ich sehen, dass sie zu meinem AxControl gehört. Es spielt auch keine Rolle, ob ich das ::CoInitializeEx vor oder nach new QAxObject aufrufe.

Der Grund, warum der Code in meinem letzten Versuch nicht lief ist, dass ich in

Code: Alles auswählen

MotorController::run()
zwar das Ax Objekt im Thread erzeuge, sobald ich aber danach ein

Code: Alles auswählen

 MotorController::doWork()
aufrufe, lande ich wieder im Haupt - Thread. Das sieht man mit

Code: Alles auswählen

qDebug() << QThread::currentThreadId();
Ich hab jetzt allerdings eine Lösung gefunden, die poste ich gleich in einem gesonderten Beitrag.
bobcat
Beiträge: 125
Registriert: 21. April 2010 14:51

Re: QtConcurrent und QAxObject

Beitrag von bobcat »

Ich habe den AxControl jetzt in einem gesonderten Thread am Laufen, indem ich den ersten Ansatz aus der Doku

http://doc.qt.io/qt-4.8/qthread.html#details

gewählt habe. Das hatte ich zwar schon zuvor versucht, aber dass es nicht lief, lag vermutlich am fehlenden CoInitializeEx.

Ich habe also eine von QObjekt abgeleitete Worker Klasse:

Code: Alles auswählen

class MotorWorker : public QObject
{
    Q_OBJECT
public:
    explicit MotorWorker(QObject *parent = 0);
    virtual ~MotorWorker();
public slots:
    void init();
    void doWork();
private:
    QAxObject *motor;
}
mit folgender Implementierung

Code: Alles auswählen

MotorWorker::MotorWorker(QObject *parent) :
    QObject(parent)
{}

MotorWorker::~MotorWorker()
{
    ::CoUninitialize();
}

void MotorWorker::init()
{
    motor = new QAxObject();
    ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    motor->setControl(QString::fromUtf8("{E63B11AE-0BF1-4B40-96A3-4D7501A50E11}"));
}

void MotorWorker::doWork()
{
    ... some work, e.g.
    motor->dynamicCall("someMethod(double)", someParam);
}
Und eine Controller Klasse

Code: Alles auswählen

class Controller : public QObject
{
    Q_OBJECT
public:
    Controller();
    virtual ~Controller();
    void init();
signals:
    void sig_doWork();
    void sig_init();
private:
    QThread workerThread;
    MotorWorker* worker;
}
mit folgender Implementierung:

Code: Alles auswählen

Controller::Controller()
{
    worker = new MotorWorker;
    worker->moveToThread(&workerThread);
    connect(&workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));

    connect(this, SIGNAL(sig_init()), worker, SLOT(init()), Qt::QueuedConnection);
    connect(this, SIGNAL(sig_doWork()), worker, SLOT(doWork()), Qt::QueuedConnection);

    workerThread.start();
}

Controller::init()
{
    emit sig_init();
}
Auf den ersten Blick scheint's mal zu laufen ... vielen Dank für die Antworten!
Antworten