一、COM简介
1.1 COM是什么?
COM,Component Object Model,即组件对象模型,是一种以组件为发布单元的对象模型,这种模型使各软件组件可以用一种统一的方式进行交互。COM 既提供了组件之间进行交互的规范,也提供了实现交互的环境,因为组件对象之间交互的规范不依赖于任何特定的语言,所以,COM也可以是不同语言协作开发的一种标准。
COM实际上是一种组件标准,COM不仅仅提供了组件之间的接口标准,它还引入了面向对象的思维。在COM标准中,对象是一个非常活跃的元素,常常被称为COM对象。组件模块为COM对象提供了活动的空间,COM对象以接口的方式提供服务,这种接口就被称为COM接口。COM组件、COM对象、COM接口三者的关系如下图所示:
在Windows系统平台上,一个COM组件可以是一个DLL(Dynamic Linking Library,动态链接库)文件,也可以是一个EXE(可执行程序)文件。一个组件程序可以包含多个COM对象,并且每个COM对象可以实现多个接口。
当另外的组件或者普通程序(即组件的客户程序)调用组件的功能时,它首先创建一个COM对象或者通过其他途径获得COM对象,然后通过该对象所实现的COM接口调用它所提供的服务。当所有的服务结束之后,如果客户程序不再需要该COM对象,那么它就应该释放掉对象所占用的资源,包括对象自身。
1.2 COM接口与API之间的区别
COM接口和经常说的API有点相似。通过API接口层,可以很好地把两个程序连接起来,但存在一些问题:1)、当API非常多时,使用会非常不方便,需要对函数进行组织。2)、API函数需要标准化,按照统一的调用方式进行处理,以适应不同编程语言的实现,包括参数传递顺序、参数类型、函数返回处理都需要标准化。而COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件接口的补足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。
1.3 .NET组件和COM组件的区别
.NET组件和COM组件之间的主要区别在于它们的设计目标、实现方式和运行环境。.NET组件是微软推出的新一代编程模型,用于构建Web应用、桌面应用和移动应用;而COM组件是Windows操作系统中基于二进制代码通信的机制,主要用于实现Windows系统中的各种组件之间的互操作。.NET组件和COM组件在实现方式、编程语言和运行环境上有所不同。
实现方式:
.NET组件:通过C#、VB.NET等.NET编程语言编写,以.NET框架为基础,运行在.NET运行时(CLR)上。
COM组件:通过C++、VB6等编程语言编写,以COM为基础,运行在COM运行时上。
编程语言:
.NET组件:使用C#、VB.NET等.NET编程语言编写,可以跨平台运行。
COM组件:使用C++、VB6等编程语言编写,只能在Windows操作系统中运行。
运行环境:
.NET组件:运行在.NET运行时(CLR)上,支持多语言、跨平台、面向对象和类型安全等功能。
COM组件:运行在COM运行时上,支持多语言、跨平台、面向对象和类型安全等功能。
生命周期:
.NET组件:具有短暂的生命周期,一旦被加载到内存中,就可以立即运行。
COM组件:具有较长的生命周期,需要经过加载、注册、卸载等步骤,需要更多的手动管理。
安全性:
.NET组件:提供了内存管理和类型安全等功能,可以避免缓冲区溢出等安全问题。
COM组件:由于手动管理,容易出现缓冲区溢出等安全问题。
总的来说,.NET组件和COM组件在设计目标、实现方式和运行环境上有所不同,但它们都是用于构建Windows应用程序的组件化编程模型。
因此COM组件并不依赖于.NET Framework的运行环境。
但两者之间是可以相互调用,见如下文章:
COM 互操作示例:.NET 客户端和 COM 服务器
COM 互操作示例:.NET 客户端和 COM 服务器 - .NET Framework | Microsoft Learn
1.4 类厂
在创建组件对象时,客户程序调用COM库中的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息并调用组件程序的入口函数来创建组件对象。所以组件程序需要提供一个标准的入口函数 DllGetObjectClass 函数,用于提供本组件的组件信息。而在 DllGetObjectClass 中,是以类厂的方式获取组件对象的。
类厂,顾名思义,就是COM类的工厂。如果对C++比较熟悉的话,应该会知道设计模式中的工厂设计模式,其实这个类厂的概念就和工厂设计模式很相似。确切的说,类厂应该成为“对象厂”,因为类厂是COM对象的生产基地,COM库通过类厂创建COM对象;COM规定,每一个COM类,对应的都要有一个类厂专门用于该COM类的对象的创建工作。
如果一个组件程序实现了多个COM对象类,则相应的有多个类厂。所以,上述关于字典组件的结构、和多个类厂的结构就如下所示:
1.5 COM库
COM除了定义了组件程序和客户程序交互的规范以外,它也提供了COM的实现部分即COM库,使得这些规范能够真正地应用起来。并且COM库也充当了组件程序和客户程序之间的桥梁,尤其是在组件对象的创建过程中,以及在对象管理、内存管理和一些标准化操作方面起着重要的作用。
COM库的一些常用函数:
客户程序调用COM库创建组件对象的顺序图:
1.6 COM实现过程
COM客户程序、COM库、COM组件程序三者之间的协作过程
1.7 QTActiveX介绍
Qt提供了QtActiveX模块来支持微软ActiveX的开发,Qt的ActiveX和COM的开发支持两种方式:
支持将已有的COM或者ActiveX空间引入到Qt的应用程序中
支持将Qt应用程序或者Qt的对象导出成COM对象或者ActiveX控件供他人使用
具体来说,Qt是通过ActiveXQt框架中的两个模块来支持上述所说的两种方式的:
使用QAxContainer模块,通过QAxObject和QAxWidget分别支持COM对象和ActiveX控件的开发,可以通过这两个对象将外部的COM或者ActiveX组件接入到Qt应用程序
使用QAxServer模块,通过QAxAggregated、QAxBindable和QAxFactory类,通过了进程内和可执行程序exe两种方式的COM Server模式,用来将Qt写的内容导出为COM或者ActiveX供他人使用。
二、基于VS+QT开发Com组件
Qt的windows商业版本提供了ActiveQt这个framework,使用这个组件我们可以在Qt中使用ActiveX控件,并且也开发基于Qt的ActiveX控件。
开源版本是没有的,需要依赖于VS的QT插件来做开发。
2.1 环境配置
2.1.1 VS+QT+vsaddin插件安装
操作参考:
QT - QT中配置MSVC编译环境 以及 VS中配置QT开发环境_qt msvc-CSDN博客
本次最终采用QT6.7 + VS2022版本。
2.1.2 安装相关问题
1.QT安装速度提升,避免各种网络超时报错:
安装QT时,更换镜像源,以带参数的方式启动:
.\qt-unified-windows-x64-4.4.2-online --mirror https://mirrors.tuna.tsinghua.edu.cn/qt
2.VS、QT、MSVC、qtaddin版本对应问题
下载qtaddin插件,与对应VS20xx版本对应即可。
QT5.12.12不支持MSVC2019,最高支持到MSVC2017
QT5.15.2可支持到MSVC2019,但是当前没有离线包的版本,在线安装也不支持;
QT6.x可支持到MSVC2022
MSVC20xx 一般要与对应的VS20xx相对应(参考的两篇文章分别都是对应的)
因此QT所支持的MSVC版本,一般需要跟VS20xx对应起来。
网上成功的:
QT5.15.2 + MSVC 2019 + VS2019
QT5.14.2 + MSVC 2017 + VS2017
自测:
QT6.7 + MSVC2019 + VS2019 ,新建activex项目,点击生成各种报错,未找到有效解决方案;
QT5.14.2 + MSVC2017 + VS2017,但VS 2017的winform项目,看不到生成的com组件……
QT5.14.2 + MSVC2017 + VS2017(负责生成COM) + VS2022(C#负责调用com),预览界面可以看到组件UI,实际运行显示不了;并且VS2017生成com有不稳定的情况,后续编译不了了……
最终测试版本成功:
QT6.7 + MSVC2019 + VS2022 + QTaddin3.2
注意点:先用IE模式,用html测试生成的Activex控件可用,随后再用winform项目做测试,会好一些。
需要使用管理员权限打开VS202软件。
安装QT时,ActiveQT组件一定要安装,不然会出现项目找不到active相关头文件的问题:
要修改已有的qt组件,运行QT安装目录下的工具即可:
MaintenanceTool.exe
VS2019 + Qt5.12 配置完成后,无法打开 Qt 源文件解决方案(非常实用)
VS2019 + Qt5.12 配置完成后,无法打开 Qt 源文件解决方案(非常实用)_无法打开qbuttongroup源文件-CSDN博客
2.2 COM(ActiveX)组件开发
QT - QT中的COM编程(dll进程内组件形式)
QT - QT中的COM编程(dll进程内组件形式)_qt com组件-CSDN博客
2.2.1 实际代码
ActiveQtServer1.h
#pragma once
#include <QtWidgets/QWidget>
#include <ActiveQt/QAxBindable>
#include "ui_ActiveQtServer1.h"
class ActiveQtServer1 : public QWidget, public QAxBindable
{
Q_OBJECT
public:
ActiveQtServer1(QWidget *parent = nullptr);
public slots://定义两个槽函数,便于外部调用
QString getVersion();
QString getCurrentTime();
private:
Ui::ActiveQtServer1Class ui;
};
ActiveQtServer1.cpp
#include "ActiveQtServer1.h"
#include <ActiveQt/QAxFactory>
ActiveQtServer1::ActiveQtServer1(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
}
QAXFACTORY_DEFAULT(ActiveQtServer1,
"{c5e4017e-73a4-47c2-ad5d-aba20c13a6ba}",
"{4c7d8024-69c9-4377-8a73-f163a00ad8d8}",
"{c46481e5-2702-476c-9cb2-e8dca9a23a47}",
"{484c7403-d8fa-4d7d-ac12-b75f20d6e60b}",
"{a3dd71cf-f57e-49ef-a6c3-939b4d2e7339}"
)
QString ActiveQtServer1::getVersion() {
return "0.0.1";
}
QString ActiveQtServer1::getCurrentTime() {
return ui.calendarWidget->selectedDate().toString();
}
2.2.2 生成dll
需要以管理员模式运行VS,才能够正常生成和注册:
2.2.3 发布(需要通过windeploy发布依赖的文件)
D:\Qt\Qt5.12.12\5.12.12\winrt_armv7_msvc2017\bin\windeployqt.exe .\ActiveQtServer1.dll
2.2.3 IE模式 Html测试
从注册表查询classid
编写html文件,替换classid,保存到本地(可以任一目录)
<html>
<head>
<title>activeQtDemo</title>
</head>
<body>
<object id="233432" width="80%" height="80%"
classid="CLSID:869BDCDE-E935-432D-AC52-F66C8F1D27DD">
<PARAM NAME="_Version" VALUE="65536">
<!-- 以下为入坑了 -->
<!-- classid="2F12BFB8-137D-4DC2-9A93-634EFE5A6DFC">
1D991CF8-6F9D-4574-9507-B526D699F432
1D991CF8-6F9D-4574-9507-B526D699F432
-->
[Object not available! Did you forget to build and register the server?]
</object>
</body>
</html>
edge浏览器配置白名单,支持IE模式(需要支持Activex的浏览器)
通过IE浏览器打开:
点击允许加载插件
2.2.4 更新Com组件:重新生成,*.dll无法打开的问题
查看占用的进程:
把dllhost.exe对应的进程kill掉:
devenv.exe 对应的是VS winform调用方,关掉该工程即可。
二、基于QT Activex开发Com组件
参考文章:
Qt开发Activex笔记(一):环境搭建、基础开发流程和演示Demo
https://blog.51cto.com/hongpangzi/3612348
- QT QtWidgetApp调用COM
4.1 参考文章
Qt调用Com组件--QT调用COM组件DLL(dumpCPP工具)_qt dumpcpp dll-CSDN博客
4.2 实际代码
.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("{c5e4017e-73a4-47c2-ad5d-aba20c13a6ba}");
//导出支持调用的函数接口
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()");
//调用COM组件函数接口:获取版本
QString result=mpAxObj->dynamicCall("getVersion()").toString();
qDebug()<<"插件的版本号:"<<result;
//调用COM组件函数接口:获取当前时间
QString result2=mpAxObj->dynamicCall("getCurrentTime()").toString();
qDebug()<<"当前时间:"<<result2;
return a.exec();
}
- VS winform调用COM
5.1 winform工程引用com组件
操作参考:
C#-winform调用COM组件(COM组件由Qt开发)-云社区-华为云
Qt开发Activex笔记(三):C#调用Qt开发的Activex控件_qt开发ocx给c#-CSDN博客
5.2 修改生成的目标平台为x64
5.3 运行最终效果
5.4 遇到的问题:
5.2.1 点击运行后,报错没有注册类
System.Runtime.InteropServices.COMException
HResult=0x80040154
Message=没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.UnsafeNativeMethods.CoCreateInstance(Guid& clsid, Object punkOuter, Int32 context, Guid& iid)
at System.Windows.Forms.AxHost.CreateWithLicense(String license, Guid clsid)
at System.Windows.Forms.AxHost.CreateInstanceCore(Guid clsid)
at System.Windows.Forms.AxHost.CreateInstance()
at System.Windows.Forms.AxHost.GetOcxCreate()
at System.Windows.Forms.AxHost.TransitionUpTo(Int32 state)
at System.Windows.Forms.AxHost.CreateHandle()
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
at System.Windows.Forms.AxHost.EndInit()
at WindowsFormsApp4.Form1.InitializeComponent() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Form1.Designer.cs:line 64
at WindowsFormsApp4.Form1..ctor() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Form1.cs:line 17
at WindowsFormsApp4.Program.Main() in D:\VisionProject\VSWorkSpace\WinformWS\WindowsFormsApp4\WindowsFormsApp4\Program.cs:line 19
项目属性,生成的目标平台修改为x64。
5.2.2 控件已经成功添加到工具箱中,但未在活动设计器中启用
问题描述:Visual studio 2022 添加com组件到工具箱错误提示:
下列控件已经成功添加到工具箱中,但未在活动设计器中启用 ,请确认要添加的控件能够兼容当前设计器和.net framework 版本。
修改方法:
要选择上面这个Windows窗体应用(.NET Framework)
【Windows 窗体应用】的窗体属性中还有其他信息,目标框架:.NET Core 3.1
而【Windows 窗体应用(.NET Framework)】,其框架则是.NET Framework
这个.NET Core与 .NET Framework是完全不一样的东西:
- .NET framework框架开发出来的应用只能在windows上运行。
- .netcore 是开源的,开发出来的应用可以跨平台运行,比如运行在MAC,Linux上 。
而我们添加的COM组件,实际上是只应用于windows环境的技术,在一个非windows 的底层技术以及上层环境肯定就是不行的了。
5.2.3 引入控件报错
1.在工具箱中,拖入控件到UI中,会弹窗报错:
直接重新生成项目,也会报错:
生成的dll确实是64位的:
2.修改为x64平台(上述第二章的qt com dll也是基于x64编译的)后,编译正常,且AxActiveQTServer2Lib不再报错
参考资料
COM简介
COM - COM的简单介绍_com组件结构-CSDN博客
windeployqt打包Qt应用程序(Com只注册了,还不够,需要通过windeploy发布依赖的文件):
windeployqt打包Qt应用程序_qt windeployqt 打包-CSDN博客
Qt的进程间通信,以Active服务器的形式,手把手教你VS上进行Qt的COM、ActivedQt Server的开发,比保姆还保姆
https://www.cnblogs.com/Leventure/p/16971934.html
VS+QT插件创建qt 的ActiveQT Server工程踩过的坑_qt activeqt server-CSDN博客