Qt ActiveQt Framework

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wyy626562203/article/details/82499038

ActiveQt Framework

Qt 5.10.1

1.Active Qt

Qt的ActiveX和COM支持允许Windows开发人员使用Qt:

  • 在其Qt应用程序中访问和使用任何提供ActiveX服务的ActiveX控件和COM对象。
    • 使他们的Qt应用程序可用作COM服务,任意数量的Qt对象和小部件作为COM对象和ActiveX控件。

有关在Qt中使用ActiveX的详细信息,请参阅在Building ActiveX servers in Qt

ActiveQt框架包含两个模块:

  • QAxContainer模块是一个实现QObject和QWidget子类的静态库,QAxObjectQAxWidget,充当COM对象和ActiveX控件的容器。
    • QAxServer模块是一个静态库,用于实现进程内和可执行COM服务的功能。 该模块提供QAxAggregatedQAxBindableQAxFactory类。

提供了一组工具来简化使用ActiveX的Qt项目的开发和构建。

要构建静态库,请更改为activeqt目录(通常为QTDIR/src/ activeqt),并在容器和控制子目录中运行qmake和make工具。 库qaxcontainer.lib和qaxserver.lib将链接到QTDIR/lib。

如果您正在使用Qt的共享配置,请进入plugin子目录并运行qmake和make工具来构建一个插件将QAxContainer模块集成到Qt Designer中。

2.ActiveQt工具

2.1 IDC

IDC (The Interface Description Compiler)工具是ActiveQt构建系统的一部分,可以将任何Qt二进制文件转换为只需几行代码的完整COM对象服务。

IDC了解以下命令行参数:

Option Result
dll -idl idl -version x.y Writes the IDL of the server dll to the file idl. The type library wll have version x.y.
dll -tlb tlb Replaces the type library in dll with tlb
-v Print version information
-regserver dll Register the COM server dll
-unregserver Unregister the COM server dll

通常不需要手动调用IDC,因为qmake构建系统负责向构建过程添加所需的后处理步骤。 有关详细信息,请参阅ActiveQt文档。

2.2 The dumpcpp Tool

dumpcpp工具为类型库生成C++命名空间。

要为类型库生成C++命名空间,请使用以下命令行参数调用dumpcpp

Option Result
input Generate documentation for input. input can specify a type library file or a type library ID, or a CLSID or ProgID for an object
-o file Writes the class declaration to file.h and meta object information to file.cpp
-n namespace Generate a C++ namespace namespace
-nometaobject Do not generate a .cpp file with the meta object information. The meta object is then generated in runtime.
-getfile libid Print the filename for the typelibrary libid to stdout
-compat Generate namespace with dynamicCall-compatible API
-v Print version information
-h Print help

dumpcpp可以集成到qmake构建系统中。 在.pro文件中,列出要在TYPELIBS变量中使用的类型库:


TEMPLATE = app
TARGET   = qutlook
QT += widgets axcontainer

TYPELIBS = $$system(dumpcpp -getfile {00062FFF-0000-0000-C000-000000000046})

生成的命名空间将声明所有枚举,以及类型库中声明的每个coclass和接口的一个QAxObject子类。 标有control属性的coclasses将由QAxWidget子类封装。

封装可创建的coclass的那些类(即未标记为不可创建的coclass)具有默认构造函数; 这通常是Application类的单实例。

Outlook::Application *outlook = new Outlook::Application;

所有其他类只能通过将IDispatch接口指针传递给构造函数来创建; 但是,不应该明确创建这些类。 而是使用已创建对象的适当API。

Outlook::_NameSpace *session = outlook->Session();

所有coclass封装器也有一个构造函数为每个实现的接口提供接口封装类。

Outlook::NameSpace *session = outlook->Session();

您必须创建coclass才能连接到子对象的信号。 请注意,构造函数会删除接口对象,因此以下操作会导致段错误:

Outlook::_NameSpace *tmp = outlook->Session();
Outlook::NameSpace *session = new Outlook::NameSpace(tmp);
delete tmp; // or any other use of tmp: segfault

如果返回类型是在另一个类型库中声明的coclass或接口类型,则必须在包含要使用的命名空间的头文件之前包含该其他类型库的命名空间头文件(必须使用此工具生成两个头文件)。

默认情况下,返回子对象的方法和属性将使用库中的类型。 函数的调用者负责删除或重新显示返回对象。 如果设置了-compat开关,则返回COM对象的属性和方法的返回类型为IDispatch *,并且命名空间不会声明接口的封装类。

在这种情况下,显式创建正确的包装类:

Outlook::NameSpace *session = new Outlook::NameSpace(outlook->Session());

您当然可以直接使用IDispatch *返回,在这种情况下,您必须在完成界面时调用Release()

命名空间中的所有类都标记有一个宏,允许您从DLL导出或导入它们。 为此,请在包含头文件之前将宏声明为扩展为__declspecdllimport/export)。

要构建该工具,您必须首先构建QAxContainer库。然后在tools/dumpcpp中运行make工具。

2.3 The dumpdoc Tool

dumpdoc工具为任何COM对象生成Qt样式的文档,并将其写入指定的文件。

使用以下命令行参数调用dumpdoc:

Option Result
-o file Writes output to file
object Generate documentation for object
-v Print version information
-h Print help

object必须是安装在本地机器上的对象(即不支持远程对象),并且可以包括可通过属性访问的子对象,即。Outlook.Application/Session/CurrentUser

生成的文件是使用Qt文档样式的HTML文件。

要构建该工具,您必须首先构建QAxContainer库。 然后在tools/dumpdoc中运行make工具。

2.4 testcon

testcon(An ActiveX Test Container)应用程序实现ActiveX控件的通用测试容器。您可以插入系统上安装的ActiveX控件,并执行方法和修改属性。容器将在日志窗口中记录有关事件和属性更改的信息以及调试输出。

部分代码使用Qt元对象和ActiveQt框架的内部,不建议在应用程序代码中使用。

使用应用程序查看使用某个ActiveX实例化时通过QAxWidget类可用的槽,信号和属性,并测试您在Qt应用程序中实现或想要使用的ActiveX控件。

应用程序可以加载和执行JavaScript,VBScript,Perl和Python(如果已安装)的脚本文件,以自动执行加载的控件。脚本子目录中提供了使用QAxWidget2类的示例脚本文件。

请注意,此示例的qmake项目包含带有版本资源的资源文件testcon.rc。这是某些ActiveX控件(即Shockwave ActiveX控件)所必需的,如果缺少此类版本信息,则可能会崩溃或行为异常。

要构建该工具,必须首先构建QAxContainerQAxServer库。然后在tools/testcon中运行make工具并运行生成的testcon.exe。

3.在Qt中使用ActiveX控件和COM

QAxContainer模块是ActiveQt框架的一部分。 它提供了一个实现QWidget子类的库,QAxWidget,它充当ActiveX控件的容器,以及一个QObject子类QAxObject,可用于轻松访问非可视COM对象。 通过QAxScriptQAxScriptManagerQAxScriptEngine类可以编写使用这些类嵌入的COM对象脚本,轻松访问COM对象。

该模块由六个类组成

  1. QAxBase是一个抽象类,它提供API来初始化和访问COM对象或ActiveX控件。
  2. QAxObject提供了一个封装COM对象的QObject。
  3. QAxWidget是一个封装ActiveX控件的QWidget。
  4. QAxScriptManagerQAxScriptQAxScriptEngine提供Windows脚本接口。

提供了一些使用标准ActiveX控件来提供高级用户界面功能的示例应用程序。

3.1 使用ActiveX库

构建可以托管COM对象和ActiveX控件的Qt应用程序通过添加将应用程序链接到QAxContainer模块到.pro文件

QT += axcontainer

3.2 分发QAxContainer应用程序

QAxContainer库是静态的,因此在使用此模块时无需重新分发任何其他文件。 但请注意,您使用的ActiveX服务二进制文件可能未安装在目标系统上,因此您必须随软件包在应用程序的安装过程中注册它们。

3.3 实例化COM对象

要实例化COM对象,请使用QAxBase::setControl()API,或者将对象的名称直接传递给您正在使用的QAxBase子类的构造函数。

控件可以以多种格式指定,但最快和最强大的格式是直接使用对象的类ID(CLSID)。 可以包含许可控件的许可证密钥。

3.4 典型错误消息

ActiveQt在运行时遇到错误情况时会将错误消息输出到调试输出。 通常,您必须在调试器中运行程序才能看到这些消息(例如,在Visual Studio的Debug输出中)。

3.5 请求的控件无法实例化

QAxBase::setControl()中请求的控件未安装在此系统上,或者当前用户无法访问。

控件可能需要管理员权限或许可证密钥。 如果控件已获得许可,请将许可证密钥传递给QAxBase::setControl,如文档所示。

3.6 访问对象API

ActiveQt为COM对象提供Qt API,并用Qt等价物替换COM数据类型。

有四种方法可以在COM对象上调用API:

  • Generating a C++ namespace
  • Call-by-name
  • Through a script engine
  • Using the native COM interfaces

3.6.1 Generating a C++ Namespace

为要访问的类型库生成C++命名空间,请使用dumpcpp工具。 在要使用的类型库上手动运行此工具,或者通过将类型库添加到应用程序的.pro文件中的TYPELIBS变量,将其集成到构建系统中:

TYPELIBS = file.tlb

请注意,dumpcpp可能无法公开类型库中的所有API。

在代码中包含生成的头文件,以通过生成的C++类访问对象API。 有关更多信息,请参阅Qutlook示例。

3.6.2 Call-by-Name

使用QAxBase::dynamicCall()QAxBase::querySubObject()以及QObject::setProperty()QObject::property()API通过名称调用COM对象的方法和属性。使用dumpdoc工具获取任何COM对象及其子对象的Qt API文档;请注意,并非所有COM对象的API都可用

有关更多信息,请参阅Webbrowser示例。

3.6.3 通过脚本引擎调用函数

Qt应用程序可以托管系统上安装的任何ActiveScript引擎。然后,脚本引擎可以运行访问COM对象的脚本代码。

要实例化脚本引擎,请使用QAxScriptManager::addObject()注册要从脚本访问的COM对象,使用QAxScriptManager::load()将脚本代码加载到引擎中。然后使用QAxScriptManager::call()QAxScript::call()调用脚本函数。

通过脚本可以使用COM对象的哪些API取决于所使用的脚本语言。

ActiveX测试容器演示了脚本文件的加载。

3.6.4使用本机COM接口调用函数

要调用无法通过上述任何方法访问的COM对象的函数,可以使用QAxBase::queryInterface()直接请求COM接口。要获得相应接口类的C++定义,请使用带有控件提供的类型库的#import指令;有关详细信息,请参阅编译器手册。

3.6.5 典型错误消息

ActiveQt在运行时遇到错误情况时会将错误消息输出到调试输出。通常,您必须在调试器中运行程序才能看到这些消息(例如,在Visual Studio的Debug输出中)。

QAxBase::internalInvoke:方法不存在

QAxBase::dynamicCall()失败 - 函数原型与对象API中的任何可用函数都不匹配。

调用IDispatch成员时出错:缺少非可选参数

QAxBase::dynamicCall()失败 - 函数原型是正确的,但提供的参数太少。

调用IDispatch成员时出错:参数n中的类型不匹配

QAxBase::dynamicCall()失败 - 函数原型是正确的,但索引n处的参数类型错误,无法强制转换为正确的类型。

QAxScriptManager::call():没有脚本提供此功能

您尝试调用通过不提供内省的引擎(即ActivePythonActivePerl)提供的函数。您需要直接在相应的QAxScript对象上调用该函数。

4.在Qt中构建ActiveX服务器

QAxServer模块是ActiveQt框架的一部分。 它由三个类组成:

  • QAxFactory定义了一个用于创建COM对象的工厂。
    • QAxBindable提供Qt小部件和COM对象之间的接口。
    • 可以对QAxAggregated进行子类化以实现其他COM接口。

4.1 使用ActiveX库

要使用QAxServer库将标准Qt应用程序转换为COM服务,必须将axserver添加到.pro文件中的QT变量。

从.pro文件生成进程外可执行服务,如下所示:

TEMPLATE = app
QT  += axserver

RC_FILE  = qaxserver.rc
...

要构建进程内服务,请使用如下的.pro文件:

TEMPLATE = lib
QT += axserver
CONFIG  += dll

DEF_FILE = qaxserver.def
RC_FILE  = qaxserver.rc
...

文件qaxserver.rc和qaxserver.def是框架的一部分,使用它们的通常位置(在.pro文件中指定路径),也可以复制到项目目录中。 您可以修改这些文件,只要它包含任何文件作为类型库条目,即, 您可以添加版本信息或指定其他工具箱图标。

使用axserver模块将使qmake工具将所需的构建步骤添加到构建系统:

  • 将二进制文件链接到qaxserver.lib而不是qtmain.lib
    • 调用idc工具为COM服务器生成IDL文件
    • 使用MIDL工具将IDL编译到类型库中(编译器安装的一部分)
    • 将生成的类型库作为二进制资源附加到服务二进制文件(再次使用idc工具)
    • 注册服务器

要跳过后处理步骤,还要设置qaxserver_no_postlink配置。

此外,您可以使用VERSION变量指定版本号,例如


  TEMPLATE = lib
  VERSION = 2.5
  ...

指定的版本号将在注册时用作类型库和服务的版本。

4.2 进程外与进程外

COM服务是作为独立可执行文件运行还是作为客户端进程中的共享库运行,主要取决于要在服务器中提供的COM对象的类型。

可执行服务的优点是能够作为独立应用程序运行,但却增加了COM客户端和COM对象之间通信的相当大的开销。如果控件有编程错误,则只有运行控件的服务进程会崩溃,客户端应用程序可能会继续运行。并非所有COM客户端都支持可执行COM服务。

进程内服务通常更小,启动时间更短。客户端和服务器之间的通信直接通过虚函数调用完成,不会引入远程过程调用所需的开销。但是,如果服务器崩溃,客户端应用程序也可能崩溃,并且并非每个功能都可用于进程内服务(即在COM的running-object-table中注册)。

