COM 应用程序示例(ActiveQt)

COM 应用程序示例演示了如何使用 ActiveQt 开发一个可通过 COM 实现自动化的 Qt 应用程序。基于QObject 的各类类被作为 COM 对象对外暴露,这些 COM 对象与正在运行的 Qt 应用程序的 GUI 进行通信。这些 COM 对象的 API 设计旨在与标准 COM 应用程序(即 Microsoft Office 中的 COM 应用程序)的 API 保持一致。

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;
};

第一个类Application 代表应用程序对象。它公开了只读属性documents 和id ,用于访问文档列表和标识符。一个读写属性visible 控制基于QTabWidget 的应用程序用户界面是否可见,而一个槽quit() 用于终止应用程序。

设置RegisterObject属性是为了确保该类的实例注册到 COM 的运行对象表 (ROT) 中——这使得 COM 客户端能够连接到已实例化的 COM 对象。

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;
};

DocumentList 类存储了一份文档列表。它提供了一个API,用于读取文档数量、按索引访问每个文档以及创建新文档。application 属性返回根对象。

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;
};

Document 类最终代表应用程序中的文档。每个文档在应用程序的标签页控件中由一个页面表示,并具有一个标题,该标题可通过文档的API进行读取和写入。application 属性同样返回根对象。

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());
}

Document 类的实现会为标签控件创建一个新页面,并将该页面的标题用于title属性。当文档被删除时,该页面也会被删除。

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;
}

DocumentList 的实现非常简单。

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"

Application 类在构造函数中初始化用户界面,并在setVisible() 方法的实现中对其进行显示和隐藏操作。对象名称(可通过id 属性访问)被设置为"From QAxFactory ",以表明该COM对象是由COM创建的。 请注意,这里没有用于删除QTabWidget 的析构函数——该操作是在quit() 槽中完成的,且在通过单次触发定时器调用quit() 之前进行,这是为了确保对该槽的 COM 调用已完全结束。

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

这些类通过QAxFactory 宏从服务器导出。只有Application 对象可以从外部实例化——其他API必须先通过Application API访问相应的对象后才能使用。

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();
}

main() 入口点函数会创建一个 `QApplication`,如果应用程序是由 COM 启动的,则直接进入事件循环。如果应用程序是由用户启动的,则创建 `Application ` 对象,并将对象名称设置为“From Application”。 随后启动 COM 服务器,并将应用程序对象注册到 COM 中。此时,COM 客户端可通过特定于客户端的 API 访问该对象。

应用程序的退出由显式控制——若由 COM 启动应用程序,则客户端代码必须调用 quit();若由用户启动应用程序,则当最后一个窗口关闭时,应用程序即终止。

最后,显示用户界面,并启动事件循环。

现在,一个简单的 Visual Basic 应用程序就可以访问这个 Qt 应用程序了。在 VB 中,创建一个新的“标准可执行文件”项目,并添加对 comappLib 类型库的项目引用。创建一个包含列表框“DocumentList”、静态标签“DocumentsCount”和命令按钮“NewDocument”的窗体。 最后,按如下方式实现该窗体的代码:

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

要构建该示例,必须先构建QAxServer 库。然后运行qmake ,并在examples\activeqt\comapp 中运行您的make工具。

示例项目 @ 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.