[SOLVED] Handling COM event Qt/C++



  • Hi there,

    I am porting an Excel macro to Qt4/C++.

    This macro uses ActiveX to command and receive data from another application.

    I need to receive events from this application and I don't know how to do it. Currently, the Excel macro uses the 'WithEvents' keyword to do so.

    Is there any equivalent with Qt? How to do it?

    Thanks



  • Hi Maxbester,

    I do not really get what you do.
    You say you port an excel macro, an excel macro is something written in VBA within Excel.
    How does Qt come into the game here? How do you use Qt here? And where? inprocess?



  • [quote author="Gerolf" date="1359970851"]Hi Maxbester,

    I do not really get what you do.
    You say you port an excel macro, an excel macro is something written in VBA within Excel.
    How does Qt come into the game here? How do you use Qt here? And where? inprocess?[/quote]

    I am sorry for the misunderstanding. Actually, I am not really porting the macro. I am re-developing it with Qt.

    Is it clearer?



  • So you are are out of the excel process?
    WithEvents is a VB macro.



  • To me, it looks as if Maxbester is trying to automate Excel using a Qt application that interfaces with Excel through ActiveX. That application is to replace an older Excel macro. Right?

    Perhaps you should study the QAx* examples, especially the one automating Outlook.



  • [quote author="Andre" date="1359975781"]To me, it looks as if Maxbester is trying to automate Excel using a Qt application that interfaces with Excel through ActiveX. That application is to replace an older Excel macro. Right?[/quote]

    Yes you are right.

    [quote author="Andre" date="1359975781"]Perhaps you should study the QAx* examples, especially the one automating Outlook.[/quote]

    I studied the Outlook example but, maybe I'm wrong, I didn't see any information about COM event handling.



  • I didn't try it myself, but it looked to me like COM events are exposed as (dynamically generated) signals.



  • [quote author="Andre" date="1359984176"]I didn't try it myself, but it looked to me like COM events are exposed as (dynamically generated) signals.[/quote]

    Yes, I think it would be a good thing. However, Qt generated a header file from a .tlb file and it doesn't have any signal at all.

    So I am stuck...



  • Hi,

    You mean COM events (I expect you mean callbacks?) which you subscribe?
    Those are callback methods implemented in a callback interface. If that is done correctly with COM, the ActiveQt creates a class with signals which you can connect to.

    You should check, whether the tlb file really contains outgoing interfaces for the class.



  • Thanks for your reply.
    I don't know how to check the tlb file (I'm new to Windows and COM programming). What I know is that ActiveQt didn't generate any signal...



  • Did you try to just connect to a signal with the appropriate name at runtime? I think it needs to be something like this:
    @
    connect(activeXObject, SIGNAL(ItemAdd(IDispatch*)), this, SLOT(whatever()));
    @
    Where ItemAdd is the name of the ActiveX event. The documentation of QAxObject states:
    [quote]You can subclass QAxObject, but you cannot use the Q_OBJECT macro in the subclass (the generated moc-file will not compile), so you cannot add further signals, slots or properties. This limitation is due to the metaobject information generated in runtime. To work around this problem, aggregate the QAxObject as a member of the QObject subclass.[/quote]
    That makes me think that QAxObject does some magic behind the scenes where it dynamically, at runtime, creates the interface in terms of signals. So, there is nothing to inspect at design time.



  • Al right, this is not a bad idea. I am going to try that.
    Thanks!



  • Hi Maxbester,

    to read the content of a Tlb (or a OCM dll, ocx file) you can use Oleview from Microsoft.
    It extracts all information of a type library.



  • It doesn't seem to work. I've got the message:
    @
    QObject::connect: No such signal QAxBase::INotify2(...)
    @

    I found a document about the software I try to interact with. It is "Pulse Labshop":http://homel.vsb.cz/~tum52/download/PULSEAutomation.pdf from "Brüel & Kjaer":http://www.bksv.com.

    This link leads to a pdf with some basic information. Page 56 is an explanation about how to work with its notification system (to me, it just looks like events).



  • INotify2 doesn't look like the actual name of the event. However, what you could try is to use QMetaObject to just list the signals from your QAxObject at runtime.

    @
    const QMetaObject* mo = myQAxObject->metaObject();
    int i(0);
    while (i < mo->methodCount()) {
    QMetaMethod mm = mo->metaMethod(i++);

    QString typeString;
    switch (mm.type()) {
    case QMetaMethod::Method: typeString = QString("Method"); break;
    case QMetaMethod::Signal: typeString = QString("Signal"); break;
    case QMetaMethod::Slot: typeString = QString("Slot"); break;
    case QMetaMethod::Constructor: typeString = QString("Constructor"); break;
    }

    QString accessString;
    switch (mm.access()) {
    case QMetaMethod::Private: typeString = QString("Private"); break;
    case QMetaMethod::Protected: typeString = QString("Protected"); break;
    case QMetaMethod::Public: typeString = QString("Public"); break;
    }

    qDebug() << accessString << typeString << mm.signature();
    }
    @

    Something like the above can be used to get a good view of all the methods, signals and slots of your object. It should also list any dynamically generated ones, if there are any. Perhaps you can use it to find out the exact signature of the signals you're after, instead of guessing what they might be called and what arguments you need for them...



  • Thanks a lot! I didn't know a such thing was possible.

    I changed a little bit your code because there are some mistakes:

    @
    PulseLabShop::IPulseLabShop2* app = new PulseLabShop::IPulseLabShop2();
    const QMetaObject* mo = app->metaObject();
    for (int i=0; i<mo->methodCount(); i++) {
    QMetaMethod mm = mo->method(i);

      QString typeString;
      switch (mm.methodType()) {
      case QMetaMethod::Method: typeString = QString("Method"); break;
      case QMetaMethod::Signal: typeString = QString("Signal"); break;
      case QMetaMethod::Slot: typeString = QString("Slot"); break;
      case QMetaMethod::Constructor: typeString = QString("Constructor"); break;
      }
    
      QString accessString;
      switch (mm.access()) {
      case QMetaMethod::Private: typeString = QString("Private"); break;
      case QMetaMethod::Protected: typeString = QString("Protected"); break;
      case QMetaMethod::Public: typeString = QString("Public"); break;
      }
    
      qDebug() << accessString << typeString << mm.signature();
    }
    

    @

    I found one signal for this class. I will try to connect it.



  • Finally, all classes and interfaces return the same signals and slots:

    @
    "Protected" "signal" destroyed(QObject*)
    "Protected" "signal" destroyed()
    "Public" "slot" deleteLater()
    "Public" "slot" _q_reregisterTimers(void*)
    "Protected" "signal" signal(QString,int,void*)
    "Protected" "signal" propertyChanged(QString)
    "Protected" "signal" exception(int,QString,QString,QString)
    @

    I cannot connect a protected signal. I am still stuck... Any idea?



  • All of these are standard QObject signals and slots, nothing specific to QAxObject. Are you sure the QAxObject is properly initialized before you do this? That is, that it represents a valid COM object? If so, then I have no clue, and I am afraid I led you down a dead end. My apologies for that.



  • [quote author="Andre" date="1360050606"]Are you sure the QAxObject is properly initialized before you do this?[/quote]

    It should be. However, the program license may be expired. I have to work on this and try to reinstall Pulse Labshop.
    I will come back here after.

    Anyway, thanks for your help ;)



  • [quote author="Maxbester" date="1359997679"]Thanks a lot! I didn't know a such thing was possible.

    I changed a little bit your code because there are some mistakes:

    @
    PulseLabShop::IPulseLabShop2* app = new PulseLabShop::IPulseLabShop2();
    const QMetaObject* mo = app->metaObject();
    for (int i=0; i<mo->methodCount(); i++) {
    QMetaMethod mm = mo->method(i);

      QString typeString;
      switch (mm.methodType()) {
      case QMetaMethod::Method: typeString = QString("Method"); break;
      case QMetaMethod::Signal: typeString = QString("Signal"); break;
      case QMetaMethod::Slot: typeString = QString("Slot"); break;
      case QMetaMethod::Constructor: typeString = QString("Constructor"); break;
      }
    
      QString accessString;
      switch (mm.access()) {
      case QMetaMethod::Private: typeString = QString("Private"); break;
      case QMetaMethod::Protected: typeString = QString("Protected"); break;
      case QMetaMethod::Public: typeString = QString("Public"); break;
      }
    
      qDebug() << accessString << typeString << mm.signature();
    }
    

    @

    I found one signal for this class. I will try to connect it.[/quote]

    Better than that:

    @
    QString filename="Doc.html";
    QFile file( filename );
    if ( file.open(QIODevice::Write) )
    {
    QTextStream stream( &file );
    stream << app->generateDocumentation();
    }
    @



  • I didn't try connecting to Event for Excel but for Word and I found out that I could only connect to Events which have no parameters like 'DocumentChange()'. I use the tool 'dumpcpp.exe' from ActiveQt and generated wrapper classes for Word, but handling events was only possible via the generic signal
    @signal(QString,int,void*)@

    I don't know why it didn't work but there are different Event signatures.

    I want to show an example:

    First of all, I connect to the generic signal 'signal(QString,int,void*)' to handle all event. In the first param (QString), you find the name of the event with full signature. In case of word, there is e.g. an event called

    @DocumentBeforeClose(IDispatch*,bool&)@

    If i directly connect to that signal...

    @connect(word, SIGNAL(DocumentBeforeClose(IDispatch*,bool&)), this, SLOT(onDocumentBeforeClose(IDispatch*,bool&)));@

    ...I get a message:

    Object::connect: No such signal Word::Application::DocumentBeforeClose(IDispatch*,bool&)

    I studied the generated code of the word wrapper classes and I found in the metadata string, that the signature is:

    @DocumentBeforeClose(Document*,bool&)@

    The same signature is printed out if I use the code from Maxbester to show all signals. If I connect to this event, there is no output on console (no error message) so I thought this is the correct event, but my slot is never called (it has no parameter just to make sure that there are no problems due to paramter conversion, etc)

    @connect(word, SIGNAL(DocumentBeforeClose(Document*,bool&)), this, SLOT(onDocumentBeforeClose()));@

    By the way, all word events are fired 2 to 4 times (http://qt-project.org/forums/viewthread/38109), so I think there are either bugs in ActiveQt event handling or there is a trick how to do it?!?!



  • There may be some bugs indeed.

    In the .pro file I added:

    @TYPELIBS = $$system( dumpcpp lib/MyLib.tlb -n MyNameSpace -o src/MyActiveXClass )@

    Note that the -o is important. It was missing in my first tries. This option writes the class declaration to MyActiveXClass.h and meta object infomation to MyActiveXClass.cpp.

    Maybe it will help you.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.