1、开发环境
操作系统:Windows 10
编译环境:QT5.14.2 + Visual Studio2017
如果是在QT中进行COM编程,则QT的编译套件必须为 MSVC,关于如何在QT5.14.2中配置MSVC2017可以参考:QT5.14.2中配置MSVC2017。
1.1、ActiveX控件
关于COM的概念可以参考:COM的简单介绍。
ActiveX控件是Microsoft的ActiveX技术的一部分。
ActiveX控件是可以在应用程序和网络中计算机上重复使用的程序对象。创建它的主要技术是Microsoft的ActiveX技术,其中主要是组件对象模型(COM)。
ActiveX控件可以以小程序下载装入网页,也可以用在一般的Windows和Macintosh应用程序环境中。ActiveX控件可以由不同的可以识别Microsoft的COM技术的语言开发,它是一个组件或自包含的软件包,它可以在同一个或分布式的计算环境中开发或使用。
1.2、Active QT
在QT中提供了对ActiveX和COM的支持,被称为ActiveQT,在QT的帮助文档中有比较详细的描述:
ActiveQt提供了进程外组件(exe形式)和进程内组件(dll形式)的开发模式,在本文中主要记录一下进程外组件(exe形式)的开发。
2、在QT Creator中开发COM组件
2.1、创建ActiveXTest工程
ActiveXTest:建立一个独立的应用程序,只包含一个日历控件。
在ActiveQt的帮助文档中有以下解释:
大致意思是说,如果我们编写的类继承自QObject及其子类,则我们创建的是 COM对象,如果是继承自QWidget,则我们创建的是ActiveX控件。因为我们开发的是一个独立的运行程序(exe),所以就采用ActiveX控件的形式进行创建。
新建ActiveXTest工程:
创建完工程之后,在界面中只添加一个日历控件,如下所示:
此时,它就是一个普通的QT开发的小程序,还需要对它进行配置才能具备COM Server的功能。
2.2、配置工程
2.2.1、配置.pro文件
根据ActiveQt的帮助文档中介绍:
大致意思是说,如果想要创建一个进程外组件(ActiveX控件),需要在.pro文件中添加axserver参数,就像上图中显示的那样。根据帮助文档对ActiveXTest.pro文件进行修改:
添加完成之后会有以下提示:
暂时先不用管它。
2.2.2、Q_CLASSINFO()宏定义
根据ActiveQt的帮助文档中介绍:
大致意思是说,Q_CLASSINFO()宏定义是用来定义COM组件的几个识别信息的,其中ClassID(即CLSID)和InteraceID(即接口的GUID)是必须的,EventsID是当程序中有信号时才定义的,其中这些ID可以通过VS2017的“创建GUID”小工具(菜单栏->工具->创建GUID)进行创建。
最后形式如下所示:
2.2.3、QAxFactory
上面的步骤只是创建了可以导出的COM类,但要想让客户端能申请创建这个类对象,那这个类就必须让外部知道才能创建。
根据ActiveQt的帮助文档中介绍:
大致意思是说,要想实现客户端能够知道创建的COM类,则COM服务端必须要导出一个QAxFactory的实例出来,该实例提供了向系统注册/销COM服务的方法,告知 COM系统,其导出了哪些类和类型可供它创建;最简单的实现方法就是在main.cpp中使用QAxFactory提供的宏构造。
具体形式如下所示:
2.2.4、-activex
ActiveX控件以exe形式发布可以不需要注册,在启动时加上-activex参数告诉它以提供COM服务的形式启动(默认是以可执行程序启动),那客户端就可以正常使用它提供的服务了。它也可以注册到系统中去,这样不用每次都加-activex启动。
根据ActiveQt的帮助文档中介绍:
大致意思是说:对于进程外的组件(exe程序),它需要有一个main函数用来实例化一个应用程序并执行事件循环(当然,这个main函数在QT创建工程的时候会自动生成),但是如果我们想让这个程序已ActiveX Server的形式运行,就必须在生成时加上 -activex 参数,如果不加,那它就是一个标准的QT应用程序。可以使用 QAxFactory::isServer来判断是否以ActiveX Server的形式运行。
添加 -activex 参数:
修改 main 函数:
修改完成之后,点击左下角的“构建项目”按钮(锤子按钮),需要注意的是,QT一定要以管理员的身份运行!(如果不以管理员的身份运行,会显示注册不成功的错误!)这也是帮助文档中提到的(QT的本地文档中没有写,是在QT的在线文档中提到的,网址:Building ActiveX servers in Qt。):
当运行完成之后,就会显示注册成功的提示信息:
此时,打开注册表编辑器,搜索前面宏定义的ClassID,就可以在注册表中看到组件的注册信息:
2.3、发布应用程序并测试
2.3.1、发布
首先打开QT的命令行,注意选择和工程构建套件一致的命令行工具:
然后进入到应用程序的生成目录下,一般QT的应用程序的生成目录如下所示:
然后再执行 windeployqt.exe 命令寻找依赖库,具体如下所示:
执行之后会在目录下生成相应的依赖文件:
2.3.2、网页测试
在任意目录下新建一个ActiveXTest.html文件,写入以下测试代码:
<html>
<head>
<title>ActiveXTest</title>
</head>
<body>
<object id="ActiveXTest" width="80%" height="80%"
classid="clsid:BEFF0314-2211-427B-9AD4-1A6DF3C5BDEF">
</object>
</body>
</html>
其中的clsid即为宏定义中定义的ClassID。
右键点击选择Internet Explorera方式打开:
允许弹出的内容
加载成功:
至此,在QT Creator中创建COM组件的过程就结束了。
3、在QT Creator中应用COM组件
3.1、直接调用COM组件 - ComTest1
创建测试工程:
创建成功之后,只保留工程中的main.cpp文件,并修改成如下形式:
#include <QApplication>
#include <QAxObject>
#include <QDebug>
#include <QFile>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QAxObject *mpAxObj;
mpAxObj = new QAxObject();
//指定调用的COM组件类ID(clsid\ClassID),这个ID要填正确,就是前面宏定义的 ClassID.
mpAxObj->setControl("{BEFF0314-2211-427B-9AD4-1A6DF3C5BDEF}");
//导出支持调用的函数接口
QString DOC = mpAxObj->generateDocumentation();
QFile outFile("com_function.html");
outFile.open(QIODevice ::ReadWrite|QIODevice ::Text);
QTextStream TS(&outFile);
TS<<DOC<<endl;
//调用COM组件函数接口: 显示界面
mpAxObj->dynamicCall("show()");
return a.exec();
}
还需要在.pro文件中添加 axcontainer 库,如下所示:
运行结果如下:
3.2、界面中集成COM组件
上一章里介绍了在QtCreate里使用QAxObject调用COM组件,并完成函数接口调用;如果调用的COM组件是带界面的程序,并需要集成到当前QT程序中,就需要使用QAxWidget实现。
为了能更直观的看到调用效果,需要对 第2章节中的 ActiveXTest组件 进行修改,添加一个获取当前时间的函数,然后重新编译生成。
根据帮助文档:Building ActiveX servers in Qt。的介绍,如果要调用ActiveX的方法,需要将COM组件的对外的接口函数定义成public slots的形式:
代码修改如下所示:
widget.h:
widget.cpp:
3.2.1、代码形式 - ComTest2
直接在工程里写代码,new一个QAxWidget并添加到布局器即可。注意,.pro文件需添加 QT += axcontainer 语句。
界面代码如下:
widget.h:
//界面类
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public:
QPushButton* pBtn;
QLineEdit* pLet;
QAxWidget* pAxwidget;
public slots:
void OnBtnClicked();
};
widget.cpp:
#include <QDebug>
#include <QFile>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//创建控件
pBtn = new QPushButton("获取当前时间");
pLet = new QLineEdit();
pAxwidget = new QAxWidget();
//创建布局
QHBoxLayout *upLayout = new QHBoxLayout();
upLayout->addWidget(pBtn);
upLayout->addWidget(pLet);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(upLayout);
mainLayout->addWidget(pAxwidget);
this->setLayout(mainLayout);
//connect-slots
connect(pBtn, SIGNAL(clicked()), this, SLOT(OnBtnClicked()));
//引入ActiveX控件
pAxwidget->resize(200, 300);
//指定调用的COM组件类ID(clsid\ClassID),这个ID要填正确,就是前面宏定义的 ClassID.
pAxwidget->setControl("{BEFF0314-2211-427B-9AD4-1A6DF3C5BDEF}");
//导出支持调用的函数接口
QString DOC = pAxwidget->generateDocumentation();
QFile outFile("com_function.html");
outFile.open(QIODevice ::ReadWrite|QIODevice ::Text);
QTextStream TS(&outFile);
TS << DOC << endl;
//调用COM组件函数接口: 获取版本号
QString result = pAxwidget->dynamicCall("getVersion()").toString();
qDebug()<<"VersionNumber: "<<result;
}
Widget::~Widget()
{
}
void Widget::OnBtnClicked(){
//调用COM组件函数接口: 获取当前日期,并显示在显示框中
QString curDate = pAxwidget->dynamicCall("getCurrentTime()").toString();
pLet->setText(curDate);
}
界面显示如下:
可以看到,当调用 getVersion()函数时,正确返回了版本号:
当点击“获取当前时间”按钮时,会正确在显示框中显示当前时间:
3.2.2、UI设计形式 - ComTest3
按照3.2.1章节设计UI界面,其中QAxWidget控件如下所示:
右键点击拖放的QAxWidget控件,选择“设置控件”,会弹出以下界面:
在弹出的界面中,包含了当前系统中可用的COM组件,要确保自己的开发COM组件已成功注册在系统中。选中我们自己开发COM组件,下方是对应ClassID,可以确定这个COM组件确实是我们自己开发的,然后点击OK。
widget.cpp:
#include <QDebug>
#include <QFile>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//指定调用的COM组件类ID,这个ID要填正确.
ui->axWidget->setControl("{BEFF0314-2211-427B-9AD4-1A6DF3C5BDEF}");
//导出支持调用的函数接口
QString DOC = ui->axWidget->generateDocumentation();
QFile outFile("com_function.html");
outFile.open(QIODevice ::ReadWrite|QIODevice ::Text);
QTextStream TS(&outFile);
TS<< DOC << endl;
//调用COM组件函数接口: 获取版本号
QString result = ui->axWidget->dynamicCall("getVersion()").toString();
qDebug()<<"VersionNumber: "<<result;
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//调用COM组件函数接口: 获取当前日期,并显示在显示框中
QString curDate = ui->axWidget->dynamicCall("getCurrentTime()").toString();
ui->lineEdit->setText(curDate);
}
运行结果:
4、不通过注册表的形式调用ActiveX组件
在 2.2.4章节 中,介绍了通过在QT编译时通过添加 -activex 参数的形式生成程序,并且在QT中,ActiveX Server的注册是QT自动注册的,如果在本机的话可以很容易的通过注册表查询ClassID,然后调用COM组件。如果不在一台机子上,其他机子上并没有进行COM组件的注册,那么在调用COM组件时就会报无法找到注册组件的错误,就拿3.2.2.章节的例子来说,会报如下错误:
同时界面上也就无法正常显示ActiveX控件了。那应该怎么办呢?
其实,ActiveX控件以exe形式发布可以不需要注册,只需要在启动时加上 -activex 参数告诉它以提供COM服务的形式启动(默认是以可执行程序启动),那客户端就可以正常使用它提供的服务了。
以管理员身份打开CMD,进入到ActiveXTest.exe所在目录,然后执行 ActiveXTest.exe -activex 命令(最好已经导出了ActiveXTest.exe的依赖库,2.3.1章节), 如下图所示:
然后再运行 3.2.2.章节的例子,就能够正确加载COM组件了:
5、测试所用工程文件
下图为测试所用的工程文件目录:
其中,ActiveXTest项目为创建ActveX控件的,ComTest1、ComTest2、ComTest3分别对应以上文章中的测试项目。
可访问:程序下载。进行下载。