两种服务器类型都可以将Qt用作共享库,或者静态链接到服务二进制文件中。

4.3 构建后步骤中的典型错误

要使ActiveQt特定的后处理步骤起作用,服务器必须满足一些要求:

  • 除了存在QApplication实例之外,只能创建暴露的所有控件
    • 服务的初始链接包括临时类型库资源
    • 运行服务器所需的所有依赖项都在系统路径中(或在调用环境使用的路径中;请注意,Visual Studio在“工具”|“选项”|“目录”对话框中列出了自己的一组环境变量)。

如果不满足这些要求,可能会发生以下错误:

4.3.1 服务器可执行文件崩溃

要生成IDL,需要实例化作为ActiveX控件公开的窗口小部件(调用构造函数)。此时,除了QApplication对象之外别无其他。您的小部件构造函数不得依赖于要创建的任何其他对象,例如它应该检查空指针。

要调试服务,请使用-dumpidl outputfile运行它,并检查它崩溃的位置。

请注意,不会调用控件的任何功能。

4.3.2 服务器可执行文件不是有效的Win32应用程序

二进制文件附加类型库损坏了。这是Windows中的一个错误,仅发生在发布版本。

第一个链接步骤必须将虚拟类型库链接到可执行文件中,以后可以用idc替换它。将具有类型库的资源文件添加到项目中,如示例中所示。

4.3.3 “无法找到DLL”

构建系统需要运行服务可执行文件以生成接口定义,并注册服务。如果服务链接的动态链接库不在路径中,则可能会失败(例如,Visual Studio使用“目录”选项中指定的环境设置调用服务)。确保服务所需的所有DLL和插件都位于错误消息框中打印的路径中列出的目录中(另请参阅Windows部署工具)。

4.3.4 “不能打开文件 …”

当最后一个客户端停止使用它时,ActiveX服务器无法正常关闭。应用程序终止通常需要大约两秒钟,但您可能必须使用任务管理器来终止进程(例如,当客户端未正确释放控件时)。

4.3.5 控件无法实例化

在这种情况下,以管理员身份注册服务器可能会有所帮助。

4.4 实现控制

要使用Qt实现COM对象,请创建QObject的子类或任何现有的QObject子类。如果该类是QWidget的子类,则COM对象将是ActiveX控件。


#include <QWidget>
class MyActiveX : public QWidget
{
    Q_OBJECT

需要Q_OBJECT宏来向ActiveQt框架提供有关窗口小部件的元对象信息。


Q_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}")
Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}")
Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}")

使用Q_CLASSINFO()宏指定COM对象的COM标识符。 ClassIDInterfaceID是必需的,而EventsID仅在对象有信号时才需要。 要生成这些标识符,请使用uuidgenguidgen等系统工具。

您可以为每个类指定其他属性; 有关详细信息,请参阅类信息和调整。

Q_PROPERTY(int value READ value WRITE setValue)

使用Q_PROPERTY()宏声明ActiveX控件的属性。

声明一个标准构造函数,它接受父对象,以及任何QObject子类的函数,信号和槽。


public:
    MyActiveX(QWidget *parent = 0)
    ...
    int value() const;

public slots:
    void setValue(int v);
    ...

signals:
    void valueChange(int v);
    ...
};

ActiveQt框架将属性和公共槽公开为ActiveX属性和方法,并将信号显示为ActiveX事件,并在Qt数据类型和等效的COM数据类型之间进行转换。

4.4.1 数据类型

属性支持的Qt数据类型是:

Qt data type COM property
bool VARIANT_BOOL
QString BSTR
int int
uint unsigned int
double double
qlonglong CY
qulonglong CY
QColor OLE_COLOR
QDate DATE
QDateTime DATE
QTime DATE
QFont IFontDisp*
QPixmap IPictureDisp*
QVariant VARIANT
QVariantList (same as QList<QVariant>) SAFEARRAY(VARIANT)
QStringList SAFEARRAY(BSTR)
QByteArray SAFEARRAY(BYTE)
QRect User defined type
QSize User defined type
QPoint User defined type

信号和槽中的参数支持的Qt数据类型是:

Qt data type COM parameter
bool [in] VARIANT_BOOL
bool& [in, out] VARIANT_BOOL*
QString, const QString& [in] BSTR
QString& [in, out] BSTR*
QString& [in, out] BSTR*
int [in] int
int& [in,out] int
uint [in] unsigned int
uint& [in, out] unsigned int*
double [in] double
double& [in, out] double*
QColor, const QColor& [in] OLE_COLOR
QColor& [in, out] OLE_COLOR*
QDate, const QDate& [in] DATE
QDate& [in, out] DATE*
QDateTime, const QDateTime& [in] DATE
QDateTime& [in, out] DATE*
QFont, const QFont& [in] IFontDisp*
QFont& [in, out] IFontDisp**
QPixmap, const QPixmap& [in] IPictureDisp*
QPixmap& [in, out] IPictureDisp**
QList<QVariant>, const QList<QVariant>& [in] SAFEARRAY(VARIANT)
QList<QVariant>& [in, out] SAFEARRAY(VARIANT)*
QStringList, const QStringList& [in] SAFEARRAY(BSTR)
QStringList& [in, out] SAFEARRAY(BSTR)*
QByteArray, const QByteArray& [in] SAFEARRAY(BYTE)
QByteArray& [in, out] SAFEARRAY(BYTE)*
QObject* [in] IDispatch*
QRect& [in, out] struct QRect (user defined)
QSize& [in, out] struct QSize (user defined)
QPoint& [in, out] struct QPoint (user defined)

还支持导出的枚举和标志(请参阅Q_ENUMS()Q_FLAGS())。 参数类型也支持作为返回值。

ActiveQt框架忽略使用任何其他数据类型具有参数的属性和信号/槽。

4.4.2 子对象

COM对象可以有多个子对象,可以表示COM对象的子元素。 表示多文档电子表格应用程序的COM对象,例如为每个电子表格提供一个子对象。

只要QAxFactory知道,任何QObject子类都可以用作ActiveX中子对象的类型。 然后,类型可以在属性中使用,或者作为槽的返回类型或参数。

4.4.3 属性通知

要使属性可绑定到ActiveX客户端,请使用QAxBindable类中的多重继承:

#include <QAxBindable>
#include <QWidget>

class MyActiveX : public QWidget, public QAxBindable
{
      Q_OBJECT

在实现属性写入函数时,使用QAxBindable类的requestPropertyChange()propertyChanged()函数来允许ActiveX客户端绑定到控件属性。

4.4.4 服务控制

要使COM服务器可用于COM系统,必须使用五个唯一标识符在系统注册表中注册它。 这些标识符由guidgenuuidgen等工具提供。 注册信息允许COM本地化二进制文件,提供所请求的ActiveX控件,对控件进行远程过程调用,并读取有关控件公开的方法和属性的类型信息。

要在客户端请求COM对象时创建COM对象,服务器须导出QAxFactory的实现。 最简单的方法是使用一组宏:

QAXFACTORY_BEGIN("{ad90301a-849e-4e8b-9a91-0a6dc5f6461f}","{a8f21901-7ff7-4f6a-b939-789620c03d83}")
    QAXCLASS(MyWidget)
    QAXCLASS(MyWidget2)
    QAXTYPE(MySubType)
QAXFACTORY_END()

这将导出MyWidget和MyWidget2作为COM客户端可以创建的COM对象,并将MySubType注册为可以在MyWidget和MyWidget2的属性和参数中使用的类型。

QAxFactory类文档说明了如何使用此宏,以及如何实现和使用自定义工厂。

对于进程外可执行服务器,您可以实现main()函数来实例化QApplication对象并像任何普通的Qt应用程序一样进入事件循环。 默认情况下,应用程序将作为标准Qt应用程序启动,但如果在命令行上传递-activex,它将作为ActiveX服务器启动。使用QAxFactory::isServer()创建和运行标准应用程序接口,或者阻止独立执行:

#include <QApplication>
#include <QAxFactory>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    if (!QAxFactory::isServer()) {
        // create and show main window
    }
    return app.exec();
}

然而,这不是必需的,因为ActiveQt提供了主函数的默认实现。 默认实现调用QAxFactory::startServer(),创建QApplication实例并调用exec()

要构建ActiveX服务器可执行文件,请运行qmake以生成makefile,并将编译器的make工具用于任何其他Qt应用程序。 make进程还将通过使用-regserver命令行选项调用生成的可执行文件来注册系统注册表中的控件。

如果ActiveX服务器是可执行文件,则支持以下命令行选项:

Option Result
-regserver Registers the server in the system registry
-unregserver Unregisters the server from the system registry
-activex Starts the application as an ActiveX server
-dumpidl -version x.y Writes the server’s IDL to the specified file. The type library will have version x.y

In-process servers can be registered using the regsvr32 tool available on all Windows systems.

Typical Compile-Time Problems

The compiler/linker errors listed are based on those issued by the Microsoft Visual C++ 6.0 compiler.

“No overloaded function takes 2 parameters”

When the error occurs in code that uses the QAXFACTORY_DEFAULT() macro, the widget class had no constructor that can be used by the default factory. Either add a standard widget constructor or implement a custom factory that doesn’t require one.

When the error occurs in code that uses the QAXFACTORY_EXPORT() macro, the QAxFactory subclass had no appropriate constructor. Provide a public class constructor like

可以使用所有Windows系统上提供的regsvr32工具注册进程内服务器。

4.4.5 典型的编译时问题

列出的编译器/链接器错误基于Microsoft Visual C ++ 6.0编译器发出的错误。

4.4.5.1 “没有重载需要2个参数函数”

当在使用QAXFACTORY_DEFAULT()宏的代码中发生错误时,窗口小部件类没有可供默认工厂使用的构造函数。 添加标准窗口小部件构造函数或实现不需要的自定义工厂。

当在使用QAXFACTORY_EXPORT()宏的代码中发生错误时,QAxFactory子类没有适当的构造函数。 提供类似的公共类构造函数

MyFactory(const QUuid &, const QUuid &);
4.4.5.2 “语法错误:bad suffix on number”

唯一标识符尚未作为字符串传递到QAXFACTORY_EXPORT()QAXFACTORY_DEFAULT()宏中。

4.4.5.3 “未解析的外部符号 _ucm_instantiate”

服务不会导出QAxFactory的实现。在项目的一个实现文件中使用QAXFACTORY_EXPORT()宏来实例化和导出工厂,或使用QAXFACTORY_DEFAULT()宏来使用默认工厂。

4.4.5.4 “_ucm_initialize已在…中定义”

服务器导出QAxFactory的多个实现,或两次导出相同的实现。如果使用默认工厂,则QAXFACTORY_DEFAULT()宏只能在项目中使用一次。如果服务器提供多个ActiveX控件,请使用自定义QAxFactory实现和QAXFACTORY_EXPORT()宏。

4.5 分发QAxServer二进制文件

用Qt编写的ActiveX服务器可以将Qt用作共享库,或者将Qt静态链接到二进制文件中。两种方式都会产生相当大的包(服务器二进制文件本身变大,或者你必须包含Qt DLL)。

4.6 安装独立服务器

当您的ActiveX服务器也可以作为独立应用程序运行时,在目标系统上安装可执行文件后,使用-regserver命令行参数运行服务器可执行文件。之后,服务器提供的控件将可供ActiveX客户端使用。

4.7 安装进程内服务

当您的ActiveX服务是安装包的一部分时,请使用Microsoft提供的regsvr32工具在目标系统上注册控件。如果此工具不存在,请将DLL加载到安装程序进程中,解析DllRegisterServer符号并调用该函数:

HMODULE dll = LoadLibrary("myserver.dll");
typedef HRESULT(__stdcall *DllRegisterServerProc)();
DllRegisterServerProc DllRegisterServer =(DllRegisterServerProc)GetProcAddress(dll, "DllRegisterServer");

HRESULT res = E_FAIL;
if (DllRegisterServer)
    res = DllRegisterServer();
if (res != S_OK)
    // error handling

4.8 通过Internet分发服务

如果要在网页中使用服务器中的控件,则需要使服务器可用于查看页面的浏览器,并且需要在页面中指定服务包的位置。

要指定服务的位置,请使用Web站点的OBJECT标记中的CODEBASE属性。 该值可以指向服务器文件本身,指向服务器所需的其他文件(例如Qt DLL)的INF文件,或压缩的CAB存档。

INF和CAB文件几乎记录在关于ActiveX和COM编程以及MSDN库和各种其他在线资源的书中。 示例包括可用于构建CAB归档的INF文件:

[version]
signature="$CHICAGO$"
AdvancedINF=2.0
[Add.Code]
simpleax.exe=simpleax.exe
[simpleax.exe]
file-win32-x86=thiscab
clsid={DF16845C-92CD-4AAB-A982-EB9840E74669}
RegisterServer=yes

Microsoft的CABARC工具可以轻松生成CAB存档:

cabarc N simpleax.cab simpleax.exe simple.inf

INF文件假定Qt静态构建,因此INF文件中没有列出与其他DLL的依赖关系。 要根据DLL分发ActiveX服务器,必须添加依赖项,并为库文件提供存档。

4.9 使用控件

要使用ActiveX控件,例如 要将它们嵌入到网页中,请使用 HTML标记。

<object ID="MyActiveX1" CLASSID="CLSID:ad90301a-849e-4e8b-9a91-0a6dc5f6461f">
...
<\object>

要初始化控件的属性,请使用

<object ID=...>
    <param name="name" value="value">
<\object>

如果Web浏览器支持脚本,则使用JavaScript,VBScript和表单来编写控件脚本。 ActiveQt示例包括示例控件的演示HTML页面。

4.9.1 支持和不支持的ActiveX客户端

以下是基于我们自己的ActiveX控件和客户端应用程序的经验,并不完整。

4.9.2 支持的客户端

