QT - QT中的COM编程(exe进程外组件形式)

4 篇文章 5 订阅

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分别对应以上文章中的测试项目。

可访问:程序下载。进行下载。

  • 9
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值