

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例 - 轩先生。
source link: https://www.cnblogs.com/Leventure/p/16998111.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例以及错误总结
ok,前面铺垫了那么多,现在来写一个开发实例,我会把其中隐藏的坑和陷阱简单谈谈,并在文章最后总结。
不愿意看长篇大论的可以直接看实例:CS_COM_Build
废话不多说直接起步。
先说场景,我这边是一个C#的DLL,然后让一个COM组件去加载这个DLL,然后再让Qt去调用这个C#的COM组件,也就是说有三个工程,如图所示:
其中FrameWork是一个窗体的DLL,如下图左边所示,右边是QtController的窗体,MiddleCOM则是充当了一个中间件,用于为普通的C#DLL提供COM服务。

如图所示,两个窗体之间可以进行交互,其中Qt应用程序是主程序,而C#的窗体程序则是以COM组件形式发布的。
一、写一个窗体
ok话不多说,先来写一个窗体:
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FrameWork
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
//声明一个委托,可以向外部发送消息
public delegate void SendMessageOutEventHandler(System.String strValue);
public event SendMessageOutEventHandler SendMessageOut;
private void button1_Click(object sender, EventArgs e)
{
this.SendMessageOut(this.textBox1.Text);
}
//接收消息,展示到窗体上
public void getMessage(System.String strValue)
{
this.richTextBox1.AppendText(strValue);
}
}
}

二、写一个COM的中间件
现在我们来做套壳的COM组件。先添加一个C#的类库(这不会也要教吧)
AssemblyInfo.cs中,讲这个[assembly:ComVisuble(false)] 改为true

右键这个COM工程,点击属性,找到为COM互操作注册
如果没有这一步,可能会导致Qt在调用的时候弹出报错提示CoCreateInstance failure(系统在找不到指定文件。),原因是如果没有互操作注册,在注册表中你去找到你的这个类,你会发现少了几行,比如BaseCode,就会导致COM在注册的时候找不到实际的代码文件,无法找到DLL文件进行加载。