这些标准应用程序与使用ActiveQt开发的ActiveX控件一起使用。 请注意,某些客户端仅支持进程内控件。

  • Internet Explorer
  • Microsoft ActiveX Control Test Container
  • Microsoft Visual Studio 6.0
  • Microsoft Visual Studio.NET/2003
  • Microsoft Visual Basic 6.0
  • MFC- and ATL-based containers
  • Sybase PowerBuilder
  • ActiveQt based containers

支持Microsoft Office应用程序,但您需要将控件注册为“Insertable”对象。 重新实现QAxFactory::registerClass以将此属性添加到COM类,或使用Q_CLASSINFO宏将类的“Insertable”类信息设置为“是”。

4.9.3 不受支持的客户端

我们还没有设法使基于ActiveQt的COM对象与以下客户端应用程序一起使用。

  • Borland C++ Builder (Versions 5 and 6)
  • Borland Delphi

4.9.4 典型的运行时错误

4.9.4.1 服务不响应

如果系统无法启动服务(请与任务管理器核实服务是否运行进程),请确保系统路径中没有缺少服务所依赖的DLL(例如Qt DLL!)。使用dependency walker查看服务器二进制文件的所有依赖项。

如果服务器运行(例如,任务管理器列出进程),请参阅以下部分以获取有关调试服务器的信息。

4.9.4.2 无法创建对象

如果可以在构建过程中正确构建和注册服务,但是对象不能初始化,例如通过OLE/COM对象查看器应用程序,确保系统路径(例如Qt DLL)中没有服务器所依赖的DLL。使用dependency walker查看服务器二进制文件的所有依赖项。

如果服务器运行,请参阅以下部分以获取有关调试服务器的信息。

4.9.4.3 调试运行时错误

要在Visual Studio中调试进程内服务,请将服务器项目设置为活动项目,并在项目设置中指定客户端“可执行调试会话”(例如,使用ActiveX测试容器)。您可以在代码中设置断点,如果安装了调试版本,还可以使用ActiveQt和Qt代码。

要调试可执行服务,请在调试器中运行该应用程序,然后从命令行参数-activex开始。然后启动您的客户端并创建ActiveX控件的实例。 COM将使用现有进程为下一个尝试创建ActiveX控件的客户端。

4.10 类信息和调整

要为每个COM类提供属性,请使用Q_CLASSINFO宏,它是Qt的元对象系统的一部分。

Key Meaning of value
Version The version of the class (1.0 is default)
Description A string describing the class.
ClassID The class ID. You must reimplement QAxFactory::classID if not specified.
InterfaceID The interface ID. You must reimplement QAxFactory::interfaceID if not specified.
EventsID The event interface ID. No signals are exposed as COM events if not specified.
DefaultProperty The property specified represents the default property of this class. Ie. the default property of a push button would be “text”.
DefaultSignal The signal specified respresents the default signal of this class. Ie. the default signal of a push button would be “clicked”.
LicenseKey Object creation requires the specified license key. The key can be empty to require a licensed machine. By default classes are not licensed. Also see the following section.
StockEvents Objects expose stock events if value is “yes”. See QAxFactory::hasStockEvents()
ToSuperClass Objects expose functionality of all super-classes up to and including the class name in value. See QAxFactory::exposeToSuperClass()
Insertable If the value is “yes” the class is registered to be “Insertable” and will be listed in OLE 2 containers (ie. Microsoft Office). This attribute is not be set by default.
Aggregatable If the value is “no” the class does not support aggregation. By default aggregation is supported.
Creatable If the value is “no” the class cannot be created by the client, and is only available through the API of another class (ie. the class is a sub-type).
RegisterObject If the value is “yes” objects of this class are registered with OLE and accessible from the running object table (ie. clients can connect to an already running instance of this class). This attribute is only supported in out-of-process servers.
MIME The object can handle data and files of the format specified in the value. The value has the format mime:extension:description. Multiple formats are separated by a semicolon.
CoClassAlias The classname used in the generated IDL and in the registry. This is esp. useful for C++ classes that live in a namespace - by default, ActiveQt just removes the “::” to make the IDL compile.
Implemented Categories List of comma-separated Category ID (CATID) UUIDs. Generic mechanism for specifying additional container capabilities, in addition to “control”, “insertable” etc. Typical CATIDs include CATID_InternetAware (“{0DE86A58-2BAA-11CF-A229-00AA003D7352}”), CATID_SafeForScripting (“{7DD95801-9882-11CF-9FA9-00AA006C42C4}”) as well as user-defined CATID values.

请注意,键和值都区分大小写。

下面声明了一个类的2.0版,它只公开自己的API,并且可以在Microsoft Office应用程序的“插入对象”对话框中找到。


class MyActiveX : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("Version", "2.0")
    Q_CLASSINFO("ClassID", "{7a4cffd8-cbcd-4ae9-ae7e-343e1e5710df}")
    Q_CLASSINFO("InterfaceID", "{6fb035bf-8019-48d8-be51-ef05427d8994}")
    Q_CLASSINFO("EventsID", "{c42fffdf-6557-47c9-817a-2da2228bc29c}")
    Q_CLASSINFO("Insertable", "yes")
    Q_CLASSINFO("ToSuperClass", "MyActiveX")
    Q_PROPERTY(...)

public:
    MyActiveX(QWidget *parent = 0);
    ...
};

4.10.1 开发许可组件

如果您在开发组件,则可能需要控制谁能够实例化这些组件。 由于服务器二进制文件可以发送到任何客户端计算机并在其上注册,因此任何人都可以在自己的软件中使用这些组件。

许可组件可以使用各种技术完成,例如, 创建控件的代码可以提供许可证密钥,或者应该运行控件的机器需要获得许可。

要将Qt类标记为已许可,请使用Q_CLASSINFO()宏指定“LicenseKey”。


class MyLicensedControl : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("LicenseKey", "<key string>")
    ...
};

密钥必须能够在未经许可的机器上创建MyLicensedControl的实例。许可开发人员现在可以使用他的应用程序重新分发服务二进制文件,该应用程序使用“LicenseKey”的值创建控件,而应用程序的用户无法在没有许可证密钥的情况下创建控件。

如果控件的单个许可证密钥不足(即,您希望不同的开发人员接收不同的许可证密钥),则可以指定一个空密钥以指示控件需要许可证,并重新实现QAxFactory::validateLicenseKey()以验证系统上存在许可证(即通过许可证文件)。

4.10.2 更多接口

ActiveQt服务器提供的ActiveX控件支持一组最小的COM接口来实现OLE规范。当ActiveX类继承自QAxBindable类时,它还可以实现其他COM接口。

创建QAxAggregated的新子类,并使用多重继承来子类化其他COM接口类。

class AxImpl : public QAxAggregated, public ISomeCOMInterface
{
public:
    AxImpl() {}

    long queryInterface(const QUuid &iid, void **iface);

    // IUnknown
    QAXAGG_IUNKNOWN

    // ISomeCOMInterface
    ...
}

重新实现QAxAggregated::queryInterface()函数以支持其他COM接口。


long AxImpl::queryInterface(const QUuid &iid, void **iface)
{
    *iface = 0;
    if (iid == IID_ISomeCOMInterface)
    *iface = (ISomeCOMInterface *)this;
    else
    return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

由于ISomeCOMInterfaceIUnknown的子类,因此您必须实现QueryInterface()AddRef()Release()函数。 在类定义中使用QAXAGG_IUNKNOWN宏来执行此操作。 如果手动实现IUnknown函数,则将调用委托给QAxAggregated::controllingUnknown()函数返回的接口指针,例如:

HRESULT AxImpl::QueryInterface(REFIID iid, void **iface)
{
    return controllingUnknown()->QueryInterface(iid, iface);
}

queryInterface()实现中不支持IUnknown接口本身。

实现COM接口的方法,如果需要调用实现控件的QObject子类,请使用QAxAggregated::object()

QAxBindable子类中,实现QAxBindable::createAggregate()以返回QAxAggregated子类的新对象。


class MyActiveX : public QWidget, public QAxBindable
{
    Q_OBJECT
public:
    MyActiveX(QWidget *parent);

    QAxAggregated *createAggregate()
    {
        return new AxImpl();
    }
};

5.ActiveQt Examples

5.1 COM App Example (ActiveQt)


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表示应用程序对象。 它公开了只读属性文档和id以访问文档列表和标识符。 可见的读/写属性控制应用程序的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()
{
}

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

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::setAttribute(Qt::AA_EnableHighDpiScaling);
    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库。 然后在examples\activeqt\comapp中运行qmake和make工具。

#.pro
include(../shared.pri)

TEMPLATE = app
QT  += axserver

QT += widgets

# Input
SOURCES += main.cpp

RC_FILE  = comapp.rc

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/comapp
INSTALLS += target

5.2 Dot Net Example (ActiveQt)

5.2.1 Qt vs. .NET

Qt是一个C++库,可以编译成传统的本机二进制文件,充分利用运行时环境提供的性能。

.NET有一个关键概念是“中间语言代码” - 源代码被编译成字节码格式,并且在运行时,该字节码在虚拟机中执行 - 公共语言运行时(CLR)。

另一个关键概念是托管代码。这基本上是以CLR可以处理内存管理的方式编写的中间语言代码,即CLR将进行自动垃圾收集,因此应用程序代码不需要为未使用的对象显式释放内存。

C#和VB.NET的MS编译器只生成托管代码。这样的程序不能直接调用普通的本机函数或类。

另一方面,用于.NET的MS C ++编译器可以生成普通代码和托管代码。要编写可以编译为托管代码的C++类,开发人员必须使用__gc关键字将该类标记为托管,并将代码限制为仅使用称为“C++托管扩展”的C++子集,或简称MC++ 。优点是MC ++代码可以自由调用和使用普通的C++函数和类。它也可以反过来:普通的C++代码可以调用托管函数并使用托管类(例如整个.NET框架类库),包括托管函数和用C#或VB.NET实现的类。这种混合托管和普通C++代码的功能极大地简化了与.NET的互操作性,并被微软称为“It Just Works”(IJW)功能。

本文档演示了将普通C++代码(使用Qt)与托管.NET代码集成的两种不同方法。首先,介绍了手动方式,其中包括在普通Qt / C++类中使用精简MC++包装类。然后,提出了自动化方法,它利用ActiveQt框架作为通用桥。第一种方法的优点是它为应用程序开发人员提供了完全控制,而第二种方法需要较少的编码,并使开发人员无需处理托管数据对象和普通数据对象之间的转换。

不耐烦的读者,他马上想看到一个QPushButton和一个在.NET GUI应用程序中运行的自定义Qt小部件(QAxWidget2)被称为ActiveQt的示例目录。它包含使用Visual Studio .NET(而不是2003)创建的C#和VB.NET的演练结果。将示例/dotnet /walkthrough/csharp.csproj,examples/dotnet/walkthrough/vb.vbproj,examples/dotnet /wrapper/wrapper.sln加载到IDE中并运行解决方案。

备注:您会注意到在生成的代码中,以下行被注释掉:

' VB is case insensitive, but our C++ controls are not.
' Me.resetButton.enabled = True

无论何时更改对话框,都会重新生成此行,在这种情况下,您必须再次将其注释掉才能运行项目。 这是Visual Studio.NET原始版本中的一个错误,并在2003版本中得到修复。

5.2.2 演练:与MC++和IJW的.NET Interop

通过提供用MC++编写的包装类,可以从托管.NET代码中使用普通C++类和函数。 包装器类将负责将调用转发到普通的C++函数或方法,并根据需要转换参数数据。 由于包装类是托管类,因此可以在任何托管的.NET应用程序中使用它,无论是用C#,VB.NET,MC++还是其他托管编程语言编写。


class Worker : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString statusString READ statusString WRITE setStatusString)
public:
    Worker();

    QString statusString() const;

public slots:
    void setStatusString(const QString &string);

signals:
    void statusStringChanged(const QString &string);

private:
    QString status;
};

对于Qt用户来说,Qt类并没有什么不寻常之处,即使像Q_PROPERTY,槽和信号也是直接的C++用实现的,因此在使用任何C++编译器编译这个类时不会造成任何麻烦。


class Worker;

// .NET class
public __gc class netWorker
{
public:
    netWorker();
    ~netWorker();

    __property String *get_StatusString();
    __property void set_StatusString(String *string);

    __event void statusStringChanged(String *args);

private:
    Worker *workerObject;
};

The .NET wrapper class uses keywords that are part of MC++ to indicate that the class is managed/garbage collected (__gc), and that StatusString should be accessible as a property in languages that support this concept (__property). We also declare an event function statusStringChanged(String*) (__event), the equivalent of the respective signal in the Qt class.

Before we can start implementing the wrapper class we need a way to convert Qt’s datatypes (and potentionally your own) into .NET datatypes, e.g. QString objects need to be converted into objects of type String*.

When operating on managed objects in normal C++ code, a little extra care must be taken because of the CLR’s garbage collection. A normal pointer variable should not

be used to refer to a managed object. The reason is that the garbage collection can kick in at any time and move the object to another place on the heap, leaving you with an invalid pointer.

However, two methods are provided that solves this problem easily. The first is to use a pinned pointer, i.e. declare the pointer variable with the __pin keyword. This guarantees that the object pointed to will not be moved by the garbage collector. It is recommended that this method not be used to keep a references to managed objects for a long time, since it will decrease the efficiency of the garbage collector. The second way is to use the gcroot smartpointer template type. This lets you create safe pointers to managed objects. E.g. a variable of type gcroot will always point to the String object, even if it has been moved by the garbage collector, and it can be used just like a normal pointer.

.NET包装器类使用属于MC++的关键字来指示该类是托管/垃圾收集(gc),并且StatusString应该可以作为支持此概念(属性)的语言中的属性访问。我们还声明了一个事件函数statusStringChanged(String *)(__ event),相当于Qt类中的信号。

在我们开始实现包装器类之前,我们需要一种方法将Qt的数据类型(以及可能属于您自己的数据类型)转换为.NET数据类型,例如需要将QString对象转换为String *类型的对象。

在普通C++代码中对托管对象进行操作时,由于CLR的垃圾回收,必须要格外小心。普通的指针变量不应该

