Exemple d'application COM (ActiveQt)

L'exemple d'application COM montre comment utiliser ActiveQt pour développer une application Qt pouvant être automatisée via COM. Différentes classes basées sur l QObject sont exposées en tant qu'objets COM qui communiquent avec l'interface graphique de l'application Qt en cours d'exécution. Les API de ces objets COM ont été conçues pour ressembler aux API des applications COM standard, c'est-à-dire celles de Microsoft Office.

class Application : public QObject
{
    Q_OBJECT

    Q_CLASSINFO("ClassID", "{b50a71db-c4a7-4551-8d14-49983566afee}")
    Q_CLASSINFO("InterfaceID", "{4a427759-16ef-4ed8-be79-59ffe5789042}")
    Q_CLASSINFO("RegisterObject", "yes")

    Q_PROPERTY(DocumentList* documents READ documents)
    Q_PROPERTY(QString id READ id)
    Q_PROPERTY(bool visible READ isVisible WRITE setVisible)

public:
    explicit Application(QObject *parent = nullptr);
    DocumentList *documents() const;

    QString id() const { return objectName(); }

    void setVisible(bool on);
    bool isVisible() const;

    QTabWidget *window() const { return m_ui.data(); }

public slots:
    void quit();

private:
    QScopedPointer <DocumentList> m_docs;
    QScopedPointer <QTabWidget> m_ui;
};

La première classe, Application, représente l’objet application. Elle expose des propriétés en lecture seule, documents et id, permettant d’accéder à la liste des documents, ainsi qu’un identifiant. Une propriété en lecture/écriture, visible, contrôle si l’interface utilisateur de l’application, basée sur QTabWidget, doit être visible, et un slot, quit(), permet de fermer l’application.

L'attribut RegisterObject est défini pour garantir que les instances de cette classe soient enregistrées dans la table des objets en cours d'exécution (ROT) de COM, ce qui permet aux clients COM de se connecter à un objet COM déjà instancié.

class DocumentList : public QObject
{
    Q_OBJECT

    Q_CLASSINFO("ClassID", "{496b761d-924b-4554-a18a-8f3704d2a9a6}")
    Q_CLASSINFO("InterfaceID", "{6c9e30e8-3ff6-4e6a-9edc-d219d074a148}")

    Q_PROPERTY(Application* application READ application)
    Q_PROPERTY(int count READ count)

public:
    explicit DocumentList(Application *application);

    int count() const;
    Application *application() const;

public slots:
    Document *addDocument();
    Document *item(int index) const;

private:
    QList<Document *> m_list;
};

La classe ` DocumentList ` stocke une liste de documents. Elle fournit une API permettant de lire le nombre de documents, d’accéder à chaque document par son index et de créer un nouveau document. La propriété ` application ` renvoie l’objet racine.

class Document : public QObject
{
    Q_OBJECT

    Q_CLASSINFO("ClassID", "{2b5775cd-72c2-43da-bc3b-b0e8d1e1c4f7}")
    Q_CLASSINFO("InterfaceID", "{2ce1761e-07a3-415c-bd11-0eab2c7283de}")

    Q_PROPERTY(Application *application READ application)
    Q_PROPERTY(QString title READ title WRITE setTitle)

public:
    explicit Document(DocumentList *list);
    virtual ~Document();

    Application *application() const;

    QString title() const;
    void setTitle(const QString &title);

private:
    QScopedPointer <QWidget> m_page;
};

Enfin, la classe ` Document ` représente un document dans l’application. Chaque document est représenté par une page dans le widget d’onglets de l’application et possède un titre accessible en lecture et en écriture via l’API du document. La propriété ` application ` renvoie là encore l’objet racine.

Document::Document(DocumentList *list)
: QObject(list)
{
    QTabWidget *tabs = list->application()->window();
    m_page.reset(new QWidget(tabs));
    m_page->setWindowTitle(tr("Unnamed"));
    tabs->addTab(m_page.data(), m_page->windowTitle());

    m_page->show();
}

Document::~Document() = default;

Application *Document::application() const
{
    return qobject_cast<DocumentList *>(parent())->application();
}

QString Document::title() const
{
    return m_page->windowTitle();
}

void Document::setTitle(const QString &t)
{
    m_page->setWindowTitle(t);

    QTabWidget *tabs = application()->window();
    int index = tabs->indexOf(m_page.data());
    tabs->setTabText(index, m_page->windowTitle());
}

L'implémentation de la classe ` Document ` crée une nouvelle page pour le widget d’onglets et utilise le titre de cette page pour la propriété `title`. La page est supprimée lorsque le document est supprimé.

DocumentList::DocumentList(Application *application)
: QObject(application)
{
}

Application *DocumentList::application() const
{
    return qobject_cast<Application *>(parent());
}

int DocumentList::count() const
{
    return m_list.size();
}

