COM 应用程序示例(ActiveQt)

COM App 示例展示了如何使用 ActiveQt 开发可通过 COM 自动运行的 Qt 应用程序。基于QObject 的不同类作为 COM 对象公开,这些对象与运行中的 Qt GUI 应用程序进行通信。这些 COM 对象的 API 设计与标准 COM 应用程序(即 Microsoft Office 中的应用程序)的 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 代表应用程序对象。它公开了只读属性documentsid ,用于访问文档列表和标识符。读/写属性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 类存储文档列表。它提供了一个应用程序接口,用于读取文档数量、按索引访问每个文档以及创建新文档。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 中,启动一个新的 "Standard Exe "项目,并添加对 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 库。然后运行qmakeexamples\activeqt\comapp 中的 make 工具。

示例项目 @ code.qt.io

© 2025 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.