用于引用托管对象。原因是垃圾收集可以随时启动并将对象移动到堆上的另一个位置,从而留下无效指针。

但是,提供了两种方法可以很容易地解决这个问题。第一种是使用固定指针,即使用__pin关键字声明指针变量。这可以保证垃圾收集器不会移动指向的对象。建议不要使用此方法长时间保持对托管对象的引用,因为这会降低垃圾收集器的效率。第二种方法是使用gcroot smartpointer模板类型。这使您可以创建托管对象的安全指针。例如。 gcroot 类型的变量将始终指向String对象,即使它已被垃圾收集器移动,并且它可以像普通指针一样使用。


#include <QString>

#using <mscorlib.dll>
#include <vcclr.h>

using namespace System;

String *QStringToString(const QString &qstring)
{
    return new String((const wchar_t *)qstring.utf16());
}

QString StringToQString(String *string)
{
    const wchar_t __pin *chars = PtrToStringChars(string);
    return QString::fromWCharArray(chars);
}

然后可以在包装器类实现中使用转换器函数来调用本机C++类中的函数。


#include "networker.h"
#include "worker.h"
#include "tools.h"

netWorker::netWorker()
{
    workerObject = new Worker();
}

netWorker::~netWorker()
{
    delete workerObject;
}

构造函数和析构函数只是创建和销毁使用C++运算符new和delete包装的Qt对象。


String *netWorker::get_StatusString()
{
    return QStringToString(workerObject->statusString());
}

netWorker类将从.NET代码调用委托给本机代码。 虽然这两个世界之间的转换意味着每个函数调用和类型转换的性能都很小,但这应该可以忽略不计,因为我们无论如何都要在CLR中运行。


void netWorker::set_StatusString(String *string)
{
    workerObject->setStatusString(StringToQString(string));
    __raise statusStringChanged(string);
}

在使用__raise关键字触发事件之前,属性setter调用本机Qt类。

这个包装类现在可以在.NET代码中使用,例如 使用C++,C#,Visual Basic或任何其他可用于.NET的编程语言。


using System;

namespace WrapperApp
{
        class App
        {
                void Run()
                {
                        netWorker worker = new netWorker();

                        worker.statusStringChanged += new netWorker.__Delegate_statusStringChanged(onStatusStringChanged);

                        System.Console.Out.WriteLine(worker.StatusString);

                        System.Console.Out.WriteLine("Working cycle begins...");
                        worker.StatusString = "Working";
                        worker.StatusString = "Lunch Break";
                        worker.StatusString = "Working";
                        worker.StatusString = "Idle";
                        System.Console.Out.WriteLine("Working cycle ends...");
                }

                private void onStatusStringChanged(string str)
                {
                        System.Console.Out.WriteLine(str);
                }

                [STAThread]
                static void Main(string[] args)
                {
                        App app = new App();
                        app.Run();
                }
        }
}

5.2.3 演练:.NET / COM与ActiveQt互操作

幸运的是,.NET为COM对象提供了一个通用的包装器,即Runtime Callable Wrapper(RCW)。此RCW是COM对象的代理,在.NET Framework客户端激活COM对象时由CLR生成。这提供了在.NET Framework项目中重用COM对象的通用方法。

使用ActiveQt可以轻松实现将QObject类转换为COM对象,并在QAxServer示例中进行了演示(例如,简单示例)。本演练将使用这些示例中实现的Qt类,因此首先要确保这些示例已正确构建,例如通过打开Internet Explorer中的演示页面来验证控件是否正常运行。

5.2.3.1 启动一个项目

启动Visual Studio.NET,并创建一个用于编写Windows应用程序的新C#项目。这将在Visual Studio的对话框编辑器中显示一个空表单。您应该看到工具箱,它为您提供了许多不同类别的可用控件和对象。如果右键单击工具箱,则可以添加新选项卡。我们将添加标签“Qt”。

5.2.3.2 导入Qt小部件

该类别默认只有一个指针工具,我们必须在表单中添加我们想要使用的Qt对象。右键单击空白区域,然后选择“自定义”。这将打开一个对话框,其中包含两个选项卡“COM组件”和“.NET Framework组件”。我们使用ActiveQt将QWidgets包装到COM对象中,因此我们选择“COM Components”页面,并查找我们想要使用的类,例如“QPushButton”和“QAxWidget2”。

当我们选择这些小部件并关闭对话框时,现在可以从工具箱中将两个小部件作为灰色方块提供,其名称旁边是它们

5.2.3.3 使用Qt小部件

我们现在可以在表单中添加QAxWidget2和QPushButton的实例。 Visual Studio将自动为对象服务器生成RCW。 QAxWidget2实例占用表单的大部分上半部分,QPushButton位于右下角。

在Visual Studio的属性编辑器中,我们可以修改控件的属性 - QPushButton公开QWidget API并具有许多属性,而QAxWidget2除了“Miscellaneous”类别中的自己的属性“lineWidth”之外只有Visual Studio标准属性。这些对象被命名为“axQPushButton1”和“axQAxWidget21”,因为特别是名字有点令人困惑,我们将对象重命名为“resetButton”和“circleWidget”。

我们还可以更改Qt属性,例如将resetButton的“text”属性设置为“Reset”,将circleWidget的“lineWidth”属性设置为5.我们还可以将这些对象放入Visual Studio的对话框编辑器提供的布局系统中,例如,通过将circleWidget的锚点设置为“Left,Top,Right,Bottom”,并将resetButton的锚点设置为“Bottom,Right”。

现在我们可以编译并启动项目,它将使用我们的两个Qt小部件打开用户界面。如果我们可以调整对话框的大小,小部件将适当调整大小。

5.2.3.4 处理Qt信号

我们现在将为小部件实现事件处理程序。选择circleWidget并在属性编辑器中选择“Events”页面。窗口小部件公开事件,因为QAxWidget2类在其类定义中设置了“StockEvents”属性。我们为ClickEvent实现事件处理程序circleClicked,以便每次点击将线宽增加一:


private void circleClicked(object sender, System.EventArgs e)
{
    this.circleWidget.lineWidth++;
}

通常,我们可以通过双击表单中的窗口小部件来实现默认事件处理程序,但是现在我们的窗口小部件的默认事件没有定义。

我们还将为QPushButton发出的点击信号实现一个事件处理程序。 将事件处理程序resetLineWidth添加到单击的事件,并实现生成的函数:


private void resetLineWidth(object sender, System.EventArgs e)
{
    this.circleWidget.lineWidth = 1;
    this.resetButton.setFocus();
}

我们将属性重置为1,并调用setFocus()槽来模拟Windows上的用户样式,当您单击按钮时,按钮会抓取焦点(以便您可以使用空格键再次单击它)。

如果我们现在编译并运行项目,我们可以单击圆形小部件以增加其线宽,然后按重置按钮将线宽设置回1。

5.2.4 总结

使用ActiveQt作为.NET世界和Qt本地世界之间的通用互操作性桥梁非常容易,并且通常不需要实现大量的手写包装类。相反,在完全跨平台的Qt项目中的QAxFactory实现提供了.NET生成RCW所需的粘合剂。

如果这还不够,我们可以通过Microsoft提供的C++扩展来实现我们自己的包装类。

5.2.4.1 限制

使用此技术与.NET进行互操作时,隐含了使用ActiveQt时的所有限制,例如:我们可以在API中使用的数据类型只能是ActiveQt和COM支持的数据类型。但是,由于这包括QObject和QWidget的子类,我们可以将任何数据类型包装到QObject子类中,以使其API可用于.NET。这具有积极的作用,即当使用Qt脚本自动化Qt应用程序和COM客户端时,相同的API可自动使用。

使用“IJW”方法时,原则上唯一的限制是编写包装类和数据类型转换函数所需的时间。

5.2.4.2 性能注意事项

从CLR字节码到本机代码的每次调用都意味着小的性能损失,并且必要的类型转换会在两个框架之间存在的每个层引入额外的延迟。因此,混合.NET和本机代码的每种方法都应尽量减少不同世界之间的通信。

由于ActiveQt同时引入了三个层 - RCW,COM和最后的ActiveQt本身 - 使用通用Qt / ActiveQt / COM / RCW / .NET桥时的性能损失大于使用手工制作的IJW-wrapper类时的性能损失。然而,执行速度仍然足以连接和修改用户界面中的交互元素,并且一旦使用Qt和C ++实现和编译性能关键算法到本机代码中的好处,ActiveQt就成为制作的有效选择即使是.NET可访问的应用程序的非可视部分。

5.3 Hierarchy Example (ActiveQt)

//objects.h 
class QParentWidget : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{d574a747-8016-46db-a07c-b2b4854ee75c}");
    Q_CLASSINFO("InterfaceID", "{4a30719d-d9c2-4659-9d16-67378209f822}");
    Q_CLASSINFO("EventsID", "{4a30719d-d9c2-4659-9d16-67378209f823}");
public:
    explicit QParentWidget(QWidget *parent = nullptr);

    QSize sizeHint() const;

public slots:
    void createSubWidget(const QString &name);

    QSubWidget *subWidget(const QString &name);

private:
    QVBoxLayout *m_vbox;
};

QParentWidget类提供了用于创建具有名字的窗口小部件的槽,并返回指向窗口小部件的指针。 类声明使用Q_CLASSINFO()为此类提供COM标识符。


QParentWidget::QParentWidget(QWidget *parent): QWidget(parent),
  m_vbox(new QVBoxLayout(this))
{
}

QParentWidget的构造函数创建一个垂直框布局。 新的子窗口小部件会自动添加到布局中。

void QParentWidget::createSubWidget(const QString &name)
{
    QSubWidget *sw = new QSubWidget(this, name);
    m_vbox->addWidget(sw);
    sw->setLabel(name);
    sw->show();
}

createSubWidget槽使用参数中提供的名称创建一个新的QSubWidget,并将标签设置为该名称。 窗口小部件也会显式显示。


QSubWidget *QParentWidget::subWidget(const QString &name)
{
    return findChild<QSubWidget *>(name);
}

subWidget插槽使用QObject::child()函数并返回具有所请求名称的QSubWidget类型的第一个子节点。

//objects.h 
class QSubWidget : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QString label READ label WRITE setLabel)

    Q_CLASSINFO("ClassID", "{850652f4-8f71-4f69-b745-bce241ccdc30}");
    Q_CLASSINFO("InterfaceID", "{2d76cc2f-3488-417a-83d6-debff88b3c3f}");
    Q_CLASSINFO("ToSuperClass", "QSubWidget");

public:
    QSubWidget(QWidget *parent = nullptr, const QString &name = QString());

    void setLabel(const QString &text);
    QString label() const;

    QSize sizeHint() const;

protected:
    void paintEvent(QPaintEvent *e);

private:
    QString m_label;
};

QSubWidget类具有单个字符串属性标签,并实现paintEvent以绘制标签。 该类再次使用Q_CLASSINFO来提供COM标识符,并将ToSuperClass属性设置为QSubWidget,以确保不暴露任何基类(即QWidget)的槽。

//objects.cpp 
QSubWidget::QSubWidget(QWidget *parent, const QString &name): QWidget(parent)
{
    setObjectName(name);
}

void QSubWidget::setLabel(const QString &text)
{
    m_label = text;
    setObjectName(text);
    update();
}

QString QSubWidget::label() const
{
    return m_label;
}

QSize QSubWidget::sizeHint() const
{
    QFontMetrics fm(font());
    return QSize(fm.width(m_label), fm.height());
}

void QSubWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setPen(palette().text().color());
    painter.drawText(rect(), Qt::AlignCenter, m_label);
}

QSubWidget类的实现是不言自明的。

//main.cpp
#include "objects.h"
#include <QAxFactory>

QAXFACTORY_BEGIN("{9e626211-be62-4d18-9483-9419358fbb03}", "{75c276de-1df5-451f-a004-e4fa1a587df1}")
    QAXCLASS(QParentWidget)
    QAXTYPE(QSubWidget)
QAXFACTORY_END()

然后使用QAxFactory导出类。 QParentWidget导出为完整类(可以创建),而QSubWidget仅作为类型导出,只能通过QParentWidget的API间接创建。

#.pro
include(../shared.pri)

TEMPLATE = lib
TARGET   = hierarchyax

CONFIG += warn_off dll
QT += widgets axserver

SOURCES  = objects.cpp main.cpp
HEADERS  = objects.h
RC_FILE  = hierarchy.rc
DEF_FILE = hierarchy.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/hierarchy
INSTALLS += target

要构建示例,您必须首先构建QAxServer库。 然后在activeqt/hierarchy中运行qmake和make工具。

演示要求WebBrowser支持ActiveX控件,并启用脚本。

//html
<script language="javascript">
function createSubWidget( form )
{
    ParentWidget.createSubWidget( form.nameEdit.value );
}

function renameSubWidget( form )
{
    var SubWidget = ParentWidget.subWidget( form.nameEdit.value );
    if ( !SubWidget ) {
        alert( "No such widget " + form.nameEdit.value + "!" );
        return;
    }
    SubWidget.label = form.labelEdit.value;
    form.nameEdit.value = SubWidget.label;
}

function setFont( form )
{
    ParentWidget.font = form.fontEdit.value;
}
</script>

<p>
This widget can have many children!
</p>
<object ID="ParentWidget" CLASSID="CLSID:d574a747-8016-46db-a07c-b2b4854ee75c"
CODEBASE="http://www.qt-project.org/demos/hierarchy.cab">
[Object not available! Did you forget to build and register the server?]
</object><br />
<form>
<input type="edit" ID="nameEdit" value="&lt;enter object name&gt;" />
<input type="button" value="Create" onClick="createSubWidget(this.form)" />
<input type="edit" ID="labelEdit" />
<input type="button" value="Rename" onClick="renameSubWidget(this.form)" />
<br />
<input type="edit" ID="fontEdit" value="MS Sans Serif" />
<input type="button" value = "Set Font" onClick="setFont(this.form)" />
</form>

5.4 Menus Example (ActiveQt)