然后我们来写一下这个COM导出类,注意事项都在代码内,可以自己看看
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace MiddleCOM
{
//Author:Leventure
//DateTime:2022.12.22
//Description:一个C# COM组件实例
//注:C#中的类,无论是方法还是事件,都需要通过接口的形式向外公布,方法接口需要通过方法接口去提供服务,如果只是类内提供的函数或者事件,在外部可能无法正常使用。
//注2:导出接口可以不需要设置ComVisible(true),这样可以使得导出的接口更加清晰
//注3:这个Guid是唯一的,可以使用VS提供的Guid生成工具生成
//方法接口,向外提供方法,注意[InterfaceType(ComInterfaceType.InterfaceIsDual)]的声明这样的接口是双向的
[Guid("7EEDF2D8-836C-4294-90A0-7A144ADC93F9")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IOutClass
{
[DispId(1)]
void getMessage(System.String strValue);
}
//事件接口,注意[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]这代表这个是用作事件处理
[Guid("7FE32A1D-F239-45ad-8188-89738C6EDB6F")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IOutClass_Event
{
[DispId(11)]
void SendMessageOut(System.String strValue);
}
[Guid("76BBA445-7554-4308-8487-322BAE955527")]
[ClassInterface(ClassInterfaceType.None)] // 指示不为类生成类接口。如果未显式实现任何接口,则该类将只能通过 IDispatch 接口提供后期绑定访问。这是 System.Runtime.InteropServices.ClassInterfaceAttribute
// 的推荐设置。要通过由类显式实现的接口来公开功能,唯一的方法是使用 ClassInterfaceType.None。
[ComDefaultInterface(typeof(IOutClass))] // 以指定的 System.Type 对象作为向 COM 公开的默认接口初始化 System.Runtime.InteropServices.ComDefaultInterfaceAttribute
// 类的新实例。
[ComSourceInterfaces(typeof(IOutClass_Event))] //使用要用作源接口的类型初始化 System.Runtime.InteropServices.ComSourceInterfacesAttribute 类的新实例。
[ComVisible(true)] //提供COM的可访问性
[ProgId("IOutClass")] //给这个导出类一个名称
public class OutClass : IOutClass
{
private FrameWork.Form1 form1 = null;
public OutClass()
{
if(form1 == null)
{
this.form1 = new FrameWork.Form1();
this.form1.SendMessageOut += new FrameWork.Form1.SendMessageOutEventHandler(this.SendMessageReceived);
this.form1.Show();
}
}
//提供方法向C#DLL发送消息
public void getMessage(System.String strValue)
{
if(this.form1 != null)
{
this.form1.getMessage(strValue);
}
}
//这个是从方法类中继承来的向外发送消息事件
public delegate void SendMessageEventHandler(System.String strValue);
public event SendMessageEventHandler SendMessageOut;
//从C#DLL中传来的消息,转发给COM服务器
private void SendMessageReceived(System.String strValue)
{
//向外发送消息
SendMessageOut(strValue);
}
}
}
ok,到这里我们的COM组件之旅几乎就已经完成了。编译这个DLL之后我们需要将其注册到我们的系统中去。这里.net的DLL和非托管的DLL的注册方式不一样。非托管的DLL注册可能是通过直接在cmd中输入regsvr32 xxx.dll进行注册,但是.net的DLL需要通过它.net自己的工具进行的注册,我们可以在菜单栏中找到vs的开发者工具,如图所示

(我这里是用的VS2019开发,其实这里用的只是regasm.exe这个工具,用哪个版本的无所谓)
调用命令如下:

注:如果你用的是系统提供的注册工具regsvr32注册的话,会提示你没有DllServerRegister入口点,请检查是否是dll或者ocx,这个是因为.net框架提供的COM组件是没有给定这两个东西的,所以需要用他们自己的工具!
OK,这个时候应该就已经注册完成了,为了检验成果我们可以去Windows系统注册表中查看,比如我们这里声明的导出类的名称为[ProgId("IOutClass")],就在注册表内查找 IOutClass,或者查找导出类对应的Guid,应该就能查找到对应的内容
比如这个路径下
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Classes\CLSID{76BBA445-7554-4308-8487-322BAE955527}\ProgId

如果出现了这一条,就说明你的注册成功了
三、编写Qt程序来调用COM组件
这部分内容比较简单,主要是提一下报错:
直接上界面和代码吧,这部分没有什么需要特别注意的:

#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_QtController.h"
#include "qaxobject.h"
#include "qfile.h"
#include "qtextstream.h"
#include "qdebug.h"
class QtController : public QMainWindow
{
Q_OBJECT
public:
//构造函数里调用了这个Init,懒得写了,将就着看吧
QtController(QWidget *parent = nullptr);
~QtController();
QAxObject ax_test;
void Init() {
this->ax_test.setControl("IOutClass");
//获取接口文档
QString interfaces = ax_test.generateDocumentation();
QFile docs("AX_Interfaces.html");
docs.open(QIODevice::ReadWrite | QIODevice::Text);
QTextStream TS(&docs);
TS << interfaces << endl;
qDebug() << QObject::connect(&this->ax_test, SIGNAL(SendMessageOut(QString)), this, SLOT(getMessageFromCS(QString)));
}
private slots:
void on_pushButton_clicked() {
this->ax_test.dynamicCall("getMessage(QString)", this->ui.lineEdit->text());
}
void getMessageFromCS(QString strValue) {
this->ui.textEdit->append(strValue);
}
private:
Ui::QtControllerClass ui;
};
这样基本上就能保证调用了,这里提几个BUG,也是我们困扰了很久的地方:(凭借记忆写的,不一定全对哈)
四、查漏补缺、总结:
1.调用时提示 No Such Property、或者如图所示:

答:就我们目前的经验来看,有可能是你的COM组件中提供的属性有问题,你依赖了其他的DLL,但是这个被依赖的DLL它的依赖可能没被加载,也就是说缺少了部分依赖,需要你自己补全所有依赖。
2.调用时报错UnKnownError,如图:

答:这有可能是因为被调用的COM组件的位数大于调用方的位数导致的,比如32位的应用程序调用64位的进程,可能会导致UnKnown Error。
3.报错提示CoCreateInstance failure(系统找不到指定的文件。)

答:有可能是由于你在编写COM组件的时候没有在COM组件的属性中勾选上COM互操作选项,导致注册表内BaseCode行未注册成功。
4.注册DLL时,显示找不到入口点DllRegisterServer

答:需要用.net提供的COM注册服务软件regasm,具体使用方法见上方。
5.注册的C# COM 组件,为什么Event变成了 add_xxx(IDispatch *value) 和remove_xxx(IDispatch *value) 了?(注:xxx是事件的名称)
答:事件需要通过继承事件接口来继承暴露,函数需要通过函数接口来继承暴露,这样会变成另外一种形式,我没用过,我不能确保能不能用。
还有什么问题可以提问,我看到了就会回答,如果需要私聊可以联系我的Github提交issue
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK