在 Qt 中构建 ActiveX 服务器
QAxServer 模块是ActiveQt框架的一部分。它由三个类组成:
- QAxFactory 定义了一个用于创建 COM 对象的工厂。
- QAxBindable 提供 Qt Widgets 控件与 COM 对象之间的接口。
- QAxAggregated 可通过继承该类来实现额外的 COM 接口。
提供了若干 ActiveX 控件和 COM 对象的示例实现。
使用该库
要使用QAxServer 库将标准Qt应用程序转换为COM服务器,必须在.pro 文件中的QT变量中添加axserver 。
通过如下所示的.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_register配置来跳过。
若要跳过后处理步骤,也请设置qaxserver_no_postlink 配置。
此外,您还可以通过VERSION 变量指定版本号,例如
TEMPLATE = lib
VERSION = 2.5
...注册时,所指定的版本号将作为类型库和服务器的版本号使用。
进程外与进程内
您的 COM 服务器是作为独立可执行文件运行,还是作为客户端进程中的共享库运行,主要取决于您要在服务器中提供的 COM 对象的类型。
可执行服务器具有能够作为独立应用程序运行的优势,但会给 COM 客户端与 COM 对象之间的通信增加相当大的开销。如果控件存在编程错误,则只有运行该控件的服务器进程会崩溃,而客户端应用程序很可能继续运行。并非所有 COM 客户端都支持可执行服务器。
进程内服务器通常体积更小,启动速度更快。 客户端与服务器之间的通信是通过虚函数调用直接进行的,因此不会产生远程过程调用所需的开销。但是,如果服务器崩溃,客户端应用程序很可能也会随之崩溃,而且并非所有功能都适用于进程内服务器(例如,在 COM 的运行对象表中注册)。
这两种服务器类型均可将 Qt 作为共享库使用,或将其静态链接到服务器二进制文件中。
构建后步骤中的常见错误
为了使 ActiveQt 特有的后处理步骤正常工作,服务器必须满足以下要求:
- 所有公开的控件仅凭一个QApplication 实例即可创建
- 服务器的初始链接必须包含一个临时类型库资源
- 运行服务器所需的所有依赖项均位于系统路径中(或位于调用环境所使用的路径中;请注意,Visual Studio 拥有自己的环境变量集,这些变量列在“工具|选项|目录”对话框中)。
如果未满足这些要求,则可能会发生以下一项或多项错误:
服务器可执行文件崩溃
要生成 IDL,必须对作为 ActiveX 控件暴露的小部件进行实例化(即调用构造函数)。此时,除了QApplication 对象之外,不存在其他任何对象。您的小部件构造函数不得依赖于其他已创建的对象,例如,应检查空指针。
要调试服务器,请使用 -dumpidl outputfile 参数运行它,并检查崩溃位置。
请注意,此时不会调用控件的任何函数。
服务器可执行文件不是有效的 Win32 应用程序
附加类型库导致服务器二进制文件损坏。这是 Windows 中的一个错误,且仅在发布版构建中发生。
第一个链接步骤必须将一个虚拟类型库链接到可执行文件中,该类型库随后可由 idc 替换。请按照示例所示,向项目中添加一个包含类型库的资源文件。
“无法找到 DLL”
构建系统需要运行服务器可执行文件来生成接口定义并注册服务器。如果服务器所链接的动态链接库不在路径中,此操作可能会失败(例如,Visual Studio 会根据“目录”选项中指定的环境设置来调用服务器)。 请确保服务器所需的所有 DLL 和插件都位于错误消息框中显示的路径所列的目录内(另请参阅《Windows 部署工具》)。
“无法打开文件...”
当最后一个客户端停止使用时,ActiveX 服务器可能无法正常关闭。应用程序通常需要大约两秒钟才能终止,但您可能需要使用任务管理器强制终止该进程(例如,当客户端未正确释放控件时)。
无法实例化控件
在这种情况下,以管理员身份注册服务器可能会有所帮助。
控件的实现
要在 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 标识符。ClassID 和InterfaceID 是必需的,而EventsID 仅在对象具有信号时才需要。要生成这些标识符,请使用uuidgen 或guidgen 等系统工具。
您可以为每个类指定其他属性;详情请参阅“类信息和调优”。
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 数据类型之间进行转换。
数据类型
支持用于属性的 Qt 数据类型包括:
| Qt 数据类型 | COM 属性 |
|---|---|
| bool | VARIANT_BOOL |
| QString | BSTR |
| int | int |
| uint | 无符号整数 |
| double | double |
| qlonglong | CY |
| qulonglong | CY |
| QColor | OLE_COLOR |
| QDate | DATE |
| QDateTime | DATE |
| QTime | DATE |
| QFont | IFontDisp* |
| QPixmap | IPictureDisp* |
| QVariant | VARIANT |
| QVariantList (与QList<QVariant> 相同) | SAFEARRAY(VARIANT) |
| QStringList | SAFEARRAY(BSTR) |
| QByteArray | SAFEARRAY(BYTE) |
| QRect | 用户定义类型 |
| QSize | 用户定义类型 |
| QPoint | 用户定义类型 |
信号和槽的参数支持以下 Qt 数据类型:
| Qt 数据类型 | COM 参数 |
|---|---|
| bool | [in] VARIANT_BOOL |
| bool& | [in, out] VARIANT_BOOL* |
| QString, constQString& | [in] BSTR |
| QString& | [in, out] BSTR* |
| QString& | [in, out] BSTR* |
| int | [in] int |
| int& | [in, out] int |
| uint | [in] 无符号整型 |
| uint& | [in, out] 无符号整型指针* |
| double | [in] double |
| double& | [in, out] double* |
| QColor, constQColor& | [in] OLE_COLOR |
| QColor& | [in, out] OLE_COLOR* |
| QDate, constQDate& | [in] DATE |
| QDate& | [in, out] DATE* |
| QDateTime, constQDateTime& | [in] DATE |
| QDateTime& | [in, out] DATE* |
| QFont, constQFont& | [in] IFontDisp* |
| QFont& | [in, out] IFontDisp** |
| QPixmap, constQPixmap& | [in] IPictureDisp* |
| QPixmap& | [in, out] IPictureDisp** |
| QList<QVariant>, constQList<QVariant>& | [in] SAFEARRAY(VARIANT) |
| QList<QVariant>& | [in, out] SAFEARRAY(VARIANT)* |
| QStringList, constQStringList& | [in] SAFEARRAY(BSTR) |
| QStringList& | [in, out] SAFEARRAY(BSTR)* |
| QByteArray, constQByteArray& | [in] SAFEARRAY(BYTE) |
| QByteArray& | [in, out] SAFEARRAY(BYTE)* |
| QObject* | [in] IDispatch* |
| QRect& | [in, out] structQRect (用户定义) |
| QSize& | [in, out] structQSize (用户定义) |
| QPoint& | [in, out] structQPoint (用户定义) |
此外还支持导出的枚举和标志(参见Q_ENUM() 和Q_FLAG())。输入参数的类型也可作为返回值使用。
对于使用任何其他数据类型的参数的属性、信号和槽,ActiveQt 框架将予以忽略。
子对象
COM 对象可以有多个子对象,这些子对象可以代表 COM 对象的一个子元素。例如,一个代表多文档电子表格应用程序的 COM 对象可以为每个电子表格提供一个子对象。
只要QAxFactory 已识别该类型,任何QObject 子类均可作为ActiveX中子对象的类型。随后,该类型即可用于属性,或作为槽的返回类型或参数。
属性通知
要使属性对 ActiveX 客户端可绑定,请使用从QAxBindable 类继承的多重继承:
#include <QAxBindable>
#include <QWidget>
class MyActiveX : public QWidget, public QAxBindable
{
Q_OBJECT在实现属性写入函数时,请使用QAxBindable 类的requestPropertyChange()和propertyChanged()函数,以允许ActiveX客户端绑定到控件属性。
提供控件
要使 COM 服务器可供 COM 系统使用,必须使用五个唯一标识符将其注册到系统注册表中。这些标识符由诸如guidgen 或uuidgen 等工具提供。注册信息使 COM 能够定位提供所请求 ActiveX 控件的二进制文件,对控件进行远程过程调用(RPC)的 marshall 处理,并读取控件所暴露的方法和属性的类型信息。
为了在客户端请求时创建 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 class documentation 说明了如何使用此宏,以及如何实现和使用自定义工厂。
对于进程外可执行服务器,您可以实现一个 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 提供了 main 函数的默认实现。该默认实现会调用QAxFactory::startServer(),创建一个QApplication 实例,并调用 exec()。
要构建 ActiveX 服务器可执行文件,请运行qmake 以生成 makefile,然后像处理其他 Qt 应用程序一样使用编译器的 make 工具。make 过程还会通过使用-regserver 命令行选项调用生成的可执行文件,从而将控件注册到系统注册表中。
如果 ActiveX 服务器是可执行文件,则支持以下命令行选项:
| 选项 | 结果 |
|---|---|
-regserver | 将服务器注册到系统注册表中 |
-regserverperuser | 将服务器注册到当前用户的系统注册表中(自 5.14 起) |
-unregserver | 从系统注册表中注销服务器 |
-unregserverperuser | 从当前用户的系统注册表中注销服务器(自 5.14 起) |
-activex | 以 ActiveX 服务器身份启动应用程序 |
-dumpidl <file> -version x.y | 将服务器的 IDL 写入指定文件。类型库的版本号为 x.y |
进程内服务器可使用所有 Windows 系统中提供的“regsvr32 ”工具进行注册。
常见的编译时问题
所列出的编译器/链接器错误基于 Microsoft Visual C++ 6.0 编译器发出的错误。
“没有重载函数接受 2 个参数”
当此错误出现在使用QAXCLASS() 或 QAXFACTORY_DEFAULT() 宏的代码中时,表明该控件类没有可供默认工厂使用的构造函数。请添加一个标准的控件构造函数,或者实现一个不需要构造函数的自定义工厂。
当该错误出现在使用QAXFACTORY_EXPORT() 宏的代码中时,QAxFactory 的子类没有合适的构造函数。请为您的工厂类提供一个公共类构造函数,例如
。
“语法错误:数字后缀不正确”
未将唯一标识符作为字符串传递给QAXFACTORY_EXPORT()、QAXFACTORY_BEGIN() 或 QAXFACTORY_DEFAULT() 宏。
“未解析的外部符号 _ucm_instantiate”
服务器未导出QAxFactory 的实现。请在项目的某个实现文件中使用QAXFACTORY_EXPORT()宏来实例化并导出工厂,或者使用QAXCLASS()或QAXFACTORY_DEFAULT()宏来使用默认工厂。
“_ucm_initialize 已在...中定义”
服务器导出了多个QAxFactory 的实现,或者将同一个实现导出了两次。如果使用默认工厂,则项目中只能使用一次QAXFACTORY_BEGIN()或QAXFACTORY_DEFAULT()宏。如果服务器提供多个ActiveX控件,请使用自定义的QAxFactory 实现以及QAXFACTORY_EXPORT()宏。
分发 QAxServer 二进制文件
使用 Qt 编写的 ActiveX 服务器可以将 Qt 作为共享库使用,也可以将 Qt 静态链接到二进制文件中。这两种方式都会产生相当大的软件包(要么服务器二进制文件本身变得很大,要么必须随附 Qt DLL)。
安装独立服务器
当您的 ActiveX 服务器也能作为独立应用程序运行时,请在目标系统上安装服务器可执行文件后,使用-regserver 命令行参数运行该可执行文件。此后,服务器提供的控件将可供 ActiveX 客户端使用。
安装进程内服务器
当您的 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通过互联网分发服务器
如果您希望在网页中使用服务器中的控件,则需要让用于浏览该页面的浏览器能够访问该服务器,并且需要在页面中指定服务器包的位置。
要指定服务器的位置,请在网站的 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微软提供的 CABARC 工具可轻松生成 CAB 存档:
cabarc N simpleax.cab simpleax.exe simple.inf这些 INF 文件假设使用的是 Qt 的静态构建版本,因此 INF 文件中未列出对其他 DLL 的依赖关系。若要分发依赖于 DLL 的 ActiveX 服务器,必须添加这些依赖项,并将库文件与存档包一同提供。
使用控件
要使用 ActiveX 控件(例如将其嵌入网页中),请使用 `<object> ` HTML 标签。
<object ID="MyActiveX1" CLASSID="CLSID:ad90301a-849e-4e8b-9a91-0a6dc5f6461f">
...
<\object>要初始化控件的属性,请使用
<object ID=...>
<param name="name" value="value">
<\object>如果网页浏览器支持脚本,请使用 JavaScript、VBScript 和表单来编写控件脚本。ActiveQt 示例中包含针对示例控件的演示 HTML 页面。
受支持和不受支持的 ActiveX 客户端
以下内容主要基于我们自己对 ActiveX 控件和客户端应用程序的实验,绝非详尽无遗。
受支持的客户端
这些标准应用程序可与使用 ActiveQt 开发的 ActiveX 控件配合使用。请注意,某些客户端仅支持同进程控件。
- Internet Explorer
- Microsoft ActiveX 控件测试容器
- Microsoft Visual Studio 6.0
- Microsoft Visual Studio.NET/2003
- Microsoft Visual Basic 6.0
- 基于 MFC 和 ATL 的容器
- Sybase PowerBuilder
- 基于 ActiveQt 的容器
支持 Microsoft Office 应用程序,但您需要将控件注册为“可插入”对象。重写QAxFactory::registerClass 方法以将此属性添加到 COM 类中,或者使用Q_CLASSINFO 宏将类的“可插入”类信息设置为“是”。
不受支持的客户端
我们尚未成功使基于 ActiveQt 的 COM 对象与以下客户端应用程序配合使用。
- Borland C++ Builder(第 5 版和第 6 版)
- Borland Delphi
常见的运行时错误
服务器无响应
如果系统无法启动服务器(请通过任务管理器检查服务器是否正在运行进程),请确保系统路径中没有缺失服务器所依赖的任何 DLL(例如 Qt DLL!)。请使用依赖关系分析工具查看服务器二进制文件的所有依赖项。
如果服务器正在运行(例如,任务管理器中列出了一个进程),请参阅下一节了解有关调试服务器的信息。
无法创建对象
如果服务器在构建过程中能够正确构建和注册,但无法初始化该对象(例如通过 OLE/COM 对象查看器应用程序),请确保系统路径中没有缺少服务器所依赖的任何 DLL(例如 Qt DLL)。 使用依赖关系分析工具查看服务器二进制文件的所有依赖项。
如果服务器可以运行,请参阅下一节了解有关调试服务器的信息。
卸载和重新加载 COM 服务器时发生崩溃
如果 Active Qt COM 服务器使用的 Qt 模块超出了 Qt Base 中的范围,则必须将该 COM 服务器作为“进程外”COM 服务器进行激活。尝试激活包含Qt Quick 等模块的“进程内”COM 服务器,可能会导致在卸载该 COM 服务器后发生崩溃。
发出 COM 调用时的崩溃或异常行为
请注意,当进程外 COM 服务器向客户端发起出站调用时,它仍会继续处理其消息队列。如果客户端同时调用该服务器,可能会导致意外行为或崩溃。在此情况下,在出站调用返回之前,服务器会先执行该入站调用。 特别是,如果客户端在 ActiveX 控件正在向客户端回调时关闭该控件,可能会导致程序崩溃。此类重入问题可通过消息过滤器(IMessageFilter 和 CoRegisterMessageFilter)加以缓解。
调试运行时错误
要在 Visual Studio 中调试进程内服务器,请将服务器项目设为活动项目,并在项目设置中指定一个用于调试会话的客户端“可执行文件”(例如使用 ActiveX 测试容器)。 您可以在代码中设置断点,如果安装了调试版本,还可以逐步进入 ActiveQt 和 Qt 代码。
要调试可执行服务器,请在调试器中运行应用程序,并使用命令行参数-activex 启动。然后启动客户端并创建 ActiveX 控件的实例。COM 将为接下来尝试创建 ActiveX 控件的客户端使用现有进程。
类信息与调优
要为每个 COM 类提供属性,请使用Q_CLASSINFO 宏,该宏是 Qt 元对象系统的一部分。
| 键 | 值的含义 |
|---|---|
| 版本 | 类的版本(默认值为 1.0) |
| 描述 | 描述该类的字符串。 |
| 类ID | 类 ID。若未指定,则必须重写 `QAxFactory::classID ` 方法。 |
| 接口ID | 接口 ID。若未指定,则必须重写 `QAxFactory::interfaceID ` 方法。 |
| EventsID | 事件接口 ID。若未指定,则不会将任何信号作为 COM 事件公开。 |
| 默认属性 | 指定的属性代表该类的默认属性。例如,按钮的默认属性为“text”。 |
| DefaultSignal | 指定的信号代表该类的默认信号。例如,按钮的默认信号为“clicked”。 |
| LicenseKey | 创建对象需要指定的许可证密钥。该密钥可以为空,此时将要求使用已授权的机器。默认情况下,类无需授权。另请参阅下一节。 |
| StockEvents | 若 value 值为“yes”,则对象将暴露系统事件。参见QAxFactory::hasStockEvents() |
| ToSuperClass | 若 value 值为“yes”,则对象将暴露所有父类(直至且包含 value 中指定的类名)的功能。参见QAxFactory::exposeToSuperClass() |
| 可插入 | 若值为“yes”,则该类被注册为“可插入”,并将被列入 OLE 2 容器(例如 Microsoft Office)中。此属性默认不被设置。 |
| 可聚合 | 如果值为“no”,则该类不支持聚合。默认情况下支持聚合。 |
| 可创建 | 如果值为“no”,则客户端无法创建该类,且该类只能通过另一个类的 API 访问(即该类是子类型)。 |
| RegisterObject | 如果值为“yes”,则该类的对象已在 OLE 中注册,并可通过运行中对象表访问(即客户端可以连接到该类的一个已运行实例)。此属性仅在进程外服务器中受支持。 |
| MIME | 该对象可处理值中指定格式的数据和文件。该值的格式为 mime:extension:description。多个格式用分号分隔。 |
| CoClassAlias | 在生成的 IDL 文件和注册表中使用的类名。这对于位于命名空间中的 C++ 类特别有用——默认情况下,ActiveQt 仅会移除“::”以使 IDL 编译通过。 |
| 已实现的类别 | 以逗号分隔的类别标识符(CATID)UUID列表。 除“control”、“insertable”等之外,用于指定其他容器功能的通用机制。典型的 CATID 包括CATID_InternetAware (“{0DE86A58-2BAA-11CF-A229-00AA003D7352}”)、CATID_SafeForScripting ("{7DD95801-9882-11CF-9FA9-00AA006C42C4}")以及用户定义的 CATID 值。 |
请注意,键和值均区分大小写。
以下声明了一个 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);
...
};开发受许可的组件
如果您开发组件,您可能希望控制谁能够实例化这些组件。由于服务器二进制文件可以分发到任何客户端计算机并注册在该计算机上,因此任何人都可以将其软件中使用这些组件。
可以使用多种技术对组件进行授权,例如,创建控件的代码可以提供授权密钥,或者需要对控件运行的计算机进行授权。
要将 Qt 类标记为受许可,请使用Q_CLASSINFO() 宏指定一个“LicenseKey”。
class MyLicensedControl : public QWidget
{
Q_OBJECT
Q_CLASSINFO("LicenseKey", "<key string>")
...
};该密钥是必须的,以便在本身未获得许可的机器上创建MyLicensedControl 的实例。获得许可的开发者现在可以随其应用程序一起分发服务器二进制文件,该服务器将使用“LicenseKey”的值来创建控件,而应用程序的用户若没有许可密钥则无法创建该控件。
如果该控件的单一许可证密钥不足以满足需求(即您希望不同的开发者获得不同的许可证密钥),您可以指定一个空密钥来表示该控件需要许可证,并重写QAxFactory::validateLicenseKey() 方法以验证系统上是否存在许可证(例如通过许可证文件)。
更多接口
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;
}由于ISomeCOMInterface 是IUnknown 的子类,您必须实现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();
}
};另请参阅 ActiveQt 框架。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.