要构建示例,您必须首先构建QAxServer库。 然后在examples/activeqt/menus中运行qmake和make工具。
演示要求WebBrowser支持ActiveX控件,并启用脚本。

<object ID="QMenus" CLASSID="CLSID:4dc3f340-a6f7-44e4-a79b-3e9217695fbd"CODEBASE="http://www.qt-project.org/demos/menusax.cab">
    [Object not available! Did you forget to build and register the server?]
</object>
//main.cpp
#include "menus.h"
#include <QApplication>
#include <QAxFactory>
#include <QScopedPointer>

QAXFACTORY_BEGIN(
    "{ce947ee3-0403-4fdc-895a-4fe779394b46}", // type library ID
    "{8de435ce-8d2a-46ac-b3b3-cb800d0847c7}") // application ID
    QAXCLASS(QMenus)
QAXFACTORY_END()

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QScopedPointer<QWidget> window;

    if (!QAxFactory::isServer()) {
        window.reset(new QMenus());
        window->show();
    }

    return a.exec();
}
//menus.cpp
#include "menus.h"
#include <QAction>
#include <QAxFactory>
#include <QMenuBar>
#include <QMessageBox>
#include <QTextEdit>
#include <QPixmap>

#include "fileopen.xpm"
#include "filesave.xpm"

QMenus::QMenus(QWidget *parent)
    : QMainWindow(parent, 0) // QMainWindow's default flag is WType_TopLevel
{
    QAction *action;

    QMenu *file = new QMenu(this);

    action = new QAction(QPixmap((const char**)fileopen), tr("&Open"), this);
    action->setShortcut(tr("CTRL+O"));
    connect(action, &QAction::triggered, this, &QMenus::fileOpen);
    file->addAction(action);

    action = new QAction(QPixmap((const char**)filesave), tr("&Save"), this);
    action->setShortcut(tr("CTRL+S"));
    connect(action, &QAction::triggered, this, &QMenus::fileSave);
    file->addAction(action);

    QMenu *edit = new QMenu(this);

    action = new QAction(tr("&Normal"), this);
    action->setShortcut(tr("CTRL+N"));
    action->setToolTip(tr("Normal"));
    action->setStatusTip(tr("Toggles Normal"));
    action->setCheckable(true);
    connect(action, &QAction::triggered, this, &QMenus::editNormal);
    edit->addAction(action);

    action = new QAction(tr("&Bold"), this);
    action->setShortcut(tr("CTRL+B"));
    action->setCheckable(true);
    connect(action, &QAction::triggered, this, &QMenus::editBold);
    edit->addAction(action);

    action = new QAction(tr("&Underline"), this);
    action->setShortcut(tr("CTRL+U"));
    action->setCheckable(true);
    connect(action, &QAction::triggered, this, &QMenus::editUnderline);
    edit->addAction(action);

    QMenu *advanced = new QMenu(this);
    action = new QAction(tr("&Font..."), this);
    connect(action, &QAction::triggered, this, &QMenus::editAdvancedFont);
    advanced->addAction(action);

    action = new QAction(tr("&Style..."), this);
    connect(action, &QAction::triggered, this, &QMenus::editAdvancedStyle);
    advanced->addAction(action);

    edit->addMenu(advanced)->setText(tr("&Advanced"));

    edit->addSeparator();

    action = new QAction(tr("Una&vailable"), this);
    action->setShortcut(tr("CTRL+V"));
    action->setCheckable(true);
    action->setEnabled(false);
    connect(action, &QAction::triggered, this, &QMenus::editUnderline);
    edit->addAction(action);

    QMenu *help = new QMenu(this);

    action = new QAction(tr("&About..."), this);
    action->setShortcut(tr("F1"));
    connect(action, &QAction::triggered, this, &QMenus::helpAbout);
    help->addAction(action);

    action = new QAction(tr("&About Qt..."), this);
    connect(action, &QAction::triggered, this, &QMenus::helpAboutQt);
    help->addAction(action);

    if (!QAxFactory::isServer())
        menuBar()->addMenu(file)->setText(tr("&File"));
    menuBar()->addMenu(edit)->setText(tr("&Edit"));
    menuBar()->addMenu(help)->setText(tr("&Help"));

    m_editor = new QTextEdit(this);
    setCentralWidget(m_editor);

    statusBar();
}

void QMenus::fileOpen()
{
    m_editor->append(tr("File Open selected."));
}

void QMenus::fileSave()
{
    m_editor->append(tr("File Save selected."));
}

void QMenus::editNormal()
{
    m_editor->append(tr("Edit Normal selected."));
}

void QMenus::editBold()
{
    m_editor->append(tr("Edit Bold selected."));
}

void QMenus::editUnderline()
{
    m_editor->append(tr("Edit Underline selected."));
}

void QMenus::editAdvancedFont()
{
    m_editor->append(tr("Edit Advanced Font selected."));
}

void QMenus::editAdvancedStyle()
{
    m_editor->append(tr("Edit Advanced Style selected."));
}

void QMenus::helpAbout()
{
    QMessageBox::about(this, tr("About QMenus"),
                       tr("This example implements an in-place ActiveX control with menus and status messages."));
}

void QMenus::helpAboutQt()
{
    QMessageBox::aboutQt(this);
}
//menus.h
#ifndef MENUS_H
#define MENUS_H

#include <QMainWindow>

class QTextEdit;

class QMenus : public QMainWindow
{
    Q_OBJECT
    Q_CLASSINFO("ClassID",     "{4dc3f340-a6f7-44e4-a79b-3e9217695fbd}")
    Q_CLASSINFO("InterfaceID", "{9ee49617-7d5c-441a-b833-4b068d40d751}")
    Q_CLASSINFO("EventsID",    "{13eca64b-ee2a-4f3c-aa04-5d9d975979a7}")

public:
    explicit QMenus(QWidget *parent = nullptr);

public slots:
    void fileOpen();
    void fileSave();

    void editNormal();
    void editBold();
    void editUnderline();

    void editAdvancedFont();
    void editAdvancedStyle();

    void helpAbout();
    void helpAboutQt();

private:
    QTextEdit *m_editor;
};

#endif // MENUS_H
#.pro
include(../shared.pri)

TEMPLATE = app
TARGET   = menusax

CONFIG += warn_off
QT += widgets axserver

SOURCES  = main.cpp menus.cpp
HEADERS  = menus.h
RC_FILE  = menus.rc
DEF_FILE = menus.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/menus
INSTALLS += target

5.5 Multiple Example (ActiveQt)

//ax1.h
class QAxWidget1 : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{1D9928BD-4453-4bdd-903D-E525ED17FDE5}")
    Q_CLASSINFO("InterfaceID", "{99F6860E-2C5A-42ec-87F2-43396F4BE389}")
    Q_CLASSINFO("EventsID", "{0A3E9F27-E4F1-45bb-9E47-63099BCCD0E3}")

    Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor)
public:
    explicit QAxWidget1(QWidget *parent = nullptr)
        : QWidget(parent), m_fillColor(Qt::red)
    {
    }

    QColor fillColor() const
    {
        return m_fillColor;
    }

    void setFillColor(const QColor &fc)
    {
        m_fillColor = fc;
        repaint();
    }

protected:
    void paintEvent(QPaintEvent *e)
    {
        QPainter paint(this);
        QRect r = rect();
        r.adjust(10, 10, -10, -10);
        paint.fillRect(r, m_fillColor);
    }

private:
    QColor m_fillColor;
};

第一个控件绘制一个填充矩形。 填充颜色作为属性公开。Q_CLASSINFO()用于指定COM标识符。

//ax2.h
class QAxWidget2 : public QWidget
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{58139D56-6BE9-4b17-937D-1B1EDEDD5B71}")
    Q_CLASSINFO("InterfaceID", "{B66280AB-08CC-4dcc-924F-58E6D7975B7D}")
    Q_CLASSINFO("EventsID", "{D72BACBA-03C4-4480-B4BB-DE4FE3AA14A0}")
    Q_CLASSINFO("ToSuperClass", "QAxWidget2")
    Q_CLASSINFO("StockEvents", "yes")
    Q_CLASSINFO("Insertable", "yes")

    Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth)
public:
    explicit QAxWidget2(QWidget *parent = nullptr)
        : QWidget(parent), m_lineWidth(1)
    {
    }

    int lineWidth() const
    {
        return m_lineWidth;
    }

    void setLineWidth(int lw)
    {
        m_lineWidth = lw;
        repaint();
    }

protected:
    void paintEvent(QPaintEvent *e)
    {
        QPainter paint(this);
        QPen pen = paint.pen();
        pen.setWidth(m_lineWidth);
        paint.setPen(pen);

        QRect r = rect();
        r.adjust(10, 10, -10, -10);
        paint.drawEllipse(r);
    }

private:
    int m_lineWidth;
};

第二个控件绘制一个圆圈。 linewith作为属性暴露。 Q_CLASSINFO()用于指定COM标识符,并设置属性ToSuperClassStockEvents以仅公开类本身的API,并将COM库存事件添加到ActiveX控件。

//main.cpp
#include "ax1.h"
#include "ax2.h"
#include <QAxFactory>

QT_USE_NAMESPACE

QAXFACTORY_BEGIN("{98DE28B6-6CD3-4e08-B9FA-3D1DB43F1D2F}", "{05828915-AD1C-47ab-AB96-D6AD1E25F0E2}")
    QAXCLASS(QAxWidget1)
    QAXCLASS(QAxWidget2)
QAXFACTORY_END()

使用QAxFactory宏从服务导出类。

#.pro
include(../shared.pri)

TEMPLATE = lib
TARGET   = multipleax

CONFIG += warn_off dll
QT += widgets axserver

SOURCES  = main.cpp
HEADERS  = ax1.h ax2.h
RC_FILE  = multipleax.rc
DEF_FILE = multipleax.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/multiple
INSTALLS += target

要构建示例,您必须首先构建QAxServer库。 然后在examples/activeqt/multiple中运行qmake和make工具。

Two Simple Qt Widgets演示要求您的WebBrowser支持ActiveX控件,并启用脚本。

//html
<script language="javascript">
function setColor( form )
{
    Ax1.fillColor = form.colorEdit.value;
}

function setWidth( form )
{
    Ax2.lineWidth = form.widthEdit.value;
}
</script>

<p />
This is one QWidget subclass:<br />
<object ID="Ax1" CLASSID="CLSID:1D9928BD-4453-4bdd-903D-E525ED17FDE5"
CODEBASE="http://qt.nokia.com/demos/multipleax.cab">
[Object not available! Did you forget to build and register the server?]
</object><br />
<form>
Fill Color: <input type="edit" ID="colorEdit" value = "red" />
<input type="button" value = "Set" onClick="setColor(this.form)" />
<input type="button" value = "Hide" onClick="Ax1.hide()" />
<input type="button" value = "Show" onClick="Ax1.show()" />
</form>

<p />
This is another QWidget subclass:<br />
<object ID="Ax2" CLASSID="CLSID:58139D56-6BE9-4b17-937D-1B1EDEDD5B71"
CODEBASE="http://qt.nokia.com/demos/multipleax.cab">
[Object not available! Did you forget to build and register the server?]
</object><br />
<form>
Line width: <input type="edit" ID="widthEdit" value = "1" />
<input type="button" value = "Set" onClick="setWidth(this.form)" />
</form>

5.6 OpenGL Example (ActiveQt)

此示例中的ActiveX控件使用Qt中的QGlWidget类在ActiveX中呈现OpenGL场景。 该控件公开了一些方法来改变场景。

该应用程序通过QAXFACTORY_BEGIN()QAXCLASS()QAXFACTORY_END()宏使用QAxFactory将GLBox小部件公开为ActiveX控件。


#include <QAxFactory>

QAXFACTORY_BEGIN(
    "{2c3c183a-eeda-41a4-896e-3d9c12c3577d}", // type library ID
    "{83e16271-6480-45d5-aaf1-3f40b7661ae4}") // application ID
    QAXCLASS(GLBox)
QAXFACTORY_END()

main的实现初始化QApplication对象,并使用QAxFactory::isServer()来确定它是否适合创建和显示应用程序接口。


  /*
    The main program is here.
  */

int main(int argc, char *argv[])
{
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication::setColorSpec(QApplication::CustomColor);
    QApplication a(argc,argv);

    if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL) {
        qWarning("This system does not support OpenGL. Exiting.");
        return -1;
    }

    if (!QAxFactory::isServer()) {
        GLObjectWindow w;
        w.resize(400, 350);
        w.show();
        return a.exec();
    }
    return a.exec();
}

GLBox类既可以从QGLWidget类继承,也可以从QAxBindable继承。


#include <QAxBindable>