Document *DocumentList::item(int index) const
{
    return m_list.value(index, nullptr);
}

Document *DocumentList::addDocument()
{
    Document *document = new Document(this);
    m_list.append(document);

    return document;
}

L’implémentation de ` DocumentList ` est simple.

Application::Application(QObject *parent)
: QObject(parent),
  m_ui(new QTabWidget),
  m_docs(new DocumentList(this))
{
    setObjectName(QStringLiteral("From QAxFactory"));
}

DocumentList *Application::documents() const
{
    return m_docs.data();
}

void Application::setVisible(bool on)
{
    m_ui->setVisible(on);
}

bool Application::isVisible() const
{
    return m_ui->isVisible();
}

void Application::quit()
{
    m_docs.reset();
    m_ui.reset();
    QTimer::singleShot(0 /*ms*/, qApp, &QCoreApplication::quit);
}

#include "main.moc"

La classe ` Application ` initialise l’interface utilisateur dans le constructeur, puis l’affiche et la masque dans l’implémentation de ` setVisible()`. Le nom de l’objet (accessible via la propriété ` id `) est défini sur ` "From QAxFactory ` pour indiquer que cet objet COM a été créé par COM. Notez qu’il n’existe aucun destructeur chargé de supprimer l’objet ` QTabWidget ` ; cette opération est effectuée dans le slot ` quit() `, avant d’appeler ` quit()` via un temporisateur à déclenchement unique, ce qui est nécessaire pour garantir que l’appel COM vers le slot soit terminé.

QAXFACTORY_BEGIN("{edd3e836-f537-4c6f-be7d-6014c155cc7a}", "{b7da3de8-83bb-4bbe-9ab7-99a05819e201}")
   QAXCLASS(Application)
   QAXTYPE(Document)
   QAXTYPE(DocumentList)
QAXFACTORY_END()

Les classes sont exportées depuis le serveur à l’aide des macros QAxFactory. Seuls les objets Application peuvent être instanciés depuis l’extérieur ; les autres API ne peuvent être utilisées qu’après avoir accédé aux objets correspondants via l’API Application.

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    app.setQuitOnLastWindowClosed(false);

    // started by COM - don't do anything
    if (QAxFactory::isServer())
        return app.exec();

    // started by user
    Application appobject;
    appobject.setObjectName(QStringLiteral("From Application"));

    QAxFactory::startServer();
    QAxFactory::registerActiveObject(&appobject);

    appobject.window()->setMinimumSize(300, 100);
    appobject.setVisible(true);

    QObject::connect(&app, &QGuiApplication::lastWindowClosed, &appobject, &Application::quit);

    return app.exec();
}

La fonction d’entrée main() crée un objet ` QApplication` et entre simplement dans la boucle d’événements si l’application a été lancée par COM. Si l’application a été lancée par l’utilisateur, l’objet ` Application ` est alors créé et son nom est défini sur « From Application ». Le serveur COM est ensuite lancé, et l’objet application est enregistré auprès de COM. Il est désormais accessible aux clients COM via les API spécifiques à chaque client.

La fermeture de l’application est contrôlée explicitement : si COM a lancé l’application, le code client doit appeler `quit()` ; si l’utilisateur a lancé l’application, celle-ci se termine lorsque la dernière fenêtre a été fermée.

Enfin, l’interface utilisateur est affichée et la boucle d’événements est lancée.

Une simple application Visual Basic peut désormais accéder à cette application Qt. Sous VB, lancez un nouveau projet « Standard Exe » et ajoutez une référence de projet à la bibliothèque de types comappLib. Créez un formulaire comportant une liste déroulante « DocumentList », une étiquette statique « DocumentsCount » et un bouton de commande « NewDocument ». Enfin, implémentez le code du formulaire comme suit :

Private Application As comappLib.Application
Private MyApp As Boolean

Private Sub UpdateList()
    DocumentList.Clear
    DocumentsCount.Caption = Application.documents.Count
    For Index = 0 To Application.documents.Count - 1
       DocumentList.AddItem (Application.documents.Item(Index).Title)
    Next
End Sub

Private Sub Form_Load()
    On Error GoTo CreateNew
    Set Application = GetObject(, "comapp.Application")
    MyApp = False
    GoTo Initialized
CreateNew:
    On Error GoTo InitializeFailed
    Set Application = New Application
    Application.Visible = True
    MyApp = True
Initialized:
    Caption = Application.id
    UpdateList
InitializeFailed:
End Sub

Private Sub Form_Unload(Cancel As Integer)
    If MyApp Then
        Application.quit
    End If
End Sub

Private Sub NewDocument_Click()
    Application.documents.addDocument
    UpdateList
End Sub

Pour compiler l’exemple, vous devez d’abord compiler la bibliothèque QAxServer. Exécutez ensuite qmake et votre outil de compilation dans examples\activeqt\comapp.

Exemple de projet @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.