class GLBox : public QGLWidget,
public QOpenGLFunctions_1_1,
public QAxBindable
{
    Q_OBJECT
    Q_CLASSINFO("ClassID",     "{5fd9c22e-ed45-43fa-ba13-1530bb6b03e0}")
    Q_CLASSINFO("InterfaceID", "{33b051af-bb25-47cf-a390-5cfd2987d26a}")
    Q_CLASSINFO("EventsID",    "{8c996c29-eafa-46ac-a6f9-901951e765b5}")

该类从QAxBindable重新实现QAxBindable::createAggregate()函数,以返回指向QAxAggregated对象的指针。


public:
    explicit GLBox(QWidget *parent, const char *name = nullptr);
    virtual ~GLBox();
    QAxAggregated *createAggregate();

public slots:
    void setXRotation(int degrees);

类声明的其余部分和OpenGL渲染的实现与原始的“box”示例相同。

GLBox类的实现文件包括objsafe.h系统头,其中定义了IObjectSafetyCOM接口。

#include <objsafe.h>

使用多重继承声明类ObjectSafetyImpl以对QAxAggregated类进行子类化,并实现IObjectSafety接口。


  class ObjectSafetyImpl : public QAxAggregated,public IObjectSafety
  {
  public:

该类声明一个默认构造函数,并实现queryInterface函数以支持IObjectSafety接口。

explicit ObjectSafetyImpl() {}

long queryInterface(const QUuid &iid, void **iface)
{
    *iface = nullptr;
    if (iid == IID_IObjectSafety)
        *iface = (IObjectSafety*)this;
    else
        return E_NOINTERFACE;

    AddRef();
    return S_OK;
}

由于每个COM接口都继承了IUnknown,因此QAXAGG_IUNKNOWN宏用于提供IUnknown接口的默认实现。 宏被定义为将对QueryInterfaceAddRefRelease的所有调用委托给controlsUnknown()函数返回的接口。

QAXAGG_IUNKNOWN;

IObjectSafety接口的实现为调用者提供有关支持和启用安全选项的信息,并为所有调用返回S_OK以指示ActiveX控件是安全的。


    HRESULT WINAPI GetInterfaceSafetyOptions(REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions)
    {
        *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER;
        *pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER;
        return S_OK;
    }

    HRESULT WINAPI SetInterfaceSafetyOptions(REFIID riid, DWORD pdwSupportedOptions, DWORD pdwEnabledOptions)
    {
        return S_OK;
    }
};

createAggregate()函数的实现只返回一个新的ObjectSafetyImpl对象。


QAxAggregated *GLBox::createAggregate()
{
    return new ObjectSafetyImpl();
}

要构建示例,您必须首先构建QAxServer库。 然后在examples/activeqt/wrapper中运行qmake和make工具。

演示要求WebBrowser支持ActiveX控件,并启用脚本。

与其他QAxServer示例相比,Internet Explorer不会打开一个对话框来询问用户是否应该允许GLBox控件的脚本编写(确切的浏览器行为取决于“Internet选项”对话框中的安全设置)。

//html
<SCRIPT LANGUAGE="JavaScript">
function setRot( form )
{
    GLBox.setXRotation( form.XEdit.value );
    GLBox.setYRotation( form.YEdit.value );
    GLBox.setZRotation( form.ZEdit.value );
}
</SCRIPT>

<p />
An OpenGL scene:<br />
<object ID="GLBox" CLASSID="CLSID:5fd9c22e-ed45-43fa-ba13-1530bb6b03e0"
CODEBASE="http://qt.nokia.com/demos/openglax.cab">
[Object not available! Did you forget to build and register the server?]
</object><br />

<form>
Rotate the scene:<br />
X:<input type="edit" ID="XEdit" value="0" /><br />
Y:<input type="edit" name="YEdit" value="0" /><br />
Z:<input type="edit" name="ZEdit" value="0" /><br />
<input type="button" value="Set" onClick="setRot(this.form)" />
</form>
//glbox.cpp
#include "glbox.h"
#include <QAxAggregated>
#include <QUuid>
#include <objsafe.h>

#if defined(Q_CC_MSVC)
#pragma warning(disable:4305) // init: truncation from const double to float
#endif

/*!
  Create a GLBox widget
*/

GLBox::GLBox(QWidget *parent, const char *name)
    : QGLWidget(parent)
{
    m_xRot = m_yRot = m_zRot = 0.0;       // default object rotation
    m_scale = 1.25;                       // default object scale
    m_object = 0;
}

/*!
  Release allocated resources
*/

GLBox::~GLBox()
{
    makeCurrent();

    if (m_object)
        glDeleteLists(m_object, 1);
}

/*!
  Paint the box. The actual openGL commands for drawing the box are
  performed here.
*/

void GLBox::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glLoadIdentity();
    glTranslatef(0.0, 0.0, -10.0);
    glScalef(m_scale, m_scale, m_scale);

    glRotatef(m_xRot, 1.0, 0.0, 0.0);
    glRotatef(m_yRot, 0.0, 1.0, 0.0);
    glRotatef(m_zRot, 0.0, 0.0, 1.0);

    glCallList(m_object);
}

/*!
  Set up the OpenGL rendering state, and define display list
*/

void GLBox::initializeGL()
{
    initializeOpenGLFunctions();

    qglClearColor(Qt::black);           // Let OpenGL clear to black
    m_object = makeObject();            // Generate an OpenGL display list
    glShadeModel(GL_FLAT);
}

/*!
  Set up the OpenGL view port, matrix mode, etc.
*/

void GLBox::resizeGL(int w, int h)
{
    glViewport(0, 0, (GLint)w, (GLint)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 15.0);
    glMatrixMode(GL_MODELVIEW);
}

/*!
  Generate an OpenGL display list for the object to be shown, i.e. the box
*/

GLuint GLBox::makeObject()
{
    GLuint list;

    list = glGenLists(1);

    glNewList(list, GL_COMPILE);

    qglColor(Qt::white);                      // Shorthand for glColor3f or glIndex

    glLineWidth(2.0);

    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0,  0.5, -0.4);
    glVertex3f( 1.0, -0.5, -0.4);
    glVertex3f(-1.0, -0.5, -0.4);
    glVertex3f(-1.0,  0.5, -0.4);
    glEnd();

    glBegin(GL_LINE_LOOP);
    glVertex3f( 1.0,  0.5, 0.4);
    glVertex3f( 1.0, -0.5, 0.4);
    glVertex3f(-1.0, -0.5, 0.4);
    glVertex3f(-1.0,  0.5, 0.4);
    glEnd();

    glBegin(GL_LINES);
    glVertex3f( 1.0,  0.5, -0.4);   glVertex3f( 1.0,  0.5, 0.4);
    glVertex3f( 1.0, -0.5, -0.4);   glVertex3f( 1.0, -0.5, 0.4);
    glVertex3f(-1.0, -0.5, -0.4);   glVertex3f(-1.0, -0.5, 0.4);
    glVertex3f(-1.0,  0.5, -0.4);   glVertex3f(-1.0,  0.5, 0.4);
    glEnd();

    glEndList();

    return list;
}

/*!
  Set the rotation angle of the object to \e degrees around the X axis.
*/

void GLBox::setXRotation(int degrees)
{
    m_xRot = (GLfloat)(degrees % 360);
    updateGL();
}

/*!
  Set the rotation angle of the object to \e degrees around the Y axis.
*/

void GLBox::setYRotation(int degrees)
{
    m_yRot = (GLfloat)(degrees % 360);
    updateGL();
}

/*!
  Set the rotation angle of the object to \e degrees around the Z axis.
*/

void GLBox::setZRotation(int degrees)
{
    m_zRot = (GLfloat)(degrees % 360);
    updateGL();
}

class ObjectSafetyImpl : public QAxAggregated,
                         public IObjectSafety
{
public:
    explicit ObjectSafetyImpl() {}

    long queryInterface(const QUuid &iid, void **iface)
    {
        *iface = nullptr;
        if (iid == IID_IObjectSafety)
            *iface = (IObjectSafety*)this;
        else
            return E_NOINTERFACE;

        AddRef();
        return S_OK;
    }

    QAXAGG_IUNKNOWN;

    HRESULT WINAPI GetInterfaceSafetyOptions(REFIID riid, DWORD *pdwSupportedOptions, DWORD *pdwEnabledOptions)
    {
        *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER;
        *pdwEnabledOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA | INTERFACESAFE_FOR_UNTRUSTED_CALLER;
        return S_OK;
    }

    HRESULT WINAPI SetInterfaceSafetyOptions(REFIID riid, DWORD pdwSupportedOptions, DWORD pdwEnabledOptions)
    {
        return S_OK;
    }
};

QAxAggregated *GLBox::createAggregate()
{
    return new ObjectSafetyImpl();
}
//glbox.h
#ifndef GLBOX_H
#define GLBOX_H

#include <QtOpenGL>
#include <QOpenGLFunctions_1_1>
#include <QAxBindable>

class GLBox : public QGLWidget,
              public QOpenGLFunctions_1_1,
              public QAxBindable
{
    Q_OBJECT
    Q_CLASSINFO("ClassID",     "{5fd9c22e-ed45-43fa-ba13-1530bb6b03e0}")
    Q_CLASSINFO("InterfaceID", "{33b051af-bb25-47cf-a390-5cfd2987d26a}")
    Q_CLASSINFO("EventsID",    "{8c996c29-eafa-46ac-a6f9-901951e765b5}")

public:
    explicit GLBox(QWidget *parent, const char *name = nullptr);
    virtual ~GLBox();
    QAxAggregated *createAggregate();

public slots:
    void                setXRotation(int degrees);
    void                setYRotation(int degrees);
    void                setZRotation(int degrees);

protected:
    void                initializeGL();
    void                paintGL();
    void                resizeGL(int w, int h);
    virtual GLuint      makeObject();

private:
    GLuint  m_object;
    GLfloat m_xRot;
    GLfloat m_yRot;
    GLfloat m_zRot;
    GLfloat m_scale;
};

#endif // GLBOX_H
//globjwin.cpp
#include "globjwin.h"
#include "glbox.h"
#include <QPushButton>
#include <QSlider>
#include <QLayout>
#include <QFrame>
#include <QMenuBar>
#include <QMenu>
#include <QApplication>

GLObjectWindow::GLObjectWindow(QWidget *parent)
    : QWidget(parent)
{
    // Create a menu
    QMenu *file = new QMenu(this);
    file->addAction(tr("Exit"), qApp, &QApplication::quit);

    // Create a menu bar
    QMenuBar *m = new QMenuBar(this);
    m->addMenu(file)->setText(tr("&File"));

    // Create a nice frame to put around the OpenGL widget
    QFrame *f = new QFrame(this);
    f->setFrameStyle(QFrame::Sunken | QFrame::Panel);
    f->setLineWidth(2);

    // Create our OpenGL widget
    GLBox *c = new GLBox(f, "glbox");

    // Create the three sliders; one for each rotation axis
    QSlider *x = new QSlider(Qt::Vertical, this);
    x->setMaximum(360);
    x->setPageStep(60);
    x->setTickPosition(QSlider::TicksLeft);
    connect(x, &QSlider::valueChanged, c, &GLBox::setXRotation);

    QSlider *y = new QSlider(Qt::Vertical, this);
    y->setMaximum(360);
    y->setPageStep(60);
    y->setTickPosition(QSlider::TicksLeft);
    connect(y, &QSlider::valueChanged, c, &GLBox::setYRotation);

    QSlider *z = new QSlider(Qt::Vertical, this);
    z->setMaximum(360);
    z->setPageStep(60);
    z->setTickPosition(QSlider::TicksLeft);
    connect(z, &QSlider::valueChanged, c, &GLBox::setZRotation);

    // Now that we have all the widgets, put them into a nice layout

    // Top level layout, puts the sliders to the left of the frame/GL widget
    QHBoxLayout *hlayout = new QHBoxLayout(this);

    // Put the sliders on top of each other
    QVBoxLayout *vlayout = new QVBoxLayout();
    vlayout->addWidget(x);
    vlayout->addWidget(y);
    vlayout->addWidget(z);

    // Put the GL widget inside the frame
    QHBoxLayout *flayout = new QHBoxLayout(f);
    flayout->setMargin(0);
    flayout->addWidget(c, 1);

    hlayout->setMenuBar(m);
    hlayout->addLayout(vlayout);
    hlayout->addWidget(f, 1);
}
//globjwin.h
#ifndef GLOBJWIN_H
#define GLOBJWIN_H

#include <qwidget.h>

class GLObjectWindow : public QWidget
{
    Q_OBJECT

public:
    explicit GLObjectWindow(QWidget *parent = nullptr);
};

#endif
//main.cpp
#include "globjwin.h"
#include "glbox.h"
#include <QApplication>
#include <QtOpenGL>
#include <QAxFactory>

QAXFACTORY_BEGIN(
    "{2c3c183a-eeda-41a4-896e-3d9c12c3577d}", // type library ID
    "{83e16271-6480-45d5-aaf1-3f40b7661ae4}") // application ID
    QAXCLASS(GLBox)
QAXFACTORY_END()

/*
  The main program is here.
*/

int main(int argc, char *argv[])
{
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication::setColorSpec(QApplication::CustomColor);
    QApplication a(argc,argv);

    if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL) {
        qWarning("This system does not support OpenGL. Exiting.");
        return -1;
    }

    if (!QAxFactory::isServer()) {
        GLObjectWindow w;
        w.resize(400, 350);
        w.show();
        return a.exec();
    }
    return a.exec();
}
#.pro
TEMPLATE = app
TARGET   = openglax

CONFIG += warn_off
QT += widgets axserver opengl

HEADERS  = glbox.h \
           globjwin.h
SOURCES  = glbox.cpp \
           globjwin.cpp \
           main.cpp

RC_FILE = opengl.rc
DEF_FILE = opengl.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/opengl
INSTALLS += target

5.7 Qutlook Example (ActiveQt)

该示例的项目文件如下所示:

#.pro
TEMPLATE = app
TARGET   = qutlook
QT += widgets axcontainer

TYPELIBS = $$system(dumpcpp -getfile {00062FFF-0000-0000-C000-000000000046})

isEmpty(TYPELIBS) {
    message("Microsoft Outlook type library not found!")
    REQUIRES += Outlook
} else {
    HEADERS  = addressview.h
    SOURCES  = addressview.cpp main.cpp
}

项目文件使用dumpcpp工具将MS Outlook类型库添加到项目中。 如果失败,那么生成的makefile将只打印一条错误消息,否则构建步骤现在将在类型库上运行dumpcpp工具,并生成一个头文件和一个cpp文件(在本例中为msoutl.h和msoutl.cpp) )声明并实现一个易于使用的API到Outlook对象。

//addressview.h
#ifndef ADDRESSVIEW_H
#define ADDRESSVIEW_H

#include <QWidget>

class AddressBookModel;
class QLineEdit;
class QModelIndex;
class QPushButton;
class QTreeView;

class AddressView : public QWidget
{
    Q_OBJECT

public:
    explicit AddressView(QWidget *parent = nullptr);

protected slots:
    void addEntry();
    void changeEntry();
    void itemSelected(const QModelIndex &index);

    void updateOutlook();

protected:
    AddressBookModel *model;

    QTreeView *m_treeView;
    QPushButton *m_addButton;
    QPushButton *m_changeButton;
    QLineEdit *m_firstName;
    QLineEdit *m_lastName;
    QLineEdit *m_address;
    QLineEdit *m_email;
};

#endif // ADDRESSVIEW_H

AddressView类是用户界面的QWidget子类。 QTreeView小部件将显示模型提供的Outlook的Contact文件夹的内容。

//addressview.cpp
#include "addressview.h"
#include "msoutl.h"
#include <QtWidgets>

class AddressBookModel : public QAbstractListModel
{
public:
    explicit AddressBookModel(AddressView *parent);
    virtual ~AddressBookModel();

    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
    QVariant data(const QModelIndex &index, int role) const;

    void changeItem(const QModelIndex &index, const QString &firstName, const QString &lastName, const QString &address, const QString &email);
    void addItem(const QString &firstName, const QString &lastName, const QString &address, const QString &email);
    void update();

private:
    Outlook::Application outlook;
    Outlook::Items * contactItems;

    mutable QHash<QModelIndex, QStringList> cache;
};

AddressBookModel类是一个QAbstractListModel子类,它使用QHash进行缓存,直接与Outlook通信。

//addressview.cpp
AddressBookModel::AddressBookModel(AddressView *parent)
: QAbstractListModel(parent)
{
    if (!outlook.isNull()) {
        Outlook::NameSpace session(outlook.Session());
        session.Logon();
        Outlook::MAPIFolder *folder = session.GetDefaultFolder(Outlook::olFolderContacts);
        contactItems = new Outlook::Items(folder->Items());
        connect(contactItems, SIGNAL(ItemAdd(IDispatch*)), parent, SLOT(updateOutlook()));
        connect(contactItems, SIGNAL(ItemChange(IDispatch*)), parent, SLOT(updateOutlook()));
        connect(contactItems, SIGNAL(ItemRemove()), parent, SLOT(updateOutlook()));

        delete folder;
    }
}

构造函数初始化Outlook。 Outlook提供的有关内容更改的各种信号连接到updateOutlook()槽。

//addressview.cpp
AddressBookModel::~AddressBookModel()
{
    delete contactItems;

    if (!outlook.isNull())
        Outlook::NameSpace(outlook.Session()).Logoff();
}

从析构函数中注销会话。


//addressview.cpp
int AddressBookModel::rowCount(const QModelIndex &) const
{
    return contactItems ? contactItems->Count() : 0;
}

int AddressBookModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 4;
}

rowCount()实现返回Outlook报告的条目数。 实现columnCountheaderData以在树视图中显示四列。

//addressview.cpp
QVariant AddressBookModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    switch (section) {
    case 0:
        return tr("First Name");
    case 1:
        return tr("Last Name");
    case 2:
        return tr("Address");
    case 3:
        return tr("Email");
    default:
        break;
    }

    return QVariant();
}

headerData()实现返回硬编码字符串。

//addressview.cpp
QVariant AddressBookModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || role != Qt::DisplayRole)
        return QVariant();

    QStringList data;
    if (cache.contains(index)) {
        data = cache.value(index);
    } else {
        Outlook::ContactItem contact(contactItems->Item(index.row() + 1));
        data << contact.FirstName() << contact.LastName() << contact.HomeAddress() << contact.Email1Address();
        cache.insert(index, data);
    }

    if (index.column() < data.count())
        return data.at(index.column());

    return QVariant();
}

data()实现是模型的核心。 如果请求的数据在缓存中,则使用缓存值,否则从Outlook获取数据。

//addressview.cpp
void AddressBookModel::changeItem(const QModelIndex &index, const QString &firstName, const QString &lastName, const QString &address, const QString &email)
{
    Outlook::ContactItem item(contactItems->Item(index.row() + 1));

    item.SetFirstName(firstName);
    item.SetLastName(lastName);
    item.SetHomeAddress(address);
    item.SetEmail1Address(email);

    item.Save();

    cache.take(index);
}

当用户使用用户界面更改当前条目时,将调用changeItem()槽。 使用Outlook API访问Outlook项目,并使用属性设置器进行修改。 最后,该项目将保存到Outlook,并从缓存中删除。 请注意,该模型不会发出数据更改视图的信号,因为Outlook将自行发出信号。

//addressview.cpp
void AddressBookModel::addItem(const QString &firstName, const QString &lastName, const QString &address, const QString &email)
{
    Outlook::ContactItem item(outlook.CreateItem(Outlook::olContactItem));
    if (!item.isNull()) {
        item.SetFirstName(firstName);
        item.SetLastName(lastName);
        item.SetHomeAddress(address);
        item.SetEmail1Address(email);

        item.Save();
    }
}

addItem()槽调用Outlook的CreateItem方法来创建新的联系人项目,将新项目的属性设置为用户输入的值并保存项目。

//addressview.cpp
void AddressBookModel::update()
{
    beginResetModel();
    cache.clear();
    endResetModel();
}

update()槽清除缓存,并发出reset()信号以通知视图需要重绘内容的数据更改。

//addressview.cpp
AddressView::AddressView(QWidget *parent)
: QWidget(parent)
{
    QGridLayout *mainGrid = new QGridLayout(this);

    QLabel *firstNameLabel = new QLabel(tr("First &Name"), this);
    firstNameLabel->resize(firstNameLabel->sizeHint());
    mainGrid->addWidget(firstNameLabel, 0, 0);

    QLabel *lastNameLabel = new QLabel(tr("&Last Name"), this);
    lastNameLabel->resize(lastNameLabel->sizeHint());
    mainGrid->addWidget(lastNameLabel, 0, 1);

    QLabel *addressLabel = new QLabel(tr("Add&ress"), this);
    addressLabel->resize(addressLabel->sizeHint());
    mainGrid->addWidget(addressLabel, 0, 2);

    QLabel *emailLabel = new QLabel(tr("&E-Mail"), this);
    emailLabel->resize(emailLabel->sizeHint());
    mainGrid->addWidget(emailLabel, 0, 3);

    m_addButton = new QPushButton(tr("A&dd"), this);
    m_addButton->resize(m_addButton->sizeHint());
    mainGrid->addWidget(m_addButton, 0, 4);
    connect(m_addButton, &QPushButton::clicked, this, &AddressView::addEntry);

    m_firstName = new QLineEdit(this);
    m_firstName->resize(m_firstName->sizeHint());
    mainGrid->addWidget(m_firstName, 1, 0);
    firstNameLabel->setBuddy(m_firstName);

    m_lastName = new QLineEdit(this);
    m_lastName->resize(m_lastName->sizeHint());
    mainGrid->addWidget(m_lastName, 1, 1);
    lastNameLabel->setBuddy(m_lastName);

    m_address = new QLineEdit(this);
    m_address->resize(m_address->sizeHint());
    mainGrid->addWidget(m_address, 1, 2);
    addressLabel->setBuddy(m_address);

    m_email = new QLineEdit(this);
    m_email->resize(m_email->sizeHint());
    mainGrid->addWidget(m_email, 1, 3);
    emailLabel->setBuddy(m_email);

    m_changeButton = new QPushButton(tr("&Change"), this);
    m_changeButton->resize(m_changeButton->sizeHint());
    mainGrid->addWidget(m_changeButton, 1, 4);
    connect(m_changeButton, &QPushButton::clicked, this, &AddressView::changeEntry);

    m_treeView = new QTreeView(this);
    m_treeView->setSelectionMode(QTreeView::SingleSelection);
    m_treeView->setRootIsDecorated(false);

    model = new AddressBookModel(this);
    m_treeView->setModel(model);

    connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AddressView::itemSelected);

    mainGrid->addWidget(m_treeView, 2, 0, 1, 5);
}

void AddressView::updateOutlook()
{
    model->update();
}

void AddressView::addEntry()
{
    if (!m_firstName->text().isEmpty() || !m_lastName->text().isEmpty() ||
         !m_address->text().isEmpty() || !m_email->text().isEmpty()) {
        model->addItem(m_firstName->text(), m_lastName->text(), m_address->text(), m_email->text());
    }

    m_firstName->clear();
    m_lastName->clear();
    m_address->clear();
    m_email->clear();
}

void AddressView::changeEntry()
{
    QModelIndex current = m_treeView->currentIndex();

    if (current.isValid())
        model->changeItem(current, m_firstName->text(), m_lastName->text(), m_address->text(), m_email->text());
}

void AddressView::itemSelected(const QModelIndex &index)
{
    if (!index.isValid())
        return;

    QAbstractItemModel *model = m_treeView->model();
    m_firstName->setText(model->data(model->index(index.row(), 0)).toString());
    m_lastName->setText(model->data(model->index(index.row(), 1)).toString());
    m_address->setText(model->data(model->index(index.row(), 2)).toString());
    m_email->setText(model->data(model->index(index.row(), 3)).toString());
}

文件的其余部分仅使用Qt API实现用户界面,即不直接与Outlook通信。

//main.cpp
#include "addressview.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);    
    AddressView view;
    view.setWindowTitle(QObject::tr("Qt Example - Looking at Outlook"));
    view.show();    
    return a.exec();
}

main()入口点函数最终实例化用户界面并进入事件循环。

要构建示例,您必须首先构建QAxContainer库。 然后在examples/activeqt/qutlook中运行make工具并运行生成的qutlook.exe。

5.8 Simple Example (ActiveQt)

Simple示例演示了如何使用QAxBindable::requestPropertyChange()QAxBindable::propertyChanged(),以及通过QAXFACTORY_BEGIN()QAXCLASS()QAXFACTORY_END()宏使用QAxFactory。

此示例中的ActiveX控件是一个带有QSliderQLCDNumberQLineEdit的QWidget。 它提供了一个信号/槽/属性接口,用于更改滑块和行编辑的值,并通知任何属性更改。

此示例的ActiveX的Qt实现是

//main.cpp
#include <QAxBindable>
#include <QAxFactory>
#include <QApplication>
#include <QLayout>
#include <QSlider>
#include <QLCDNumber>
#include <QLineEdit>
#include <QMessageBox>
class QSimpleAX : public QWidget, public QAxBindable
{
    Q_OBJECT
    Q_CLASSINFO("ClassID",     "{DF16845C-92CD-4AAB-A982-EB9840E74669}")
    Q_CLASSINFO("InterfaceID", "{616F620B-91C5-4410-A74E-6B81C76FFFE0}")
    Q_CLASSINFO("EventsID",    "{E1816BBA-BF5D-4A31-9855-D6BA432055FF}")
    Q_PROPERTY(QString text READ text WRITE setText)
    Q_PROPERTY(int value READ value WRITE setValue)
public:
    explicit QSimpleAX(QWidget *parent = nullptr)
    : QWidget(parent)
    {
        QVBoxLayout *vbox = new QVBoxLayout(this);

        m_slider = new QSlider(Qt::Horizontal, this);
        m_LCD = new QLCDNumber(3, this);
        m_edit = new QLineEdit(this);

        connect(m_slider, &QAbstractSlider::valueChanged, this, &QSimpleAX::setValue);
        connect(m_edit, &QLineEdit::textChanged, this, &QSimpleAX::setText);

        vbox->addWidget(m_slider);
        vbox->addWidget(m_LCD);
        vbox->addWidget(m_edit);
    }

    QString text() const
    {
        return m_edit->text();
    }

    int value() const
    {
        return m_slider->value();
    }

signals:
    void someSignal();
    void valueChanged(int);
    void textChanged(const QString&);

public slots:
    void setText(const QString &string)
    {
        if (!requestPropertyChange("text"))
            return;

        QSignalBlocker blocker(m_edit);
        m_edit->setText(string);
        emit someSignal();
        emit textChanged(string);

        propertyChanged("text");
    }

    void about()
    {
        QMessageBox::information( this, "About QSimpleAX", "This is a Qt widget, and        this slot has been\n"
        "called through ActiveX/OLE automation!" );
    }

    void setValue(int i)
    {
        if (!requestPropertyChange("value"))
            return;

        QSignalBlocker blocker(m_slider);
        m_slider->setValue(i);
        m_LCD->display(i);
        emit valueChanged(i);

        propertyChanged("value");
    }

private:
    QSlider *m_slider;
    QLCDNumber *m_LCD;
    QLineEdit *m_edit;
};

使用默认的QAxFactory导出控件

//main.cpp
#include "main.moc"
QAXFACTORY_BEGIN(
    "{EC08F8FC-2754-47AB-8EFE-56A54057F34E}", // type library ID
    "{A095BA0C-224F-4933-A458-2DD7F6B85D8F}") // application ID
    QAXCLASS(QSimpleAX)
QAXFACTORY_END()
#.pro
include(../shared.pri)

TEMPLATE = app
TARGET   = simpleax

CONFIG += warn_off
QT += widgets axserver

SOURCES  = main.cpp
RC_FILE  = simple.rc
DEF_FILE = simple.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/simple
INSTALLS += target

要构建示例,您必须首先构建QAxServer库。 然后在examples/activeqt/simple中运行qmake和make工具。

演示要求WebBrowser支持ActiveX控件,并启用脚本。

使用标记嵌入简单的ActiveX控件。

//html
<object ID="QSimpleAX" CLASSID="CLSID:DF16845C-92CD-4AAB-A982-EB9840E74669"
CODEBASE="http://qt.nokia.com/demos/simpleax.cab">
    <PARAM NAME="text" VALUE="A simple control" />
    <PARAM NAME="value" VALUE="1" />
[Object not available! Did you forget to build and register the server?]
</object>

A simple HTML button is connected to the ActiveQt’s about() slot.

//html
<FORM>
    <INPUT TYPE="BUTTON" VALUE="About..." onClick="QSimpleAX.about()" />
</FORM>

A second ActiveX control - the standard Calendar Control - is instantiated

//html
<object ID="Calendar" CLASSID="CLSID:8E27C92B-1264-101C-8A2F-040224009C02">
[Standard Calendar control not available!]
    <PARAM NAME="day" VALUE="1" />
</object>

Events from the ActiveX controls are handled using both Visual Basic Script and JavaScript.

//html
<SCRIPT LANGUAGE="VBScript">
Sub Calendar_Click()
    MsgBox( "Calendar Clicked!" )
End Sub

Sub QSimpleAX_TextChanged( str )
    document.title = str
End Sub
</SCRIPT>

<SCRIPT LANGUAGE="JavaScript">
function QSimpleAX::ValueChanged( Newvalue )
{
    Calendar.Day = Newvalue;
}
</SCRIPT>

5.9 Web Browser Example (ActiveQt)

该代码演示了Qt应用程序如何使用信号,槽和dynamicCall()函数与嵌入式ActiveX控件进行通信。

//main.cpp
class MainWindow : public QMainWindow, public Ui::MainWindow
{
    Q_OBJECT
public:
    explicit MainWindow();
    virtual ~MainWindow();

public slots:
    void navigate(const QString &address);
    void on_WebBrowser_TitleChange(const QString &title);
    void on_WebBrowser_ProgressChange(int a, int b);
    void on_WebBrowser_CommandStateChange(int cmd, bool on);
    void on_WebBrowser_BeforeNavigate();
    void on_WebBrowser_NavigateComplete(const QString &address);

    void on_actionGo_triggered();
    void on_actionNewWindow_triggered();
    void on_actionAddBookmark_triggered();
    void on_actionAbout_triggered();
    void on_actionAboutQt_triggered();
    void on_actionFileClose_triggered();

private:
    inline const QString address() const
        { return m_addressEdit->text().trimmed(); }
    QList<Location> bookmarks() const;
    QAction *addLocation(const Location &location, QMenu *menu);
    inline void addBookmark(const Location &location)
        { m_bookmarkActions << addLocation(location, BookmarksMenu); }

    QProgressBar *m_progressBar;
    QLineEdit *m_addressEdit;
    QList<QAction *> m_bookmarkActions;
    QList<QAction *> m_historyActions;
    QSignalMapper m_locationActionMapper;
};

MainWindow类使用Qt Designer生成的Ui::MainWindow类声明基于QMainWindow的用户界面。 实现了许多槽来处理来自各种用户界面元素的事件,包括WebBrowser对象,它是托管Microsoft Web浏览器控件QAxWidget。

//main.cpp
MainWindow::MainWindow()
{
    setupUi(this);

    m_addressEdit = new QLineEdit;
    tbAddress->insertWidget(actionGo, new QLabel(tr("Address")));
    tbAddress->insertWidget(actionGo, m_addressEdit);

    connect(m_addressEdit, SIGNAL(returnPressed()), actionGo, SLOT(trigger()));

    connect(actionBack, SIGNAL(triggered()), WebBrowser, SLOT(GoBack()));
    connect(actionForward, SIGNAL(triggered()), WebBrowser, SLOT(GoForward()));
    connect(actionStop, SIGNAL(triggered()), WebBrowser, SLOT(Stop()));
    connect(actionRefresh, SIGNAL(triggered()), WebBrowser, SLOT(Refresh()));
    connect(actionHome, SIGNAL(triggered()), WebBrowser, SLOT(GoHome()));
    connect(actionSearch, SIGNAL(triggered()), WebBrowser, SLOT(GoSearch()));

    m_progressBar = new QProgressBar(statusBar());
    m_progressBar->setTextVisible(false);
    m_progressBar->hide();
    statusBar()->addPermanentWidget(m_progressBar);

    connect(&m_locationActionMapper, QOverload<const QString &>::of(&QSignalMapper::mapped), this, &MainWindow::navigate);

    QSettings settings(QSettings::IniFormat, QSettings::UserScope,
                       QCoreApplication::organizationName(), QCoreApplication::applicationName());
    const QByteArray restoredGeometry = settings.value(QLatin1String(geometryKey)).toByteArray();
    if (restoredGeometry.isEmpty() || !restoreGeometry(restoredGeometry)) {
        const QRect availableGeometry = QApplication::desktop()->availableGeometry(this);
        const QSize size = (availableGeometry.size() * 4) / 5;
        resize(size);
        move(availableGeometry.center() - QPoint(size.width(), size.height()) / 2);
    }
    const QString restoredVersion = settings.value(QLatin1String(versionKey)).toString();
    QList<Location> bookmarks = readBookMarks(settings);
    if (bookmarks.isEmpty() || restoredVersion.isEmpty())
        bookmarks = defaultBookmarks();
    for (const Location &bookmark : qAsConst(bookmarks))
        addBookmark(bookmark);
}

构造函数初始化用户界面,在状态栏上安装进度条,然后加载书签。

//main.cpp
void MainWindow::on_WebBrowser_TitleChange(const QString &title)
{
    // This is called multiple times after NavigateComplete().
    // Add new URLs to history here.
    setWindowTitle(tr("Qt WebBrowser - ") + title);
    const QString currentAddress = address();
    const QString historyAddress = m_historyActions.isEmpty() ?
        QString() : locationFromAction(m_historyActions.last()).address;
    if (currentAddress.isEmpty() || currentAddress == QStringLiteral("about:blank") || currentAddress == historyAddress)
        return;
    m_historyActions << addLocation(Location(title, currentAddress), HistoryMenu);
    if (m_historyActions.size() > 10)
        delete m_historyActions.takeFirst();
}

void MainWindow::on_WebBrowser_ProgressChange(int a, int b)
{
    if (a <= 0 || b <= 0) {
        m_progressBar->hide();
        return;
    }
    m_progressBar->setRange(0, b);
    m_progressBar->setValue(a);
    m_progressBar->show();
}

void MainWindow::on_WebBrowser_CommandStateChange(int cmd, bool on)
{
    switch (cmd) {
    case 1:
        actionForward->setEnabled(on);
        break;
    case 2:
        actionBack->setEnabled(on);
        break;
    }
}

void MainWindow::on_WebBrowser_BeforeNavigate()
{
    actionStop->setEnabled(true);
}

void MainWindow::on_WebBrowser_NavigateComplete(const QString &url)
{
    QSignalBlocker blocker(m_addressEdit);
    actionStop->setEnabled(false);
    m_addressEdit->setText(url);
}

void MainWindow::on_actionGo_triggered()
{
    navigate(address());
}

不同的槽处理WebBrowser对象发出的信号。

在Qt Designer中已经完成了不需要任何编码的连接,将后退动作连接到GoBack()插槽。

//main.cpp
void MainWindow::on_actionGo_triggered()
{
    navigate(address());
}


void MainWindow::navigate(const QString &url)
{
    WebBrowser->dynamicCall("Navigate(const QString&)", url);
}

void MainWindow::on_actionNewWindow_triggered()
{
    MainWindow *window = new MainWindow;
    window->show();
    if (m_addressEdit->text().isEmpty())
        return;
    window->m_addressEdit->setText(m_addressEdit->text());
    window->actionStop->setEnabled(true);
    window->on_actionGo_triggered();
}

void MainWindow::on_actionAbout_triggered()
{
    QMessageBox::about(this, tr("About WebBrowser"),
                       tr("This Example has been created using the ActiveQt integration into Qt Designer.\n"
                       "It demonstrates the use of QAxWidget to embed the Internet Explorer ActiveX\n"
                       "control into a Qt application."));
}

void MainWindow::on_actionAboutQt_triggered()
{
    QMessageBox::aboutQt(this, tr("About Qt"));
}

void MainWindow::on_actionFileClose_triggered()
{
    close();
}

#include "main.moc"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QCoreApplication::setApplicationVersion(QT_VERSION_STR);
    QCoreApplication::setApplicationName(QStringLiteral("Active Qt Web Browser"));
    QCoreApplication::setOrganizationName(QStringLiteral("QtProject"));
    MainWindow w;
    const auto &arguments = QCoreApplication::arguments();
    const QString url = arguments.value(1, QString::fromLatin1(qtUrl));
    w.navigate(url);
    w.show();
    return a.exec();
}

其余的实现与ActiveQt无关 - 操作由不同的插槽处理,入口点函数使用标准Qt API启动应用程序。

要构建示例,您必须首先构建QAxContainer库。 然后在examples/activeqt/webbrowser中运行make工具并运行生成的webbrowser.exe。

5.10 Wrapper Example (ActiveQt)

//main.cpp
#include <QAxFactory>
#include <QCheckBox>
#include <QRadioButton>
#include <QPushButton>
#include <QToolButton>
#include <QPixmap>
#include <functional>
class ActiveQtFactory : public QAxFactory
{
public:
    ActiveQtFactory(const QUuid &lib, const QUuid &app)
        : QAxFactory(lib, app)
    {}

    QStringList featureList() const
    {
        return m_activeElements.keys();
    }

    QObject *createObject(const QString &key)
    {
        auto it = m_activeElements.find(key);
        if (it != m_activeElements.end())
            return it->create();
        return nullptr;
    }

    const QMetaObject *metaObject(const QString &key) const
    {
        auto it = m_activeElements.find(key);
        if (it != m_activeElements.end())
            return it->metaObject;
        return nullptr;
    }

    QUuid classID(const QString &key) const
    {
        auto it = m_activeElements.find(key);
        if (it != m_activeElements.end())
            return it->classID;
        return QUuid();
    }

    QUuid interfaceID(const QString &key) const
    {
        auto it = m_activeElements.find(key);
        if (it != m_activeElements.end())
            return it->interfaceID;
        return QUuid();
    }

    QUuid eventsID(const QString &key) const
    {
        auto it = m_activeElements.find(key);
        if (it != m_activeElements.end())
            return it->eventsID;
        return QUuid();
    }

private:

    struct ActiveElement {
       QUuid classID;
       QUuid interfaceID;
       QUuid eventsID;
       const QMetaObject *metaObject;
       std::function<QObject *()> create;
    };

    const QHash<QString, ActiveElement> m_activeElements {
        {
            QStringLiteral("QCheckBox"), {
                QUuid("{6E795DE9-872D-43CF-A831-496EF9D86C68}"),
                QUuid("{4FD39DD7-2DE0-43C1-A8C2-27C51A052810}"),
                QUuid("{FDB6FFBE-56A3-4E90-8F4D-198488418B3A}"),
                &QCheckBox::staticMetaObject,
                []() { return new QCheckBox; }
            }
        },
        {
            QStringLiteral("QRadioButton"), {
                QUuid("{AFCF78C8-446C-409A-93B3-BA2959039189}"),
                QUuid("{7CC8AE30-206C-48A3-A009-B0A088026C2F}"),
                QUuid("{73EE4860-684C-4A66-BF63-9B9EFFA0CBE5}"),
                &QRadioButton::staticMetaObject,
                []() { return new QRadioButton; }
            }
        },
        {
            QStringLiteral("QPushButton"), {
                QUuid("{2B262458-A4B6-468B-B7D4-CF5FEE0A7092}"),
                QUuid("{06831CC9-59B6-436A-9578-6D53E5AD03D3}"),
                QUuid("{3CC3F17F-EA59-4B58-BBD3-842D467131DD}"),
                &QPushButton::staticMetaObject,
                []() { return new QPushButton; }
            }
        },
        {
            QStringLiteral("QToolButton"), {
                QUuid("{7c0ffe7a-60c3-4666-bde2-5cf2b54390a1}"),
                QUuid("{6726080f-d63d-4950-a366-9bf33e5cdf84}"),
                QUuid("{f4d421fd-4ead-4fd9-8a25-440766939639}"),
                &QToolButton::staticMetaObject,
                []() { return new QToolButton; }
            }
        },
    };

};

工厂实现返回支持的控件列表,根据请求创建控件,并提供有关每个控件的COM类和接口的唯一ID的信息。

//main.cpp
QAXFACTORY_EXPORT(ActiveQtFactory, "{3B756301-0075-4E40-8BE8-5A81DE2426B7}", "{AB068077-4924-406a-BBAF-42D91C8727DD}")
#.pro
include(../shared.pri)

TEMPLATE = lib
TARGET   = wrapperax

CONFIG += warn_off dll
QT += widgets axserver

SOURCES  = main.cpp
RC_FILE  = wrapperax.rc
DEF_FILE = wrapperax.def

# install
target.path = $$[QT_INSTALL_EXAMPLES]/activeqt/wrapper
INSTALLS += target

使用QAXFACTORY_EXPORT宏导出工厂。

要构建示例,您必须首先构建QAxServer库。 然后在examples/activeqt/wrapper中运行qmake和make工具。

演示需要支持ActiveX控件的Web浏览器,并启用脚本。

//html
<SCRIPT LANGUAGE="VBScript">
Sub ToolButton_Clicked()
    RadioButton.text = InputBox( "Enter something", "Wrapper Demo" )
End Sub

Sub PushButton_clicked()
    MsgBox( "Thank you!" )
End Sub

Sub CheckBox_toggled( state )
    if state = 0 then
        CheckBox.text = "Check me!"
    else
        CheckBox.text = "Uncheck me!"
    end if
End Sub
</SCRIPT>
<p />
A QPushButton:<br />
<object ID="PushButton" CLASSID="CLSID:2B262458-A4B6-468B-B7D4-CF5FEE0A7092"
CODEBASE="http://qt.nokia.com/demos/wrapperax.cab">
    <PARAM NAME="text" VALUE="Click me!" />
[Object not available! Did you forget to build and register the server?]
</object><br />

<p />
A QCheckBox:<br />
<object ID="CheckBox" CLASSID="CLSID:6E795de9-872d-43cf-a831-496ef9d86c68"
CODEBASE="http://qt.nokia.com/demos/wrapperax.cab">
    <PARAM NAME="text" VALUE="Check me!" />
[Object not available! Did you forget to build and register the server?]
</object><br />

<p />
A QToolButton:<br />
<object ID="ToolButton" CLASSID="CLSID:7c0ffe7a-60c3-4666-bde2-5cf2b54390a1"
CODEBASE="http://qt.nokia.com/demos/wrapperax.cab">
[Object not available! Did you forget to build and register the server?]
</object><br />

<p />
A QRadioButton:<br />
<object ID="RadioButton" CLASSID="CLSID:afcf78c8-446c-409a-93b3-ba2959039189"
CODEBASE="http://qt.nokia.com/demos/wrapperax.cab">
    <PARAM NAME="text" VALUE="Tune me!" />
[Object not available! Did you forget to build and register the server?]
</object><br />

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/82499038